4 * Copyright (C) 2008 The Geeqie Team
8 * This software is released under the GNU General Public License (GNU GPL).
9 * Please read the included file COPYING for more information.
10 * This software comes with no warranty of any kind, use at your own risk!
17 #include "filefilter.h"
20 #include "secure_save.h"
21 #include "thumb_standard.h"
22 #include "ui_fileops.h"
25 static GHashTable *file_data_pool = NULL;
27 static gint sidecar_file_priority(const gchar *path);
31 *-----------------------------------------------------------------------------
32 * text conversion utils
33 *-----------------------------------------------------------------------------
36 gchar *text_from_size(gint64 size)
42 /* what I would like to use is printf("%'d", size)
43 * BUT: not supported on every libc :(
47 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
48 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
52 a = g_strdup_printf("%d", (guint)size);
58 b = g_new(gchar, l + n + 1);
83 gchar *text_from_size_abrev(gint64 size)
85 if (size < (gint64)1024)
87 return g_strdup_printf(_("%d bytes"), (gint)size);
89 if (size < (gint64)1048576)
91 return g_strdup_printf(_("%.1f K"), (double)size / 1024.0);
93 if (size < (gint64)1073741824)
95 return g_strdup_printf(_("%.1f MB"), (double)size / 1048576.0);
98 /* to avoid overflowing the double, do division in two steps */
100 return g_strdup_printf(_("%.1f GB"), (double)size / 1024.0);
103 /* note: returned string is valid until next call to text_from_time() */
104 const gchar *text_from_time(time_t t)
106 static gchar *ret = NULL;
110 GError *error = NULL;
112 btime = localtime(&t);
114 /* the %x warning about 2 digit years is not an error */
115 buflen = strftime(buf, sizeof(buf), "%x %H:%M", btime);
116 if (buflen < 1) return "";
119 ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
122 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
131 *-----------------------------------------------------------------------------
133 *-----------------------------------------------------------------------------
136 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source);
137 static void file_data_check_sidecars(FileData *fd);
138 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
141 void file_data_increment_version(FileData *fd)
144 if (fd->parent) fd->parent->version++;
147 static void file_data_set_collate_keys(FileData *fd)
149 gchar *caseless_name;
151 g_assert(g_utf8_validate(fd->name, -1, NULL));
153 caseless_name = g_utf8_casefold(fd->name, -1);
155 g_free(fd->collate_key_name);
156 g_free(fd->collate_key_name_nocase);
158 #if GLIB_CHECK_VERSION(2, 8, 0)
159 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
160 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
162 fd->collate_key_name = g_utf8_collate_key(fd->name, -1);
163 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
165 g_free(caseless_name);
168 static void file_data_set_path(FileData *fd, const gchar *path)
170 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
171 g_assert(file_data_pool);
175 if (fd->original_path)
177 g_hash_table_remove(file_data_pool, fd->original_path);
178 g_free(fd->original_path);
180 fd->original_path = g_strdup(path);
181 g_hash_table_insert(file_data_pool, fd->original_path, fd);
183 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
185 fd->path = g_strdup(path);
187 fd->extension = fd->name + 1;
188 file_data_set_collate_keys(fd);
192 fd->path = g_strdup(path);
193 fd->name = filename_from_path(fd->path);
195 if (strcmp(fd->name, "..") == 0)
197 gchar *dir = remove_level_from_path(path);
199 fd->path = remove_level_from_path(dir);
202 fd->extension = fd->name + 2;
203 file_data_set_collate_keys(fd);
206 else if (strcmp(fd->name, ".") == 0)
209 fd->path = remove_level_from_path(path);
211 fd->extension = fd->name + 1;
212 file_data_set_collate_keys(fd);
216 fd->extension = extension_from_path(fd->path);
217 if (fd->extension == NULL)
218 fd->extension = fd->name + strlen(fd->name);
220 file_data_set_collate_keys(fd);
223 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
225 gboolean ret = FALSE;
228 if (fd->size != st->st_size ||
229 fd->date != st->st_mtime)
231 fd->size = st->st_size;
232 fd->date = st->st_mtime;
233 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
234 fd->thumb_pixbuf = NULL;
235 file_data_increment_version(fd);
236 file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
240 work = fd->sidecar_files;
243 FileData *sfd = work->data;
247 if (!stat_utf8(sfd->path, &st))
251 file_data_disconnect_sidecar_file(fd, sfd);
256 ret |= file_data_check_changed_files_recursive(sfd, &st);
262 static gboolean file_data_check_changed_files(FileData *fd)
264 gboolean ret = FALSE;
267 if (fd->parent) fd = fd->parent;
269 if (!stat_utf8(fd->path, &st))
272 FileData *sfd = NULL;
274 /* parent is missing, we have to rebuild whole group */
279 work = fd->sidecar_files;
285 file_data_disconnect_sidecar_file(fd, sfd);
287 if (sfd) file_data_check_sidecars(sfd); /* this will group the sidecars back together */
288 file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
292 ret |= file_data_check_changed_files_recursive(fd, &st);
298 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean check_sidecars)
302 DEBUG_2("file_data_new: '%s' %d", path_utf8, check_sidecars);
305 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
307 fd = g_hash_table_lookup(file_data_pool, path_utf8);
313 changed = file_data_check_changed_files(fd);
315 changed = file_data_check_changed_files_recursive(fd, st);
316 if (changed && check_sidecars && sidecar_file_priority(fd->extension))
317 file_data_check_sidecars(fd);
318 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
320 return file_data_ref(fd);
323 fd = g_new0(FileData, 1);
327 fd->collate_key_name = NULL;
328 fd->collate_key_name_nocase = NULL;
329 fd->original_path = NULL;
331 fd->size = st->st_size;
332 fd->date = st->st_mtime;
333 fd->thumb_pixbuf = NULL;
334 fd->sidecar_files = NULL;
336 fd->magick = 0x12345678;
338 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
341 file_data_check_sidecars(fd);
346 static void file_data_check_sidecars(FileData *fd)
350 FileData *parent_fd = NULL;
353 if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
356 base_len = fd->extension - fd->path;
357 fname = g_string_new_len(fd->path, base_len);
358 work = sidecar_ext_get_list();
362 /* check for possible sidecar files;
363 the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
364 they have fd->ref set to 0 and file_data unref must chack and free them all together
365 (using fd->ref would cause loops and leaks)
369 gchar *ext = work->data;
373 if (strcmp(ext, fd->extension) == 0)
375 new_fd = fd; /* processing the original file */
380 g_string_truncate(fname, base_len);
381 g_string_append(fname, ext);
383 if (!stat_utf8(fname->str, &nst))
386 new_fd = file_data_new(fname->str, &nst, FALSE);
388 if (new_fd->disable_grouping)
390 file_data_unref(new_fd);
394 new_fd->ref--; /* do not use ref here */
398 parent_fd = new_fd; /* parent is the one with the highest prio, found first */
400 file_data_merge_sidecar_files(parent_fd, new_fd);
402 g_string_free(fname, TRUE);
406 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars)
408 gchar *path_utf8 = path_to_utf8(path);
409 FileData *ret = file_data_new(path_utf8, st, check_sidecars);
415 FileData *file_data_new_simple(const gchar *path_utf8)
419 if (!stat_utf8(path_utf8, &st))
425 return file_data_new(path_utf8, &st, TRUE);
428 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
430 sfd->parent = target;
431 if (!g_list_find(target->sidecar_files, sfd))
432 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
433 file_data_increment_version(sfd); /* increments both sfd and target */
438 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
442 file_data_add_sidecar_file(target, source);
444 work = source->sidecar_files;
447 FileData *sfd = work->data;
448 file_data_add_sidecar_file(target, sfd);
452 g_list_free(source->sidecar_files);
453 source->sidecar_files = NULL;
455 target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
461 FileData *file_data_ref(FileData *fd)
463 if (fd == NULL) return NULL;
464 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
466 // return g_memdup(fd, sizeof(FileData));
467 g_assert(fd->magick == 0x12345678);
472 static void file_data_free(FileData *fd)
474 g_assert(fd->magick == 0x12345678);
475 g_assert(fd->ref == 0);
477 g_hash_table_remove(file_data_pool, fd->original_path);
480 g_free(fd->original_path);
481 g_free(fd->collate_key_name);
482 g_free(fd->collate_key_name_nocase);
483 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
485 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
487 file_data_change_info_free(NULL, fd);
491 void file_data_unref(FileData *fd)
493 if (fd == NULL) return;
494 g_assert(fd->magick == 0x12345678);
497 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
502 FileData *parent = fd->parent ? fd->parent : fd;
507 work = parent->sidecar_files;
510 FileData *sfd = work->data;
516 /* none of parent/children is referenced, we can free everything */
518 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, parent->path);
520 work = parent->sidecar_files;
523 FileData *sfd = work->data;
528 g_list_free(parent->sidecar_files);
529 parent->sidecar_files = NULL;
531 file_data_free(parent);
535 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
537 sfd->parent = target;
538 g_assert(g_list_find(target->sidecar_files, sfd));
540 file_data_increment_version(sfd); /* increments both sfd and target */
542 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
554 /* disables / enables grouping for particular file, sends UPDATE notification */
555 void file_data_disable_grouping(FileData *fd, gboolean disable)
557 if (!fd->disable_grouping == !disable) return;
558 fd->disable_grouping = !!disable;
564 FileData *parent = file_data_ref(fd->parent);
565 file_data_disconnect_sidecar_file(parent, fd);
566 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
567 file_data_send_notification(parent, NOTIFY_TYPE_INTERNAL);
568 file_data_unref(parent);
570 else if (fd->sidecar_files)
572 GList *sidecar_files = filelist_copy(fd->sidecar_files);
573 GList *work = sidecar_files;
576 FileData *sfd = work->data;
578 file_data_disconnect_sidecar_file(fd, sfd);
579 file_data_send_notification(sfd, NOTIFY_TYPE_INTERNAL);
581 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
582 file_data_check_sidecars((FileData *)sidecar_files->data); /* this will group the sidecars back together */
583 filelist_free(sidecar_files);
588 file_data_check_sidecars(fd);
589 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
593 /* compare name without extension */
594 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
596 size_t len1 = fd1->extension - fd1->name;
597 size_t len2 = fd2->extension - fd2->name;
599 if (len1 < len2) return -1;
600 if (len1 > len2) return 1;
602 return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
605 gboolean file_data_add_change_info(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
607 FileDataChangeInfo *fdci;
609 if (fd->change) return FALSE;
611 fdci = g_new0(FileDataChangeInfo, 1);
615 fdci->source = g_strdup(src);
617 fdci->source = g_strdup(fd->path);
620 fdci->dest = g_strdup(dest);
627 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
635 g_free(fdci->source);
648 *-----------------------------------------------------------------------------
649 * sidecar file info struct
650 *-----------------------------------------------------------------------------
655 static gint sidecar_file_priority(const gchar *path)
657 const char *extension = extension_from_path(path);
661 if (extension == NULL)
664 work = sidecar_ext_get_list();
667 gchar *ext = work->data;
670 if (strcmp(extension, ext) == 0) return i;
678 *-----------------------------------------------------------------------------
680 *-----------------------------------------------------------------------------
683 static SortType filelist_sort_method = SORT_NONE;
684 static gint filelist_sort_ascend = TRUE;
687 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
689 if (!filelist_sort_ascend)
696 switch (filelist_sort_method)
701 if (fa->size < fb->size) return -1;
702 if (fa->size > fb->size) return 1;
703 /* fall back to name */
706 if (fa->date < fb->date) return -1;
707 if (fa->date > fb->date) return 1;
708 /* fall back to name */
710 #ifdef HAVE_STRVERSCMP
712 return strverscmp(fa->name, fb->name);
719 if (options->file_sort.case_sensitive)
720 return strcmp(fa->collate_key_name, fb->collate_key_name);
722 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
725 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gint ascend)
727 filelist_sort_method = method;
728 filelist_sort_ascend = ascend;
729 return filelist_sort_compare_filedata(fa, fb);
732 static gint filelist_sort_file_cb(void *a, void *b)
734 return filelist_sort_compare_filedata(a, b);
737 GList *filelist_sort_full(GList *list, SortType method, gint ascend, GCompareFunc cb)
739 filelist_sort_method = method;
740 filelist_sort_ascend = ascend;
741 return g_list_sort(list, cb);
744 GList *filelist_insert_sort_full(GList *list, void *data, SortType method, gint ascend, GCompareFunc cb)
746 filelist_sort_method = method;
747 filelist_sort_ascend = ascend;
748 return g_list_insert_sorted(list, data, cb);
751 GList *filelist_sort(GList *list, SortType method, gint ascend)
753 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
756 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gint ascend)
758 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
762 static GList *filelist_filter_out_sidecars(GList *flist)
765 GList *flist_filtered = NULL;
769 FileData *fd = work->data;
772 if (fd->parent) /* remove fd's that are children */
775 flist_filtered = g_list_prepend(flist_filtered, fd);
779 return flist_filtered;
782 static gint filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gint follow_symlinks)
789 int (*stat_func)(const char *path, struct stat *buf);
791 g_assert(files || dirs);
793 if (files) *files = NULL;
794 if (dirs) *dirs = NULL;
796 pathl = path_from_utf8(dir_fd->path);
797 if (!pathl) return FALSE;
811 while ((dir = readdir(dp)) != NULL)
813 struct stat ent_sbuf;
814 const gchar *name = dir->d_name;
817 if (!options->file_filter.show_hidden_files && ishidden(name))
820 filepath = g_build_filename(pathl, name, NULL);
821 if (stat_func(filepath, &ent_sbuf) >= 0)
823 if (S_ISDIR(ent_sbuf.st_mode))
825 /* we ignore the .thumbnails dir for cleanliness */
827 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
828 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
829 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
830 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
832 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE));
837 if (files && filter_name_exists(name))
839 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE));
850 if (dirs) *dirs = dlist;
851 if (files) *files = filelist_filter_out_sidecars(flist);
856 gint filelist_read(FileData *dir_fd, GList **files, GList **dirs)
858 return filelist_read_real(dir_fd, files, dirs, TRUE);
861 gint filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
863 return filelist_read_real(dir_fd, files, dirs, FALSE);
866 void filelist_free(GList *list)
873 file_data_unref((FileData *)work->data);
881 GList *filelist_copy(GList *list)
883 GList *new_list = NULL;
894 new_list = g_list_prepend(new_list, file_data_ref(fd));
897 return g_list_reverse(new_list);
900 GList *filelist_from_path_list(GList *list)
902 GList *new_list = NULL;
913 new_list = g_list_prepend(new_list, file_data_new_simple(path));
916 return g_list_reverse(new_list);
919 GList *filelist_to_path_list(GList *list)
921 GList *new_list = NULL;
932 new_list = g_list_prepend(new_list, g_strdup(fd->path));
935 return g_list_reverse(new_list);
938 GList *filelist_filter(GList *list, gint is_dir_list)
942 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
947 FileData *fd = (FileData *)(work->data);
948 const gchar *name = fd->name;
950 if ((!options->file_filter.show_hidden_files && ishidden(name)) ||
951 (!is_dir_list && !filter_name_exists(name)) ||
952 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
953 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
957 list = g_list_remove_link(list, link);
969 *-----------------------------------------------------------------------------
971 *-----------------------------------------------------------------------------
974 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
976 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
979 GList *filelist_sort_path(GList *list)
981 return g_list_sort(list, filelist_sort_path_cb);
984 static void filelist_recursive_append(GList **list, GList *dirs)
991 FileData *fd = (FileData *)(work->data);
995 if (filelist_read(fd, &f, &d))
997 f = filelist_filter(f, FALSE);
998 f = filelist_sort_path(f);
999 *list = g_list_concat(*list, f);
1001 d = filelist_filter(d, TRUE);
1002 d = filelist_sort_path(d);
1003 filelist_recursive_append(list, d);
1011 GList *filelist_recursive(FileData *dir_fd)
1016 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1017 list = filelist_filter(list, FALSE);
1018 list = filelist_sort_path(list);
1020 d = filelist_filter(d, TRUE);
1021 d = filelist_sort_path(d);
1022 filelist_recursive_append(&list, d);
1030 * marks and orientation
1034 gboolean file_data_get_mark(FileData *fd, gint n)
1036 return !!(fd->marks & (1 << n));
1039 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1041 if (!value == !(fd->marks & (1 << n))) return;
1043 fd->marks = fd->marks ^ (1 << n);
1044 file_data_increment_version(fd);
1045 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1048 gint file_data_get_user_orientation(FileData *fd)
1050 return fd->user_orientation;
1053 void file_data_set_user_orientation(FileData *fd, gint value)
1055 if (fd->user_orientation == value) return;
1057 fd->user_orientation = value;
1058 file_data_increment_version(fd);
1059 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1065 * file_data - operates on the given fd
1066 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1070 /* return list of sidecar file extensions in a string */
1071 gchar *file_data_sc_list_to_string(FileData *fd)
1074 GString *result = g_string_new("");
1076 work = fd->sidecar_files;
1079 FileData *sfd = work->data;
1081 result = g_string_append(result, "+ ");
1082 result = g_string_append(result, sfd->extension);
1084 if (work) result = g_string_append_c(result, ' ');
1087 return g_string_free(result, FALSE);
1093 * add FileDataChangeInfo (see typedefs.h) for the given operation
1094 * uses file_data_add_change_info
1096 * fails if the fd->change already exists - change operations can't run in parallel
1097 * fd->change_info works as a lock
1099 * dest can be NULL - in this case the current name is used for now, it will
1104 FileDataChangeInfo types:
1106 MOVE - patch is changed, name may be changed too
1107 RENAME - path remains unchanged, name is changed
1108 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1109 sidecar names are changed too, extensions are not changed
1111 UPDATE - file size, date or grouping has been changed
1114 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1116 FileDataChangeInfo *fdci;
1118 if (fd->change) return FALSE;
1120 fdci = g_new0(FileDataChangeInfo, 1);
1125 fdci->source = g_strdup(src);
1127 fdci->source = g_strdup(fd->path);
1130 fdci->dest = g_strdup(dest);
1137 void file_data_free_ci(FileData *fd)
1139 FileDataChangeInfo *fdci = fd->change;
1144 g_free(fdci->source);
1153 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1157 if (fd->parent) fd = fd->parent;
1159 if (fd->change) return FALSE;
1161 work = fd->sidecar_files;
1164 FileData *sfd = work->data;
1166 if (sfd->change) return FALSE;
1170 file_data_add_ci(fd, type, NULL, NULL);
1172 work = fd->sidecar_files;
1175 FileData *sfd = work->data;
1177 file_data_add_ci(sfd, type, NULL, NULL);
1184 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1188 if (fd->parent) fd = fd->parent;
1190 if (!fd->change || fd->change->type != type) return FALSE;
1192 work = fd->sidecar_files;
1195 FileData *sfd = work->data;
1197 if (!sfd->change || sfd->change->type != type) return FALSE;
1205 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1207 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1208 file_data_sc_update_ci_copy(fd, dest_path);
1212 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1214 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1215 file_data_sc_update_ci_move(fd, dest_path);
1219 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1221 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1222 file_data_sc_update_ci_rename(fd, dest_path);
1226 gboolean file_data_sc_add_ci_delete(FileData *fd)
1228 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1231 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1233 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1234 file_data_sc_update_ci_unspecified(fd, dest_path);
1238 void file_data_sc_free_ci(FileData *fd)
1242 if (fd->parent) fd = fd->parent;
1244 file_data_free_ci(fd);
1246 work = fd->sidecar_files;
1249 FileData *sfd = work->data;
1251 file_data_free_ci(sfd);
1256 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1259 gboolean ret = TRUE;
1264 FileData *fd = work->data;
1266 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1273 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1276 gboolean ret = TRUE;
1281 FileData *fd = work->data;
1283 if (!file_data_sc_add_ci_copy(fd, dest)) ret = FALSE;
1290 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1293 gboolean ret = TRUE;
1298 FileData *fd = work->data;
1300 if (!file_data_sc_add_ci_move(fd, dest)) ret = FALSE;
1307 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1310 gboolean ret = TRUE;
1315 FileData *fd = work->data;
1317 if (!file_data_sc_add_ci_rename(fd, dest)) ret = FALSE;
1324 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1327 gboolean ret = TRUE;
1332 FileData *fd = work->data;
1334 if (!file_data_sc_add_ci_unspecified(fd, dest)) ret = FALSE;
1341 void file_data_sc_free_ci_list(GList *fd_list)
1348 FileData *fd = work->data;
1350 file_data_sc_free_ci(fd);
1356 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1357 * fails if fd->change does not exist or the change type does not match
1360 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1362 g_free(fd->change->dest);
1363 fd->change->dest = g_strdup(dest_path);
1366 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1368 const char *extension = extension_from_path(fd->change->source);
1369 gchar *base = remove_extension_from_path(dest_path);
1371 g_free(fd->change->dest);
1372 fd->change->dest = g_strdup_printf("%s%s", base, extension);
1377 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1380 gchar *dest_path_full = NULL;
1382 if (fd->parent) fd = fd->parent;
1384 if (!dest_path) dest_path = fd->path;
1386 if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1388 gchar *dir = remove_level_from_path(fd->path);
1390 dest_path_full = g_build_filename(dir, dest_path, NULL);
1392 dest_path = dest_path_full;
1395 if (isdir(dest_path))
1397 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1398 dest_path = dest_path_full;
1401 file_data_update_ci_dest(fd, dest_path);
1403 work = fd->sidecar_files;
1406 FileData *sfd = work->data;
1408 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1412 g_free(dest_path_full);
1415 gint file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1417 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1418 file_data_sc_update_ci(fd, dest_path);
1422 gint file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1424 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1425 file_data_sc_update_ci(fd, dest_path);
1429 gint file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1431 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1432 file_data_sc_update_ci(fd, dest_path);
1436 gint file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1438 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1439 file_data_sc_update_ci(fd, dest_path);
1444 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1447 gboolean ret = TRUE;
1452 FileData *fd = work->data;
1454 if (!file_data_sc_update_ci_move(fd, dest)) ret = FALSE;
1461 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1464 gboolean ret = TRUE;
1469 FileData *fd = work->data;
1471 if (!file_data_sc_update_ci_copy(fd, dest)) ret = FALSE;
1478 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1481 gboolean ret = TRUE;
1486 FileData *fd = work->data;
1488 if (!file_data_sc_update_ci_unspecified(fd, dest)) ret = FALSE;
1497 * check dest paths - dest image exists, etc.
1499 * it should detect all possible problems with the planned operation
1502 gint file_data_sc_check_ci_dest(FileData *fd)
1510 * perform the change described by FileFataChangeInfo
1511 * it is used for internal operations,
1512 * this function actually operates with files on the filesystem
1513 * it should implement safe delete
1516 static gboolean file_data_perform_move(FileData *fd)
1518 g_assert(!strcmp(fd->change->source, fd->path));
1519 return move_file(fd->change->source, fd->change->dest);
1522 static gboolean file_data_perform_copy(FileData *fd)
1524 g_assert(!strcmp(fd->change->source, fd->path));
1525 return copy_file(fd->change->source, fd->change->dest);
1528 static gboolean file_data_perform_delete(FileData *fd)
1530 return unlink_file(fd->path);
1533 static gboolean file_data_perform_ci(FileData *fd)
1535 FileDataChangeType type = fd->change->type;
1538 case FILEDATA_CHANGE_MOVE:
1539 return file_data_perform_move(fd);
1540 case FILEDATA_CHANGE_COPY:
1541 return file_data_perform_copy(fd);
1542 case FILEDATA_CHANGE_RENAME:
1543 return file_data_perform_move(fd); /* the same as move */
1544 case FILEDATA_CHANGE_DELETE:
1545 return file_data_perform_delete(fd);
1546 case FILEDATA_CHANGE_UNSPECIFIED:
1547 /* nothing to do here */
1555 gboolean file_data_sc_perform_ci(FileData *fd)
1558 gboolean ret = TRUE;
1559 FileDataChangeType type = fd->change->type;
1561 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1563 work = fd->sidecar_files;
1566 FileData *sfd = work->data;
1568 if (!file_data_perform_ci(sfd)) ret = FALSE;
1572 if (!file_data_perform_ci(fd)) ret = FALSE;
1578 * updates FileData structure according to FileDataChangeInfo
1581 static void file_data_apply_ci(FileData *fd)
1583 FileDataChangeType type = fd->change->type;
1586 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1588 file_data_set_path(fd, fd->change->dest);
1590 file_data_increment_version(fd);
1591 file_data_send_notification(fd, NOTIFY_TYPE_CHANGE);
1594 gint file_data_sc_apply_ci(FileData *fd)
1597 FileDataChangeType type = fd->change->type;
1599 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1601 work = fd->sidecar_files;
1604 FileData *sfd = work->data;
1606 file_data_apply_ci(sfd);
1610 file_data_apply_ci(fd);
1617 * notify other modules about the change described by FileFataChangeInfo
1620 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
1621 FIXME do we need the ignore_list? It looks like a workaround for ineffective
1622 implementation in view_file_list.c */
1627 typedef struct _NotifyData NotifyData;
1629 struct _NotifyData {
1630 FileDataNotifyFunc func;
1632 NotifyPriority priority;
1635 static GList *notify_func_list = NULL;
1637 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
1639 NotifyData *nda = (NotifyData *)a;
1640 NotifyData *ndb = (NotifyData *)b;
1642 if (nda->priority < ndb->priority) return -1;
1643 if (nda->priority > ndb->priority) return 1;
1647 gint file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
1651 nd = g_new(NotifyData, 1);
1654 nd->priority = priority;
1656 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
1657 DEBUG_1("Notify func registered: %p", nd);
1662 gint file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
1664 GList *work = notify_func_list;
1668 NotifyData *nd = (NotifyData *)work->data;
1670 if (nd->func == func && nd->data == data)
1672 notify_func_list = g_list_delete_link(notify_func_list, work);
1674 DEBUG_1("Notify func unregistered: %p", nd);
1684 void file_data_send_notification(FileData *fd, NotifyType type)
1686 GList *work = notify_func_list;
1690 NotifyData *nd = (NotifyData *)work->data;
1692 DEBUG_1("Notify func calling: %p %s", nd, fd->path);
1693 nd->func(fd, type, nd->data);
1698 static GHashTable *file_data_monitor_pool = NULL;
1699 static gint realtime_monitor_id = -1;
1701 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
1705 file_data_check_changed_files(fd);
1707 DEBUG_1("monitor %s", fd->path);
1710 static gboolean realtime_monitor_cb(gpointer data)
1712 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
1716 gint file_data_register_real_time_monitor(FileData *fd)
1722 if (!file_data_monitor_pool)
1723 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
1725 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
1727 DEBUG_1("Register realtime %d %s", count, fd->path);
1730 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
1732 if (realtime_monitor_id == -1)
1734 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
1740 gint file_data_unregister_real_time_monitor(FileData *fd)
1744 g_assert(file_data_monitor_pool);
1746 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
1748 DEBUG_1("Unregister realtime %d %s", count, fd->path);
1750 g_assert(count > 0);
1755 g_hash_table_remove(file_data_monitor_pool, fd);
1757 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
1759 file_data_unref(fd);
1761 if (g_hash_table_size(file_data_monitor_pool) == 0)
1763 g_source_remove(realtime_monitor_id);
1764 realtime_monitor_id = -1;