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);
183 g_assert(!g_hash_table_lookup(file_data_pool, path));
185 fd->original_path = g_strdup(path);
186 g_hash_table_insert(file_data_pool, fd->original_path, fd);
188 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
190 fd->path = g_strdup(path);
192 fd->extension = fd->name + 1;
193 file_data_set_collate_keys(fd);
197 fd->path = g_strdup(path);
198 fd->name = filename_from_path(fd->path);
200 if (strcmp(fd->name, "..") == 0)
202 gchar *dir = remove_level_from_path(path);
204 fd->path = remove_level_from_path(dir);
207 fd->extension = fd->name + 2;
208 file_data_set_collate_keys(fd);
211 else if (strcmp(fd->name, ".") == 0)
214 fd->path = remove_level_from_path(path);
216 fd->extension = fd->name + 1;
217 file_data_set_collate_keys(fd);
221 fd->extension = extension_from_path(fd->path);
222 if (fd->extension == NULL)
223 fd->extension = fd->name + strlen(fd->name);
225 file_data_set_collate_keys(fd);
228 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
230 gboolean ret = FALSE;
233 if (fd->size != st->st_size ||
234 fd->date != st->st_mtime)
236 fd->size = st->st_size;
237 fd->date = st->st_mtime;
238 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
239 fd->thumb_pixbuf = NULL;
240 file_data_increment_version(fd);
241 file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
245 work = fd->sidecar_files;
248 FileData *sfd = work->data;
252 if (!stat_utf8(sfd->path, &st))
256 file_data_disconnect_sidecar_file(fd, sfd);
261 ret |= file_data_check_changed_files_recursive(sfd, &st);
267 gboolean file_data_check_changed_files(FileData *fd)
269 gboolean ret = FALSE;
272 if (fd->parent) fd = fd->parent;
274 if (!stat_utf8(fd->path, &st))
277 FileData *sfd = NULL;
279 /* parent is missing, we have to rebuild whole group */
284 work = fd->sidecar_files;
290 file_data_disconnect_sidecar_file(fd, sfd);
292 if (sfd) file_data_check_sidecars(sfd); /* this will group the sidecars back together */
293 file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
297 ret |= file_data_check_changed_files_recursive(fd, &st);
303 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean check_sidecars)
307 DEBUG_2("file_data_new: '%s' %d", path_utf8, check_sidecars);
310 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
312 if ((fd = g_hash_table_lookup(file_data_pool, path_utf8)))
317 if (!fd && file_data_planned_change_hash)
319 if ((fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8)))
321 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
323 file_data_apply_ci(fd);
332 changed = file_data_check_changed_files(fd);
334 changed = file_data_check_changed_files_recursive(fd, st);
335 if (changed && check_sidecars && sidecar_file_priority(fd->extension))
336 file_data_check_sidecars(fd);
337 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
342 fd = g_new0(FileData, 1);
346 fd->collate_key_name = NULL;
347 fd->collate_key_name_nocase = NULL;
348 fd->original_path = NULL;
350 fd->size = st->st_size;
351 fd->date = st->st_mtime;
352 fd->thumb_pixbuf = NULL;
353 fd->sidecar_files = NULL;
355 fd->magick = 0x12345678;
357 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
360 file_data_check_sidecars(fd);
365 static void file_data_check_sidecars(FileData *fd)
369 FileData *parent_fd = NULL;
372 if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
375 base_len = fd->extension - fd->path;
376 fname = g_string_new_len(fd->path, base_len);
377 work = sidecar_ext_get_list();
381 /* check for possible sidecar files;
382 the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
383 they have fd->ref set to 0 and file_data unref must chack and free them all together
384 (using fd->ref would cause loops and leaks)
388 gchar *ext = work->data;
392 if (strcmp(ext, fd->extension) == 0)
394 new_fd = fd; /* processing the original file */
399 g_string_truncate(fname, base_len);
400 g_string_append(fname, ext);
402 if (!stat_utf8(fname->str, &nst))
405 new_fd = file_data_new(fname->str, &nst, FALSE);
407 if (new_fd->disable_grouping)
409 file_data_unref(new_fd);
413 new_fd->ref--; /* do not use ref here */
417 parent_fd = new_fd; /* parent is the one with the highest prio, found first */
419 file_data_merge_sidecar_files(parent_fd, new_fd);
421 g_string_free(fname, TRUE);
425 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars)
427 gchar *path_utf8 = path_to_utf8(path);
428 FileData *ret = file_data_new(path_utf8, st, check_sidecars);
434 FileData *file_data_new_simple(const gchar *path_utf8)
438 if (!stat_utf8(path_utf8, &st))
444 return file_data_new(path_utf8, &st, TRUE);
447 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
449 sfd->parent = target;
450 if (!g_list_find(target->sidecar_files, sfd))
451 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
452 file_data_increment_version(sfd); /* increments both sfd and target */
457 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
461 file_data_add_sidecar_file(target, source);
463 work = source->sidecar_files;
466 FileData *sfd = work->data;
467 file_data_add_sidecar_file(target, sfd);
471 g_list_free(source->sidecar_files);
472 source->sidecar_files = NULL;
474 target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
479 #ifdef DEBUG_FILEDATA
480 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
482 FileData *file_data_ref(FileData *fd)
485 if (fd == NULL) return NULL;
486 #ifdef DEBUG_FILEDATA
487 if (fd->magick != 0x12345678)
488 DEBUG_0("fd magick mismatch at %s:%d", file, line);
490 g_assert(fd->magick == 0x12345678);
493 #ifdef DEBUG_FILEDATA
494 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
496 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
501 static void file_data_free(FileData *fd)
503 g_assert(fd->magick == 0x12345678);
504 g_assert(fd->ref == 0);
506 g_hash_table_remove(file_data_pool, fd->original_path);
509 g_free(fd->original_path);
510 g_free(fd->collate_key_name);
511 g_free(fd->collate_key_name_nocase);
512 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
514 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
516 file_data_change_info_free(NULL, fd);
520 #ifdef DEBUG_FILEDATA
521 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
523 void file_data_unref(FileData *fd)
526 if (fd == NULL) return;
527 #ifdef DEBUG_FILEDATA
528 if (fd->magick != 0x12345678)
529 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
531 g_assert(fd->magick == 0x12345678);
534 #ifdef DEBUG_FILEDATA
535 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
538 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
543 FileData *parent = fd->parent ? fd->parent : fd;
548 work = parent->sidecar_files;
551 FileData *sfd = work->data;
557 /* none of parent/children is referenced, we can free everything */
559 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
561 work = parent->sidecar_files;
564 FileData *sfd = work->data;
569 g_list_free(parent->sidecar_files);
570 parent->sidecar_files = NULL;
572 file_data_free(parent);
576 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
578 sfd->parent = target;
579 g_assert(g_list_find(target->sidecar_files, sfd));
581 file_data_increment_version(sfd); /* increments both sfd and target */
583 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
595 /* disables / enables grouping for particular file, sends UPDATE notification */
596 void file_data_disable_grouping(FileData *fd, gboolean disable)
598 if (!fd->disable_grouping == !disable) return;
599 fd->disable_grouping = !!disable;
605 FileData *parent = file_data_ref(fd->parent);
606 file_data_disconnect_sidecar_file(parent, fd);
607 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
608 file_data_send_notification(parent, NOTIFY_TYPE_INTERNAL);
609 file_data_unref(parent);
611 else if (fd->sidecar_files)
613 GList *sidecar_files = filelist_copy(fd->sidecar_files);
614 GList *work = sidecar_files;
617 FileData *sfd = work->data;
619 file_data_disconnect_sidecar_file(fd, sfd);
620 file_data_send_notification(sfd, NOTIFY_TYPE_INTERNAL);
622 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
623 file_data_check_sidecars((FileData *)sidecar_files->data); /* this will group the sidecars back together */
624 filelist_free(sidecar_files);
629 file_data_check_sidecars(fd);
630 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
634 /* compare name without extension */
635 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
637 size_t len1 = fd1->extension - fd1->name;
638 size_t len2 = fd2->extension - fd2->name;
640 if (len1 < len2) return -1;
641 if (len1 > len2) return 1;
643 return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
646 gboolean file_data_add_change_info(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
648 FileDataChangeInfo *fdci;
650 if (fd->change) return FALSE;
652 fdci = g_new0(FileDataChangeInfo, 1);
656 fdci->source = g_strdup(src);
658 fdci->source = g_strdup(fd->path);
661 fdci->dest = g_strdup(dest);
668 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
676 g_free(fdci->source);
689 *-----------------------------------------------------------------------------
690 * sidecar file info struct
691 *-----------------------------------------------------------------------------
696 static gint sidecar_file_priority(const gchar *path)
698 const char *extension = extension_from_path(path);
702 if (extension == NULL)
705 work = sidecar_ext_get_list();
708 gchar *ext = work->data;
711 if (strcmp(extension, ext) == 0) return i;
719 *-----------------------------------------------------------------------------
721 *-----------------------------------------------------------------------------
724 static SortType filelist_sort_method = SORT_NONE;
725 static gint filelist_sort_ascend = TRUE;
728 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
730 if (!filelist_sort_ascend)
737 switch (filelist_sort_method)
742 if (fa->size < fb->size) return -1;
743 if (fa->size > fb->size) return 1;
744 /* fall back to name */
747 if (fa->date < fb->date) return -1;
748 if (fa->date > fb->date) return 1;
749 /* fall back to name */
751 #ifdef HAVE_STRVERSCMP
753 return strverscmp(fa->name, fb->name);
760 if (options->file_sort.case_sensitive)
761 return strcmp(fa->collate_key_name, fb->collate_key_name);
763 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
766 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gint ascend)
768 filelist_sort_method = method;
769 filelist_sort_ascend = ascend;
770 return filelist_sort_compare_filedata(fa, fb);
773 static gint filelist_sort_file_cb(void *a, void *b)
775 return filelist_sort_compare_filedata(a, b);
778 GList *filelist_sort_full(GList *list, SortType method, gint ascend, GCompareFunc cb)
780 filelist_sort_method = method;
781 filelist_sort_ascend = ascend;
782 return g_list_sort(list, cb);
785 GList *filelist_insert_sort_full(GList *list, void *data, SortType method, gint ascend, GCompareFunc cb)
787 filelist_sort_method = method;
788 filelist_sort_ascend = ascend;
789 return g_list_insert_sorted(list, data, cb);
792 GList *filelist_sort(GList *list, SortType method, gint ascend)
794 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
797 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gint ascend)
799 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
803 static GList *filelist_filter_out_sidecars(GList *flist)
806 GList *flist_filtered = NULL;
810 FileData *fd = work->data;
813 if (fd->parent) /* remove fd's that are children */
816 flist_filtered = g_list_prepend(flist_filtered, fd);
820 return flist_filtered;
823 static gint filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gint follow_symlinks)
830 int (*stat_func)(const char *path, struct stat *buf);
832 g_assert(files || dirs);
834 if (files) *files = NULL;
835 if (dirs) *dirs = NULL;
837 pathl = path_from_utf8(dir_fd->path);
838 if (!pathl) return FALSE;
852 while ((dir = readdir(dp)) != NULL)
854 struct stat ent_sbuf;
855 const gchar *name = dir->d_name;
858 if (!options->file_filter.show_hidden_files && ishidden(name))
861 filepath = g_build_filename(pathl, name, NULL);
862 if (stat_func(filepath, &ent_sbuf) >= 0)
864 if (S_ISDIR(ent_sbuf.st_mode))
866 /* we ignore the .thumbnails dir for cleanliness */
868 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
869 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
870 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
871 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
873 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE));
878 if (files && filter_name_exists(name))
880 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE));
891 if (dirs) *dirs = dlist;
892 if (files) *files = filelist_filter_out_sidecars(flist);
897 gint filelist_read(FileData *dir_fd, GList **files, GList **dirs)
899 return filelist_read_real(dir_fd, files, dirs, TRUE);
902 gint filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
904 return filelist_read_real(dir_fd, files, dirs, FALSE);
907 void filelist_free(GList *list)
914 file_data_unref((FileData *)work->data);
922 GList *filelist_copy(GList *list)
924 GList *new_list = NULL;
935 new_list = g_list_prepend(new_list, file_data_ref(fd));
938 return g_list_reverse(new_list);
941 GList *filelist_from_path_list(GList *list)
943 GList *new_list = NULL;
954 new_list = g_list_prepend(new_list, file_data_new_simple(path));
957 return g_list_reverse(new_list);
960 GList *filelist_to_path_list(GList *list)
962 GList *new_list = NULL;
973 new_list = g_list_prepend(new_list, g_strdup(fd->path));
976 return g_list_reverse(new_list);
979 GList *filelist_filter(GList *list, gint is_dir_list)
983 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
988 FileData *fd = (FileData *)(work->data);
989 const gchar *name = fd->name;
991 if ((!options->file_filter.show_hidden_files && ishidden(name)) ||
992 (!is_dir_list && !filter_name_exists(name)) ||
993 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
994 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
998 list = g_list_remove_link(list, link);
1010 *-----------------------------------------------------------------------------
1011 * filelist recursive
1012 *-----------------------------------------------------------------------------
1015 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1017 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1020 GList *filelist_sort_path(GList *list)
1022 return g_list_sort(list, filelist_sort_path_cb);
1025 static void filelist_recursive_append(GList **list, GList *dirs)
1032 FileData *fd = (FileData *)(work->data);
1036 if (filelist_read(fd, &f, &d))
1038 f = filelist_filter(f, FALSE);
1039 f = filelist_sort_path(f);
1040 *list = g_list_concat(*list, f);
1042 d = filelist_filter(d, TRUE);
1043 d = filelist_sort_path(d);
1044 filelist_recursive_append(list, d);
1052 GList *filelist_recursive(FileData *dir_fd)
1057 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1058 list = filelist_filter(list, FALSE);
1059 list = filelist_sort_path(list);
1061 d = filelist_filter(d, TRUE);
1062 d = filelist_sort_path(d);
1063 filelist_recursive_append(&list, d);
1071 * marks and orientation
1075 gboolean file_data_get_mark(FileData *fd, gint n)
1077 return !!(fd->marks & (1 << n));
1080 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1082 if (!value == !(fd->marks & (1 << n))) return;
1084 fd->marks = fd->marks ^ (1 << n);
1085 file_data_increment_version(fd);
1086 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1089 gint file_data_get_user_orientation(FileData *fd)
1091 return fd->user_orientation;
1094 void file_data_set_user_orientation(FileData *fd, gint value)
1096 if (fd->user_orientation == value) return;
1098 fd->user_orientation = value;
1099 file_data_increment_version(fd);
1100 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1106 * file_data - operates on the given fd
1107 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1111 /* return list of sidecar file extensions in a string */
1112 gchar *file_data_sc_list_to_string(FileData *fd)
1115 GString *result = g_string_new("");
1117 work = fd->sidecar_files;
1120 FileData *sfd = work->data;
1122 result = g_string_append(result, "+ ");
1123 result = g_string_append(result, sfd->extension);
1125 if (work) result = g_string_append_c(result, ' ');
1128 return g_string_free(result, FALSE);
1134 * add FileDataChangeInfo (see typedefs.h) for the given operation
1135 * uses file_data_add_change_info
1137 * fails if the fd->change already exists - change operations can't run in parallel
1138 * fd->change_info works as a lock
1140 * dest can be NULL - in this case the current name is used for now, it will
1145 FileDataChangeInfo types:
1147 MOVE - patch is changed, name may be changed too
1148 RENAME - path remains unchanged, name is changed
1149 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1150 sidecar names are changed too, extensions are not changed
1152 UPDATE - file size, date or grouping has been changed
1155 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1157 FileDataChangeInfo *fdci;
1159 if (fd->change) return FALSE;
1161 fdci = g_new0(FileDataChangeInfo, 1);
1166 fdci->source = g_strdup(src);
1168 fdci->source = g_strdup(fd->path);
1171 fdci->dest = g_strdup(dest);
1178 static void file_data_planned_change_remove(FileData *fd)
1180 if (file_data_planned_change_hash &&
1181 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1183 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1185 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1186 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1187 file_data_unref(fd);
1188 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1190 g_hash_table_destroy(file_data_planned_change_hash);
1191 file_data_planned_change_hash = NULL;
1192 DEBUG_1("planned change: empty");
1199 void file_data_free_ci(FileData *fd)
1201 FileDataChangeInfo *fdci = fd->change;
1206 file_data_planned_change_remove(fd);
1208 g_free(fdci->source);
1217 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1221 if (fd->parent) fd = fd->parent;
1223 if (fd->change) return FALSE;
1225 work = fd->sidecar_files;
1228 FileData *sfd = work->data;
1230 if (sfd->change) return FALSE;
1234 file_data_add_ci(fd, type, NULL, NULL);
1236 work = fd->sidecar_files;
1239 FileData *sfd = work->data;
1241 file_data_add_ci(sfd, type, NULL, NULL);
1248 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1252 if (fd->parent) fd = fd->parent;
1254 if (!fd->change || fd->change->type != type) return FALSE;
1256 work = fd->sidecar_files;
1259 FileData *sfd = work->data;
1261 if (!sfd->change || sfd->change->type != type) return FALSE;
1269 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1271 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1272 file_data_sc_update_ci_copy(fd, dest_path);
1276 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1278 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1279 file_data_sc_update_ci_move(fd, dest_path);
1283 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1285 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1286 file_data_sc_update_ci_rename(fd, dest_path);
1290 gboolean file_data_sc_add_ci_delete(FileData *fd)
1292 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1295 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1297 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1298 file_data_sc_update_ci_unspecified(fd, dest_path);
1302 void file_data_sc_free_ci(FileData *fd)
1306 if (fd->parent) fd = fd->parent;
1308 file_data_free_ci(fd);
1310 work = fd->sidecar_files;
1313 FileData *sfd = work->data;
1315 file_data_free_ci(sfd);
1320 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1323 gboolean ret = TRUE;
1328 FileData *fd = work->data;
1330 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1337 static void file_data_sc_revert_ci_list(GList *fd_list)
1344 FileData *fd = work->data;
1346 file_data_sc_free_ci(fd);
1352 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1359 FileData *fd = work->data;
1361 if (!file_data_sc_add_ci_copy(fd, dest))
1363 file_data_sc_revert_ci_list(work->prev);
1372 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1379 FileData *fd = work->data;
1381 if (!file_data_sc_add_ci_move(fd, dest))
1383 file_data_sc_revert_ci_list(work->prev);
1392 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1399 FileData *fd = work->data;
1401 if (!file_data_sc_add_ci_rename(fd, dest))
1403 file_data_sc_revert_ci_list(work->prev);
1412 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1419 FileData *fd = work->data;
1421 if (!file_data_sc_add_ci_unspecified(fd, dest))
1423 file_data_sc_revert_ci_list(work->prev);
1432 void file_data_sc_free_ci_list(GList *fd_list)
1439 FileData *fd = work->data;
1441 file_data_sc_free_ci(fd);
1447 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1448 * fails if fd->change does not exist or the change type does not match
1451 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1453 FileDataChangeType type = fd->change->type;
1455 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1459 if (!file_data_planned_change_hash)
1460 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1462 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1464 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1465 g_hash_table_remove(file_data_planned_change_hash, old_path);
1466 file_data_unref(fd);
1469 if ((ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path)) != fd)
1473 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1474 g_hash_table_remove(file_data_planned_change_hash, new_path);
1475 file_data_unref(ofd);
1478 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1480 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1485 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1487 gchar *old_path = fd->change->dest;
1488 fd->change->dest = g_strdup(dest_path);
1489 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1493 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1495 const char *extension = extension_from_path(fd->change->source);
1496 gchar *base = remove_extension_from_path(dest_path);
1497 gchar *old_path = fd->change->dest;
1499 fd->change->dest = g_strdup_printf("%s%s", base, extension);
1500 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1506 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1509 gchar *dest_path_full = NULL;
1511 if (fd->parent) fd = fd->parent;
1513 if (!dest_path) dest_path = fd->path;
1515 if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1517 gchar *dir = remove_level_from_path(fd->path);
1519 dest_path_full = g_build_filename(dir, dest_path, NULL);
1521 dest_path = dest_path_full;
1524 if (isdir(dest_path))
1526 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1527 dest_path = dest_path_full;
1530 file_data_update_ci_dest(fd, dest_path);
1532 work = fd->sidecar_files;
1535 FileData *sfd = work->data;
1537 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1541 g_free(dest_path_full);
1544 gint file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1546 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1547 file_data_sc_update_ci(fd, dest_path);
1551 gint file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1553 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1554 file_data_sc_update_ci(fd, dest_path);
1558 gint file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1560 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1561 file_data_sc_update_ci(fd, dest_path);
1565 gint file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1567 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1568 file_data_sc_update_ci(fd, dest_path);
1573 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1576 gboolean ret = TRUE;
1581 FileData *fd = work->data;
1583 if (!file_data_sc_update_ci_move(fd, dest)) ret = FALSE;
1590 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1593 gboolean ret = TRUE;
1598 FileData *fd = work->data;
1600 if (!file_data_sc_update_ci_copy(fd, dest)) ret = FALSE;
1607 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1610 gboolean ret = TRUE;
1615 FileData *fd = work->data;
1617 if (!file_data_sc_update_ci_unspecified(fd, dest)) ret = FALSE;
1626 * check dest paths - dest image exists, etc.
1627 * it should detect all possible problems with the planned operation
1628 * FIXME: add more tests
1631 gint file_data_check_ci_dest(FileData *fd)
1633 gint ret = CHANGE_OK;
1635 g_assert(fd->change);
1637 if (fd->change->dest &&
1638 strcmp(fd->change->dest, fd->path) != 0 &&
1639 isname(fd->change->dest))
1641 ret |= CHANGE_DEST_EXISTS;
1642 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1645 if (!access_file(fd->path, R_OK))
1647 ret |= CHANGE_NO_PERM;
1648 DEBUG_1("Change checked: no read permission: %s", fd->path);
1651 fd->change->error = ret;
1652 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
1658 gint file_data_sc_check_ci_dest(FileData *fd)
1663 ret = file_data_check_ci_dest(fd);
1665 work = fd->sidecar_files;
1668 FileData *sfd = work->data;
1670 ret |= file_data_check_ci_dest(sfd);
1681 * perform the change described by FileFataChangeInfo
1682 * it is used for internal operations,
1683 * this function actually operates with files on the filesystem
1684 * it should implement safe delete
1687 static gboolean file_data_perform_move(FileData *fd)
1689 g_assert(!strcmp(fd->change->source, fd->path));
1690 return move_file(fd->change->source, fd->change->dest);
1693 static gboolean file_data_perform_copy(FileData *fd)
1695 g_assert(!strcmp(fd->change->source, fd->path));
1696 return copy_file(fd->change->source, fd->change->dest);
1699 static gboolean file_data_perform_delete(FileData *fd)
1701 if (isdir(fd->path) && !islink(fd->path))
1702 return rmdir_utf8(fd->path);
1704 return unlink_file(fd->path);
1707 static gboolean file_data_perform_ci(FileData *fd)
1709 FileDataChangeType type = fd->change->type;
1712 case FILEDATA_CHANGE_MOVE:
1713 return file_data_perform_move(fd);
1714 case FILEDATA_CHANGE_COPY:
1715 return file_data_perform_copy(fd);
1716 case FILEDATA_CHANGE_RENAME:
1717 return file_data_perform_move(fd); /* the same as move */
1718 case FILEDATA_CHANGE_DELETE:
1719 return file_data_perform_delete(fd);
1720 case FILEDATA_CHANGE_UNSPECIFIED:
1721 /* nothing to do here */
1729 gboolean file_data_sc_perform_ci(FileData *fd)
1732 gboolean ret = TRUE;
1733 FileDataChangeType type = fd->change->type;
1735 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1737 work = fd->sidecar_files;
1740 FileData *sfd = work->data;
1742 if (!file_data_perform_ci(sfd)) ret = FALSE;
1746 if (!file_data_perform_ci(fd)) ret = FALSE;
1752 * updates FileData structure according to FileDataChangeInfo
1755 static void file_data_apply_ci(FileData *fd)
1757 FileDataChangeType type = fd->change->type;
1760 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1762 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
1763 file_data_planned_change_remove(fd);
1765 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
1767 /* this change overwrites another file which is already known to other modules
1768 renaming fd would create duplicate FileData structure
1769 the best thing we can do is nothing
1770 FIXME: maybe we could copy stuff like marks
1772 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
1776 file_data_set_path(fd, fd->change->dest);
1779 file_data_increment_version(fd);
1780 file_data_send_notification(fd, NOTIFY_TYPE_CHANGE);
1783 gint file_data_sc_apply_ci(FileData *fd)
1786 FileDataChangeType type = fd->change->type;
1788 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1790 work = fd->sidecar_files;
1793 FileData *sfd = work->data;
1795 file_data_apply_ci(sfd);
1799 file_data_apply_ci(fd);
1805 * notify other modules about the change described by FileFataChangeInfo
1808 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
1809 FIXME do we need the ignore_list? It looks like a workaround for ineffective
1810 implementation in view_file_list.c */
1815 typedef struct _NotifyData NotifyData;
1817 struct _NotifyData {
1818 FileDataNotifyFunc func;
1820 NotifyPriority priority;
1823 static GList *notify_func_list = NULL;
1825 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
1827 NotifyData *nda = (NotifyData *)a;
1828 NotifyData *ndb = (NotifyData *)b;
1830 if (nda->priority < ndb->priority) return -1;
1831 if (nda->priority > ndb->priority) return 1;
1835 gint file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
1839 nd = g_new(NotifyData, 1);
1842 nd->priority = priority;
1844 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
1845 DEBUG_1("Notify func registered: %p", nd);
1850 gint file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
1852 GList *work = notify_func_list;
1856 NotifyData *nd = (NotifyData *)work->data;
1858 if (nd->func == func && nd->data == data)
1860 notify_func_list = g_list_delete_link(notify_func_list, work);
1862 DEBUG_1("Notify func unregistered: %p", nd);
1872 void file_data_send_notification(FileData *fd, NotifyType type)
1874 GList *work = notify_func_list;
1878 NotifyData *nd = (NotifyData *)work->data;
1880 DEBUG_1("Notify func calling: %p %s", nd, fd->path);
1881 nd->func(fd, type, nd->data);
1886 static GHashTable *file_data_monitor_pool = NULL;
1887 static gint realtime_monitor_id = -1;
1889 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
1893 file_data_check_changed_files(fd);
1895 DEBUG_1("monitor %s", fd->path);
1898 static gboolean realtime_monitor_cb(gpointer data)
1900 if (!options->update_on_time_change) return TRUE;
1901 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
1905 gint file_data_register_real_time_monitor(FileData *fd)
1911 if (!file_data_monitor_pool)
1912 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
1914 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
1916 DEBUG_1("Register realtime %d %s", count, fd->path);
1919 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
1921 if (realtime_monitor_id == -1)
1923 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
1929 gint file_data_unregister_real_time_monitor(FileData *fd)
1933 g_assert(file_data_monitor_pool);
1935 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
1937 DEBUG_1("Unregister realtime %d %s", count, fd->path);
1939 g_assert(count > 0);
1944 g_hash_table_remove(file_data_monitor_pool, fd);
1946 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
1948 file_data_unref(fd);
1950 if (g_hash_table_size(file_data_monitor_pool) == 0)
1952 g_source_remove(realtime_monitor_id);
1953 realtime_monitor_id = -1;