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;
26 static GHashTable *file_data_planned_change_hash = NULL;
28 static gint sidecar_file_priority(const gchar *path);
29 static void file_data_apply_ci(FileData *fd);
33 *-----------------------------------------------------------------------------
34 * text conversion utils
35 *-----------------------------------------------------------------------------
38 gchar *text_from_size(gint64 size)
44 /* what I would like to use is printf("%'d", size)
45 * BUT: not supported on every libc :(
49 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
50 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
54 a = g_strdup_printf("%d", (guint)size);
60 b = g_new(gchar, l + n + 1);
85 gchar *text_from_size_abrev(gint64 size)
87 if (size < (gint64)1024)
89 return g_strdup_printf(_("%d bytes"), (gint)size);
91 if (size < (gint64)1048576)
93 return g_strdup_printf(_("%.1f K"), (double)size / 1024.0);
95 if (size < (gint64)1073741824)
97 return g_strdup_printf(_("%.1f MB"), (double)size / 1048576.0);
100 /* to avoid overflowing the double, do division in two steps */
102 return g_strdup_printf(_("%.1f GB"), (double)size / 1024.0);
105 /* note: returned string is valid until next call to text_from_time() */
106 const gchar *text_from_time(time_t t)
108 static gchar *ret = NULL;
112 GError *error = NULL;
114 btime = localtime(&t);
116 /* the %x warning about 2 digit years is not an error */
117 buflen = strftime(buf, sizeof(buf), "%x %H:%M", btime);
118 if (buflen < 1) return "";
121 ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
124 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
133 *-----------------------------------------------------------------------------
135 *-----------------------------------------------------------------------------
138 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source);
139 static void file_data_check_sidecars(FileData *fd);
140 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
143 void file_data_increment_version(FileData *fd)
146 if (fd->parent) fd->parent->version++;
149 static void file_data_set_collate_keys(FileData *fd)
151 gchar *caseless_name;
153 g_assert(g_utf8_validate(fd->name, -1, NULL));
155 caseless_name = g_utf8_casefold(fd->name, -1);
157 g_free(fd->collate_key_name);
158 g_free(fd->collate_key_name_nocase);
160 #if GLIB_CHECK_VERSION(2, 8, 0)
161 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
162 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
164 fd->collate_key_name = g_utf8_collate_key(fd->name, -1);
165 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
167 g_free(caseless_name);
170 static void file_data_set_path(FileData *fd, const gchar *path)
172 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
173 g_assert(file_data_pool);
177 if (fd->original_path)
179 g_hash_table_remove(file_data_pool, fd->original_path);
180 g_free(fd->original_path);
182 fd->original_path = g_strdup(path);
183 g_hash_table_insert(file_data_pool, fd->original_path, fd);
185 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
187 fd->path = g_strdup(path);
189 fd->extension = fd->name + 1;
190 file_data_set_collate_keys(fd);
194 fd->path = g_strdup(path);
195 fd->name = filename_from_path(fd->path);
197 if (strcmp(fd->name, "..") == 0)
199 gchar *dir = remove_level_from_path(path);
201 fd->path = remove_level_from_path(dir);
204 fd->extension = fd->name + 2;
205 file_data_set_collate_keys(fd);
208 else if (strcmp(fd->name, ".") == 0)
211 fd->path = remove_level_from_path(path);
213 fd->extension = fd->name + 1;
214 file_data_set_collate_keys(fd);
218 fd->extension = extension_from_path(fd->path);
219 if (fd->extension == NULL)
220 fd->extension = fd->name + strlen(fd->name);
222 file_data_set_collate_keys(fd);
225 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
227 gboolean ret = FALSE;
230 if (fd->size != st->st_size ||
231 fd->date != st->st_mtime)
233 fd->size = st->st_size;
234 fd->date = st->st_mtime;
235 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
236 fd->thumb_pixbuf = NULL;
237 file_data_increment_version(fd);
238 file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
242 work = fd->sidecar_files;
245 FileData *sfd = work->data;
249 if (!stat_utf8(sfd->path, &st))
253 file_data_disconnect_sidecar_file(fd, sfd);
258 ret |= file_data_check_changed_files_recursive(sfd, &st);
264 gboolean file_data_check_changed_files(FileData *fd)
266 gboolean ret = FALSE;
269 if (fd->parent) fd = fd->parent;
271 if (!stat_utf8(fd->path, &st))
274 FileData *sfd = NULL;
276 /* parent is missing, we have to rebuild whole group */
281 work = fd->sidecar_files;
287 file_data_disconnect_sidecar_file(fd, sfd);
289 if (sfd) file_data_check_sidecars(sfd); /* this will group the sidecars back together */
290 file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
294 ret |= file_data_check_changed_files_recursive(fd, &st);
300 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean check_sidecars)
304 DEBUG_2("file_data_new: '%s' %d", path_utf8, check_sidecars);
307 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
309 if ((fd = g_hash_table_lookup(file_data_pool, path_utf8)))
314 if (!fd && file_data_planned_change_hash)
316 if ((fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8)))
318 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
320 file_data_apply_ci(fd);
329 changed = file_data_check_changed_files(fd);
331 changed = file_data_check_changed_files_recursive(fd, st);
332 if (changed && check_sidecars && sidecar_file_priority(fd->extension))
333 file_data_check_sidecars(fd);
334 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
339 fd = g_new0(FileData, 1);
343 fd->collate_key_name = NULL;
344 fd->collate_key_name_nocase = NULL;
345 fd->original_path = NULL;
347 fd->size = st->st_size;
348 fd->date = st->st_mtime;
349 fd->thumb_pixbuf = NULL;
350 fd->sidecar_files = NULL;
352 fd->magick = 0x12345678;
354 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
357 file_data_check_sidecars(fd);
362 static void file_data_check_sidecars(FileData *fd)
366 FileData *parent_fd = NULL;
369 if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
372 base_len = fd->extension - fd->path;
373 fname = g_string_new_len(fd->path, base_len);
374 work = sidecar_ext_get_list();
378 /* check for possible sidecar files;
379 the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
380 they have fd->ref set to 0 and file_data unref must chack and free them all together
381 (using fd->ref would cause loops and leaks)
385 gchar *ext = work->data;
389 if (strcmp(ext, fd->extension) == 0)
391 new_fd = fd; /* processing the original file */
396 g_string_truncate(fname, base_len);
397 g_string_append(fname, ext);
399 if (!stat_utf8(fname->str, &nst))
402 new_fd = file_data_new(fname->str, &nst, FALSE);
404 if (new_fd->disable_grouping)
406 file_data_unref(new_fd);
410 new_fd->ref--; /* do not use ref here */
414 parent_fd = new_fd; /* parent is the one with the highest prio, found first */
416 file_data_merge_sidecar_files(parent_fd, new_fd);
418 g_string_free(fname, TRUE);
422 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars)
424 gchar *path_utf8 = path_to_utf8(path);
425 FileData *ret = file_data_new(path_utf8, st, check_sidecars);
431 FileData *file_data_new_simple(const gchar *path_utf8)
435 if (!stat_utf8(path_utf8, &st))
441 return file_data_new(path_utf8, &st, TRUE);
444 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
446 sfd->parent = target;
447 if (!g_list_find(target->sidecar_files, sfd))
448 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
449 file_data_increment_version(sfd); /* increments both sfd and target */
454 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
458 file_data_add_sidecar_file(target, source);
460 work = source->sidecar_files;
463 FileData *sfd = work->data;
464 file_data_add_sidecar_file(target, sfd);
468 g_list_free(source->sidecar_files);
469 source->sidecar_files = NULL;
471 target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
476 #ifdef DEBUG_FILEDATA
477 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
479 FileData *file_data_ref(FileData *fd)
482 if (fd == NULL) return NULL;
483 #ifdef DEBUG_FILEDATA
484 if (fd->magick != 0x12345678)
485 DEBUG_0("fd magick mismatch at %s:%d", file, line);
487 g_assert(fd->magick == 0x12345678);
490 #ifdef DEBUG_FILEDATA
491 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
493 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
498 static void file_data_free(FileData *fd)
500 g_assert(fd->magick == 0x12345678);
501 g_assert(fd->ref == 0);
503 g_hash_table_remove(file_data_pool, fd->original_path);
506 g_free(fd->original_path);
507 g_free(fd->collate_key_name);
508 g_free(fd->collate_key_name_nocase);
509 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
511 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
513 file_data_change_info_free(NULL, fd);
517 #ifdef DEBUG_FILEDATA
518 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
520 void file_data_unref(FileData *fd)
523 if (fd == NULL) return;
524 #ifdef DEBUG_FILEDATA
525 if (fd->magick != 0x12345678)
526 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
528 g_assert(fd->magick == 0x12345678);
531 #ifdef DEBUG_FILEDATA
532 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
535 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
540 FileData *parent = fd->parent ? fd->parent : fd;
545 work = parent->sidecar_files;
548 FileData *sfd = work->data;
554 /* none of parent/children is referenced, we can free everything */
556 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
558 work = parent->sidecar_files;
561 FileData *sfd = work->data;
566 g_list_free(parent->sidecar_files);
567 parent->sidecar_files = NULL;
569 file_data_free(parent);
573 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
575 sfd->parent = target;
576 g_assert(g_list_find(target->sidecar_files, sfd));
578 file_data_increment_version(sfd); /* increments both sfd and target */
580 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
592 /* disables / enables grouping for particular file, sends UPDATE notification */
593 void file_data_disable_grouping(FileData *fd, gboolean disable)
595 if (!fd->disable_grouping == !disable) return;
596 fd->disable_grouping = !!disable;
602 FileData *parent = file_data_ref(fd->parent);
603 file_data_disconnect_sidecar_file(parent, fd);
604 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
605 file_data_send_notification(parent, NOTIFY_TYPE_INTERNAL);
606 file_data_unref(parent);
608 else if (fd->sidecar_files)
610 GList *sidecar_files = filelist_copy(fd->sidecar_files);
611 GList *work = sidecar_files;
614 FileData *sfd = work->data;
616 file_data_disconnect_sidecar_file(fd, sfd);
617 file_data_send_notification(sfd, NOTIFY_TYPE_INTERNAL);
619 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
620 file_data_check_sidecars((FileData *)sidecar_files->data); /* this will group the sidecars back together */
621 filelist_free(sidecar_files);
626 file_data_check_sidecars(fd);
627 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
631 /* compare name without extension */
632 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
634 size_t len1 = fd1->extension - fd1->name;
635 size_t len2 = fd2->extension - fd2->name;
637 if (len1 < len2) return -1;
638 if (len1 > len2) return 1;
640 return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
643 gboolean file_data_add_change_info(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
645 FileDataChangeInfo *fdci;
647 if (fd->change) return FALSE;
649 fdci = g_new0(FileDataChangeInfo, 1);
653 fdci->source = g_strdup(src);
655 fdci->source = g_strdup(fd->path);
658 fdci->dest = g_strdup(dest);
665 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
673 g_free(fdci->source);
686 *-----------------------------------------------------------------------------
687 * sidecar file info struct
688 *-----------------------------------------------------------------------------
693 static gint sidecar_file_priority(const gchar *path)
695 const char *extension = extension_from_path(path);
699 if (extension == NULL)
702 work = sidecar_ext_get_list();
705 gchar *ext = work->data;
708 if (strcmp(extension, ext) == 0) return i;
716 *-----------------------------------------------------------------------------
718 *-----------------------------------------------------------------------------
721 static SortType filelist_sort_method = SORT_NONE;
722 static gint filelist_sort_ascend = TRUE;
725 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
727 if (!filelist_sort_ascend)
734 switch (filelist_sort_method)
739 if (fa->size < fb->size) return -1;
740 if (fa->size > fb->size) return 1;
741 /* fall back to name */
744 if (fa->date < fb->date) return -1;
745 if (fa->date > fb->date) return 1;
746 /* fall back to name */
748 #ifdef HAVE_STRVERSCMP
750 return strverscmp(fa->name, fb->name);
757 if (options->file_sort.case_sensitive)
758 return strcmp(fa->collate_key_name, fb->collate_key_name);
760 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
763 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gint ascend)
765 filelist_sort_method = method;
766 filelist_sort_ascend = ascend;
767 return filelist_sort_compare_filedata(fa, fb);
770 static gint filelist_sort_file_cb(void *a, void *b)
772 return filelist_sort_compare_filedata(a, b);
775 GList *filelist_sort_full(GList *list, SortType method, gint ascend, GCompareFunc cb)
777 filelist_sort_method = method;
778 filelist_sort_ascend = ascend;
779 return g_list_sort(list, cb);
782 GList *filelist_insert_sort_full(GList *list, void *data, SortType method, gint ascend, GCompareFunc cb)
784 filelist_sort_method = method;
785 filelist_sort_ascend = ascend;
786 return g_list_insert_sorted(list, data, cb);
789 GList *filelist_sort(GList *list, SortType method, gint ascend)
791 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
794 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gint ascend)
796 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
800 static GList *filelist_filter_out_sidecars(GList *flist)
803 GList *flist_filtered = NULL;
807 FileData *fd = work->data;
810 if (fd->parent) /* remove fd's that are children */
813 flist_filtered = g_list_prepend(flist_filtered, fd);
817 return flist_filtered;
820 static gint filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gint follow_symlinks)
827 int (*stat_func)(const char *path, struct stat *buf);
829 g_assert(files || dirs);
831 if (files) *files = NULL;
832 if (dirs) *dirs = NULL;
834 pathl = path_from_utf8(dir_fd->path);
835 if (!pathl) return FALSE;
849 while ((dir = readdir(dp)) != NULL)
851 struct stat ent_sbuf;
852 const gchar *name = dir->d_name;
855 if (!options->file_filter.show_hidden_files && ishidden(name))
858 filepath = g_build_filename(pathl, name, NULL);
859 if (stat_func(filepath, &ent_sbuf) >= 0)
861 if (S_ISDIR(ent_sbuf.st_mode))
863 /* we ignore the .thumbnails dir for cleanliness */
865 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
866 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
867 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
868 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
870 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE));
875 if (files && filter_name_exists(name))
877 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE));
888 if (dirs) *dirs = dlist;
889 if (files) *files = filelist_filter_out_sidecars(flist);
894 gint filelist_read(FileData *dir_fd, GList **files, GList **dirs)
896 return filelist_read_real(dir_fd, files, dirs, TRUE);
899 gint filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
901 return filelist_read_real(dir_fd, files, dirs, FALSE);
904 void filelist_free(GList *list)
911 file_data_unref((FileData *)work->data);
919 GList *filelist_copy(GList *list)
921 GList *new_list = NULL;
932 new_list = g_list_prepend(new_list, file_data_ref(fd));
935 return g_list_reverse(new_list);
938 GList *filelist_from_path_list(GList *list)
940 GList *new_list = NULL;
951 new_list = g_list_prepend(new_list, file_data_new_simple(path));
954 return g_list_reverse(new_list);
957 GList *filelist_to_path_list(GList *list)
959 GList *new_list = NULL;
970 new_list = g_list_prepend(new_list, g_strdup(fd->path));
973 return g_list_reverse(new_list);
976 GList *filelist_filter(GList *list, gint is_dir_list)
980 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
985 FileData *fd = (FileData *)(work->data);
986 const gchar *name = fd->name;
988 if ((!options->file_filter.show_hidden_files && ishidden(name)) ||
989 (!is_dir_list && !filter_name_exists(name)) ||
990 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
991 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
995 list = g_list_remove_link(list, link);
1007 *-----------------------------------------------------------------------------
1008 * filelist recursive
1009 *-----------------------------------------------------------------------------
1012 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1014 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1017 GList *filelist_sort_path(GList *list)
1019 return g_list_sort(list, filelist_sort_path_cb);
1022 static void filelist_recursive_append(GList **list, GList *dirs)
1029 FileData *fd = (FileData *)(work->data);
1033 if (filelist_read(fd, &f, &d))
1035 f = filelist_filter(f, FALSE);
1036 f = filelist_sort_path(f);
1037 *list = g_list_concat(*list, f);
1039 d = filelist_filter(d, TRUE);
1040 d = filelist_sort_path(d);
1041 filelist_recursive_append(list, d);
1049 GList *filelist_recursive(FileData *dir_fd)
1054 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1055 list = filelist_filter(list, FALSE);
1056 list = filelist_sort_path(list);
1058 d = filelist_filter(d, TRUE);
1059 d = filelist_sort_path(d);
1060 filelist_recursive_append(&list, d);
1068 * marks and orientation
1072 gboolean file_data_get_mark(FileData *fd, gint n)
1074 return !!(fd->marks & (1 << n));
1077 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1079 if (!value == !(fd->marks & (1 << n))) return;
1081 fd->marks = fd->marks ^ (1 << n);
1082 file_data_increment_version(fd);
1083 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1086 gint file_data_get_user_orientation(FileData *fd)
1088 return fd->user_orientation;
1091 void file_data_set_user_orientation(FileData *fd, gint value)
1093 if (fd->user_orientation == value) return;
1095 fd->user_orientation = value;
1096 file_data_increment_version(fd);
1097 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1103 * file_data - operates on the given fd
1104 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1108 /* return list of sidecar file extensions in a string */
1109 gchar *file_data_sc_list_to_string(FileData *fd)
1112 GString *result = g_string_new("");
1114 work = fd->sidecar_files;
1117 FileData *sfd = work->data;
1119 result = g_string_append(result, "+ ");
1120 result = g_string_append(result, sfd->extension);
1122 if (work) result = g_string_append_c(result, ' ');
1125 return g_string_free(result, FALSE);
1131 * add FileDataChangeInfo (see typedefs.h) for the given operation
1132 * uses file_data_add_change_info
1134 * fails if the fd->change already exists - change operations can't run in parallel
1135 * fd->change_info works as a lock
1137 * dest can be NULL - in this case the current name is used for now, it will
1142 FileDataChangeInfo types:
1144 MOVE - patch is changed, name may be changed too
1145 RENAME - path remains unchanged, name is changed
1146 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1147 sidecar names are changed too, extensions are not changed
1149 UPDATE - file size, date or grouping has been changed
1152 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1154 FileDataChangeInfo *fdci;
1156 if (fd->change) return FALSE;
1158 fdci = g_new0(FileDataChangeInfo, 1);
1163 fdci->source = g_strdup(src);
1165 fdci->source = g_strdup(fd->path);
1168 fdci->dest = g_strdup(dest);
1175 static void file_data_planned_change_remove(FileData *fd)
1177 if (file_data_planned_change_hash &&
1178 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1180 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1182 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1183 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1184 file_data_unref(fd);
1185 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1187 g_hash_table_destroy(file_data_planned_change_hash);
1188 file_data_planned_change_hash = NULL;
1189 DEBUG_1("planned change: empty");
1196 void file_data_free_ci(FileData *fd)
1198 FileDataChangeInfo *fdci = fd->change;
1203 file_data_planned_change_remove(fd);
1205 g_free(fdci->source);
1214 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1218 if (fd->parent) fd = fd->parent;
1220 if (fd->change) return FALSE;
1222 work = fd->sidecar_files;
1225 FileData *sfd = work->data;
1227 if (sfd->change) return FALSE;
1231 file_data_add_ci(fd, type, NULL, NULL);
1233 work = fd->sidecar_files;
1236 FileData *sfd = work->data;
1238 file_data_add_ci(sfd, type, NULL, NULL);
1245 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1249 if (fd->parent) fd = fd->parent;
1251 if (!fd->change || fd->change->type != type) return FALSE;
1253 work = fd->sidecar_files;
1256 FileData *sfd = work->data;
1258 if (!sfd->change || sfd->change->type != type) return FALSE;
1266 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1268 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1269 file_data_sc_update_ci_copy(fd, dest_path);
1273 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1275 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1276 file_data_sc_update_ci_move(fd, dest_path);
1280 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1282 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1283 file_data_sc_update_ci_rename(fd, dest_path);
1287 gboolean file_data_sc_add_ci_delete(FileData *fd)
1289 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1292 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1294 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1295 file_data_sc_update_ci_unspecified(fd, dest_path);
1299 void file_data_sc_free_ci(FileData *fd)
1303 if (fd->parent) fd = fd->parent;
1305 file_data_free_ci(fd);
1307 work = fd->sidecar_files;
1310 FileData *sfd = work->data;
1312 file_data_free_ci(sfd);
1317 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1320 gboolean ret = TRUE;
1325 FileData *fd = work->data;
1327 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1334 static void file_data_sc_revert_ci_list(GList *fd_list)
1341 FileData *fd = work->data;
1343 file_data_sc_free_ci(fd);
1349 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1356 FileData *fd = work->data;
1358 if (!file_data_sc_add_ci_copy(fd, dest))
1360 file_data_sc_revert_ci_list(work->prev);
1369 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1376 FileData *fd = work->data;
1378 if (!file_data_sc_add_ci_move(fd, dest))
1380 file_data_sc_revert_ci_list(work->prev);
1389 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1396 FileData *fd = work->data;
1398 if (!file_data_sc_add_ci_rename(fd, dest))
1400 file_data_sc_revert_ci_list(work->prev);
1409 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1416 FileData *fd = work->data;
1418 if (!file_data_sc_add_ci_unspecified(fd, dest))
1420 file_data_sc_revert_ci_list(work->prev);
1429 void file_data_sc_free_ci_list(GList *fd_list)
1436 FileData *fd = work->data;
1438 file_data_sc_free_ci(fd);
1444 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1445 * fails if fd->change does not exist or the change type does not match
1448 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1450 FileDataChangeType type = fd->change->type;
1452 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1456 if (!file_data_planned_change_hash)
1457 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1459 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1461 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1462 g_hash_table_remove(file_data_planned_change_hash, old_path);
1463 file_data_unref(fd);
1466 if ((ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path)) != fd)
1470 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1471 g_hash_table_remove(file_data_planned_change_hash, new_path);
1472 file_data_unref(ofd);
1475 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1477 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1482 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1484 gchar *old_path = fd->change->dest;
1485 fd->change->dest = g_strdup(dest_path);
1486 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1490 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1492 const char *extension = extension_from_path(fd->change->source);
1493 gchar *base = remove_extension_from_path(dest_path);
1494 gchar *old_path = fd->change->dest;
1496 fd->change->dest = g_strdup_printf("%s%s", base, extension);
1497 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1503 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1506 gchar *dest_path_full = NULL;
1508 if (fd->parent) fd = fd->parent;
1510 if (!dest_path) dest_path = fd->path;
1512 if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1514 gchar *dir = remove_level_from_path(fd->path);
1516 dest_path_full = g_build_filename(dir, dest_path, NULL);
1518 dest_path = dest_path_full;
1521 if (isdir(dest_path))
1523 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1524 dest_path = dest_path_full;
1527 file_data_update_ci_dest(fd, dest_path);
1529 work = fd->sidecar_files;
1532 FileData *sfd = work->data;
1534 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1538 g_free(dest_path_full);
1541 gint file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1543 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1544 file_data_sc_update_ci(fd, dest_path);
1548 gint file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1550 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1551 file_data_sc_update_ci(fd, dest_path);
1555 gint file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1557 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1558 file_data_sc_update_ci(fd, dest_path);
1562 gint file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1564 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1565 file_data_sc_update_ci(fd, dest_path);
1570 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1573 gboolean ret = TRUE;
1578 FileData *fd = work->data;
1580 if (!file_data_sc_update_ci_move(fd, dest)) ret = FALSE;
1587 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1590 gboolean ret = TRUE;
1595 FileData *fd = work->data;
1597 if (!file_data_sc_update_ci_copy(fd, dest)) ret = FALSE;
1604 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1607 gboolean ret = TRUE;
1612 FileData *fd = work->data;
1614 if (!file_data_sc_update_ci_unspecified(fd, dest)) ret = FALSE;
1623 * check dest paths - dest image exists, etc.
1625 * it should detect all possible problems with the planned operation
1628 gint file_data_sc_check_ci_dest(FileData *fd)
1636 * perform the change described by FileFataChangeInfo
1637 * it is used for internal operations,
1638 * this function actually operates with files on the filesystem
1639 * it should implement safe delete
1642 static gboolean file_data_perform_move(FileData *fd)
1644 g_assert(!strcmp(fd->change->source, fd->path));
1645 return move_file(fd->change->source, fd->change->dest);
1648 static gboolean file_data_perform_copy(FileData *fd)
1650 g_assert(!strcmp(fd->change->source, fd->path));
1651 return copy_file(fd->change->source, fd->change->dest);
1654 static gboolean file_data_perform_delete(FileData *fd)
1656 if (isdir(fd->path) && !islink(fd->path))
1657 return rmdir_utf8(fd->path);
1659 return unlink_file(fd->path);
1662 static gboolean file_data_perform_ci(FileData *fd)
1664 FileDataChangeType type = fd->change->type;
1667 case FILEDATA_CHANGE_MOVE:
1668 return file_data_perform_move(fd);
1669 case FILEDATA_CHANGE_COPY:
1670 return file_data_perform_copy(fd);
1671 case FILEDATA_CHANGE_RENAME:
1672 return file_data_perform_move(fd); /* the same as move */
1673 case FILEDATA_CHANGE_DELETE:
1674 return file_data_perform_delete(fd);
1675 case FILEDATA_CHANGE_UNSPECIFIED:
1676 /* nothing to do here */
1684 gboolean file_data_sc_perform_ci(FileData *fd)
1687 gboolean ret = TRUE;
1688 FileDataChangeType type = fd->change->type;
1690 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1692 work = fd->sidecar_files;
1695 FileData *sfd = work->data;
1697 if (!file_data_perform_ci(sfd)) ret = FALSE;
1701 if (!file_data_perform_ci(fd)) ret = FALSE;
1707 * updates FileData structure according to FileDataChangeInfo
1710 static void file_data_apply_ci(FileData *fd)
1712 FileDataChangeType type = fd->change->type;
1715 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1717 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
1718 file_data_planned_change_remove(fd);
1719 file_data_set_path(fd, fd->change->dest);
1721 file_data_increment_version(fd);
1722 file_data_send_notification(fd, NOTIFY_TYPE_CHANGE);
1725 gint file_data_sc_apply_ci(FileData *fd)
1728 FileDataChangeType type = fd->change->type;
1730 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1732 work = fd->sidecar_files;
1735 FileData *sfd = work->data;
1737 file_data_apply_ci(sfd);
1741 file_data_apply_ci(fd);
1747 * notify other modules about the change described by FileFataChangeInfo
1750 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
1751 FIXME do we need the ignore_list? It looks like a workaround for ineffective
1752 implementation in view_file_list.c */
1757 typedef struct _NotifyData NotifyData;
1759 struct _NotifyData {
1760 FileDataNotifyFunc func;
1762 NotifyPriority priority;
1765 static GList *notify_func_list = NULL;
1767 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
1769 NotifyData *nda = (NotifyData *)a;
1770 NotifyData *ndb = (NotifyData *)b;
1772 if (nda->priority < ndb->priority) return -1;
1773 if (nda->priority > ndb->priority) return 1;
1777 gint file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
1781 nd = g_new(NotifyData, 1);
1784 nd->priority = priority;
1786 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
1787 DEBUG_1("Notify func registered: %p", nd);
1792 gint file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
1794 GList *work = notify_func_list;
1798 NotifyData *nd = (NotifyData *)work->data;
1800 if (nd->func == func && nd->data == data)
1802 notify_func_list = g_list_delete_link(notify_func_list, work);
1804 DEBUG_1("Notify func unregistered: %p", nd);
1814 void file_data_send_notification(FileData *fd, NotifyType type)
1816 GList *work = notify_func_list;
1820 NotifyData *nd = (NotifyData *)work->data;
1822 DEBUG_1("Notify func calling: %p %s", nd, fd->path);
1823 nd->func(fd, type, nd->data);
1828 static GHashTable *file_data_monitor_pool = NULL;
1829 static gint realtime_monitor_id = -1;
1831 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
1835 file_data_check_changed_files(fd);
1837 DEBUG_1("monitor %s", fd->path);
1840 static gboolean realtime_monitor_cb(gpointer data)
1842 if (!options->update_on_time_change) return TRUE;
1843 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
1847 gint file_data_register_real_time_monitor(FileData *fd)
1853 if (!file_data_monitor_pool)
1854 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
1856 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
1858 DEBUG_1("Register realtime %d %s", count, fd->path);
1861 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
1863 if (realtime_monitor_id == -1)
1865 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
1871 gint file_data_unregister_real_time_monitor(FileData *fd)
1875 g_assert(file_data_monitor_pool);
1877 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
1879 DEBUG_1("Unregister realtime %d %s", count, fd->path);
1881 g_assert(count > 0);
1886 g_hash_table_remove(file_data_monitor_pool, fd);
1888 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
1890 file_data_unref(fd);
1892 if (g_hash_table_size(file_data_monitor_pool) == 0)
1894 g_source_remove(realtime_monitor_id);
1895 realtime_monitor_id = -1;