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 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1337 gboolean ret = TRUE;
1342 FileData *fd = work->data;
1344 if (!file_data_sc_add_ci_copy(fd, dest)) ret = FALSE;
1351 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1354 gboolean ret = TRUE;
1359 FileData *fd = work->data;
1361 if (!file_data_sc_add_ci_move(fd, dest)) ret = FALSE;
1368 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1371 gboolean ret = TRUE;
1376 FileData *fd = work->data;
1378 if (!file_data_sc_add_ci_rename(fd, dest)) ret = FALSE;
1385 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1388 gboolean ret = TRUE;
1393 FileData *fd = work->data;
1395 if (!file_data_sc_add_ci_unspecified(fd, dest)) ret = FALSE;
1402 void file_data_sc_free_ci_list(GList *fd_list)
1409 FileData *fd = work->data;
1411 file_data_sc_free_ci(fd);
1417 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1418 * fails if fd->change does not exist or the change type does not match
1421 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1423 FileDataChangeType type = fd->change->type;
1425 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1429 if (!file_data_planned_change_hash)
1430 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1432 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1434 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1435 g_hash_table_remove(file_data_planned_change_hash, old_path);
1436 file_data_unref(fd);
1439 if ((ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path)) != fd)
1443 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1444 g_hash_table_remove(file_data_planned_change_hash, new_path);
1445 file_data_unref(ofd);
1448 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1450 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1455 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1457 gchar *old_path = fd->change->dest;
1458 fd->change->dest = g_strdup(dest_path);
1459 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1463 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1465 const char *extension = extension_from_path(fd->change->source);
1466 gchar *base = remove_extension_from_path(dest_path);
1467 gchar *old_path = fd->change->dest;
1469 fd->change->dest = g_strdup_printf("%s%s", base, extension);
1470 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1476 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1479 gchar *dest_path_full = NULL;
1481 if (fd->parent) fd = fd->parent;
1483 if (!dest_path) dest_path = fd->path;
1485 if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1487 gchar *dir = remove_level_from_path(fd->path);
1489 dest_path_full = g_build_filename(dir, dest_path, NULL);
1491 dest_path = dest_path_full;
1494 if (isdir(dest_path))
1496 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1497 dest_path = dest_path_full;
1500 file_data_update_ci_dest(fd, dest_path);
1502 work = fd->sidecar_files;
1505 FileData *sfd = work->data;
1507 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1511 g_free(dest_path_full);
1514 gint file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1516 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1517 file_data_sc_update_ci(fd, dest_path);
1521 gint file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1523 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1524 file_data_sc_update_ci(fd, dest_path);
1528 gint file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1530 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1531 file_data_sc_update_ci(fd, dest_path);
1535 gint file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1537 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1538 file_data_sc_update_ci(fd, dest_path);
1543 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1546 gboolean ret = TRUE;
1551 FileData *fd = work->data;
1553 if (!file_data_sc_update_ci_move(fd, dest)) ret = FALSE;
1560 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1563 gboolean ret = TRUE;
1568 FileData *fd = work->data;
1570 if (!file_data_sc_update_ci_copy(fd, dest)) ret = FALSE;
1577 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1580 gboolean ret = TRUE;
1585 FileData *fd = work->data;
1587 if (!file_data_sc_update_ci_unspecified(fd, dest)) ret = FALSE;
1596 * check dest paths - dest image exists, etc.
1598 * it should detect all possible problems with the planned operation
1601 gint file_data_sc_check_ci_dest(FileData *fd)
1609 * perform the change described by FileFataChangeInfo
1610 * it is used for internal operations,
1611 * this function actually operates with files on the filesystem
1612 * it should implement safe delete
1615 static gboolean file_data_perform_move(FileData *fd)
1617 g_assert(!strcmp(fd->change->source, fd->path));
1618 return move_file(fd->change->source, fd->change->dest);
1621 static gboolean file_data_perform_copy(FileData *fd)
1623 g_assert(!strcmp(fd->change->source, fd->path));
1624 return copy_file(fd->change->source, fd->change->dest);
1627 static gboolean file_data_perform_delete(FileData *fd)
1629 if (isdir(fd->path) && !islink(fd->path))
1630 return rmdir_utf8(fd->path);
1632 return unlink_file(fd->path);
1635 static gboolean file_data_perform_ci(FileData *fd)
1637 FileDataChangeType type = fd->change->type;
1640 case FILEDATA_CHANGE_MOVE:
1641 return file_data_perform_move(fd);
1642 case FILEDATA_CHANGE_COPY:
1643 return file_data_perform_copy(fd);
1644 case FILEDATA_CHANGE_RENAME:
1645 return file_data_perform_move(fd); /* the same as move */
1646 case FILEDATA_CHANGE_DELETE:
1647 return file_data_perform_delete(fd);
1648 case FILEDATA_CHANGE_UNSPECIFIED:
1649 /* nothing to do here */
1657 gboolean file_data_sc_perform_ci(FileData *fd)
1660 gboolean ret = TRUE;
1661 FileDataChangeType type = fd->change->type;
1663 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1665 work = fd->sidecar_files;
1668 FileData *sfd = work->data;
1670 if (!file_data_perform_ci(sfd)) ret = FALSE;
1674 if (!file_data_perform_ci(fd)) ret = FALSE;
1680 * updates FileData structure according to FileDataChangeInfo
1683 static void file_data_apply_ci(FileData *fd)
1685 FileDataChangeType type = fd->change->type;
1688 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1690 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
1691 file_data_planned_change_remove(fd);
1692 file_data_set_path(fd, fd->change->dest);
1694 file_data_increment_version(fd);
1695 file_data_send_notification(fd, NOTIFY_TYPE_CHANGE);
1698 gint file_data_sc_apply_ci(FileData *fd)
1701 FileDataChangeType type = fd->change->type;
1703 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1705 work = fd->sidecar_files;
1708 FileData *sfd = work->data;
1710 file_data_apply_ci(sfd);
1714 file_data_apply_ci(fd);
1720 * notify other modules about the change described by FileFataChangeInfo
1723 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
1724 FIXME do we need the ignore_list? It looks like a workaround for ineffective
1725 implementation in view_file_list.c */
1730 typedef struct _NotifyData NotifyData;
1732 struct _NotifyData {
1733 FileDataNotifyFunc func;
1735 NotifyPriority priority;
1738 static GList *notify_func_list = NULL;
1740 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
1742 NotifyData *nda = (NotifyData *)a;
1743 NotifyData *ndb = (NotifyData *)b;
1745 if (nda->priority < ndb->priority) return -1;
1746 if (nda->priority > ndb->priority) return 1;
1750 gint file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
1754 nd = g_new(NotifyData, 1);
1757 nd->priority = priority;
1759 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
1760 DEBUG_1("Notify func registered: %p", nd);
1765 gint file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
1767 GList *work = notify_func_list;
1771 NotifyData *nd = (NotifyData *)work->data;
1773 if (nd->func == func && nd->data == data)
1775 notify_func_list = g_list_delete_link(notify_func_list, work);
1777 DEBUG_1("Notify func unregistered: %p", nd);
1787 void file_data_send_notification(FileData *fd, NotifyType type)
1789 GList *work = notify_func_list;
1793 NotifyData *nd = (NotifyData *)work->data;
1795 DEBUG_1("Notify func calling: %p %s", nd, fd->path);
1796 nd->func(fd, type, nd->data);
1801 static GHashTable *file_data_monitor_pool = NULL;
1802 static gint realtime_monitor_id = -1;
1804 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
1808 file_data_check_changed_files(fd);
1810 DEBUG_1("monitor %s", fd->path);
1813 static gboolean realtime_monitor_cb(gpointer data)
1815 if (!options->update_on_time_change) return TRUE;
1816 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
1820 gint file_data_register_real_time_monitor(FileData *fd)
1826 if (!file_data_monitor_pool)
1827 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
1829 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
1831 DEBUG_1("Register realtime %d %s", count, fd->path);
1834 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
1836 if (realtime_monitor_id == -1)
1838 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
1844 gint file_data_unregister_real_time_monitor(FileData *fd)
1848 g_assert(file_data_monitor_pool);
1850 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
1852 DEBUG_1("Unregister realtime %d %s", count, fd->path);
1854 g_assert(count > 0);
1859 g_hash_table_remove(file_data_monitor_pool, fd);
1861 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
1863 file_data_unref(fd);
1865 if (g_hash_table_size(file_data_monitor_pool) == 0)
1867 g_source_remove(realtime_monitor_id);
1868 realtime_monitor_id = -1;