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"), (gdouble)size / 1024.0);
95 if (size < (gint64)1073741824)
97 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
100 /* to avoid overflowing the gdouble, do division in two steps */
102 return g_strdup_printf(_("%.1f GB"), (gdouble)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 caseless_name = g_utf8_casefold(fd->name, -1);
155 g_free(fd->collate_key_name);
156 g_free(fd->collate_key_name_nocase);
158 #if GLIB_CHECK_VERSION(2, 8, 0)
159 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
160 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
162 fd->collate_key_name = g_utf8_collate_key(fd->name, -1);
163 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
165 g_free(caseless_name);
168 static void file_data_set_path(FileData *fd, const gchar *path)
170 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
171 g_assert(file_data_pool);
175 if (fd->original_path)
177 g_hash_table_remove(file_data_pool, fd->original_path);
178 g_free(fd->original_path);
181 g_assert(!g_hash_table_lookup(file_data_pool, path));
183 fd->original_path = g_strdup(path);
184 g_hash_table_insert(file_data_pool, fd->original_path, fd);
186 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
188 fd->path = g_strdup(path);
190 fd->extension = fd->name + 1;
191 file_data_set_collate_keys(fd);
195 fd->path = g_strdup(path);
196 fd->name = filename_from_path(fd->path);
198 if (strcmp(fd->name, "..") == 0)
200 gchar *dir = remove_level_from_path(path);
202 fd->path = remove_level_from_path(dir);
205 fd->extension = fd->name + 2;
206 file_data_set_collate_keys(fd);
209 else if (strcmp(fd->name, ".") == 0)
212 fd->path = remove_level_from_path(path);
214 fd->extension = fd->name + 1;
215 file_data_set_collate_keys(fd);
219 fd->extension = extension_from_path(fd->path);
220 if (fd->extension == NULL)
221 fd->extension = fd->name + strlen(fd->name);
223 file_data_set_collate_keys(fd);
226 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
228 gboolean ret = FALSE;
231 if (fd->size != st->st_size ||
232 fd->date != st->st_mtime)
234 fd->size = st->st_size;
235 fd->date = st->st_mtime;
236 fd->mode = st->st_mode;
237 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
238 fd->thumb_pixbuf = NULL;
239 file_data_increment_version(fd);
240 file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
244 work = fd->sidecar_files;
247 FileData *sfd = work->data;
251 if (!stat_utf8(sfd->path, &st))
255 file_data_disconnect_sidecar_file(fd, sfd);
260 ret |= file_data_check_changed_files_recursive(sfd, &st);
266 gboolean file_data_check_changed_files(FileData *fd)
268 gboolean ret = FALSE;
271 if (fd->parent) fd = fd->parent;
273 if (!stat_utf8(fd->path, &st))
276 FileData *sfd = NULL;
278 /* parent is missing, we have to rebuild whole group */
283 work = fd->sidecar_files;
289 file_data_disconnect_sidecar_file(fd, sfd);
291 if (sfd) file_data_check_sidecars(sfd); /* this will group the sidecars back together */
292 file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
296 ret |= file_data_check_changed_files_recursive(fd, &st);
302 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean check_sidecars)
306 DEBUG_2("file_data_new: '%s' %d", path_utf8, check_sidecars);
309 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
311 fd = g_hash_table_lookup(file_data_pool, path_utf8);
317 if (!fd && file_data_planned_change_hash)
319 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
322 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
324 file_data_apply_ci(fd);
333 changed = file_data_check_changed_files(fd);
335 changed = file_data_check_changed_files_recursive(fd, st);
336 if (changed && check_sidecars && sidecar_file_priority(fd->extension))
337 file_data_check_sidecars(fd);
338 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
343 fd = g_new0(FileData, 1);
347 fd->collate_key_name = NULL;
348 fd->collate_key_name_nocase = NULL;
349 fd->original_path = NULL;
351 fd->size = st->st_size;
352 fd->date = st->st_mtime;
353 fd->mode = st->st_mode;
354 fd->thumb_pixbuf = NULL;
355 fd->sidecar_files = NULL;
357 fd->magick = 0x12345678;
359 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
362 file_data_check_sidecars(fd);
367 static void file_data_check_sidecars(FileData *fd)
371 FileData *parent_fd = NULL;
374 if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
377 base_len = fd->extension - fd->path;
378 fname = g_string_new_len(fd->path, base_len);
379 work = sidecar_ext_get_list();
383 /* check for possible sidecar files;
384 the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
385 they have fd->ref set to 0 and file_data unref must chack and free them all together
386 (using fd->ref would cause loops and leaks)
390 gchar *ext = work->data;
394 if (strcmp(ext, fd->extension) == 0)
396 new_fd = fd; /* processing the original file */
401 g_string_truncate(fname, base_len);
402 g_string_append(fname, ext);
404 if (!stat_utf8(fname->str, &nst))
407 new_fd = file_data_new(fname->str, &nst, FALSE);
409 if (new_fd->disable_grouping)
411 file_data_unref(new_fd);
415 new_fd->ref--; /* do not use ref here */
419 parent_fd = new_fd; /* parent is the one with the highest prio, found first */
421 file_data_merge_sidecar_files(parent_fd, new_fd);
423 g_string_free(fname, TRUE);
427 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars)
429 gchar *path_utf8 = path_to_utf8(path);
430 FileData *ret = file_data_new(path_utf8, st, check_sidecars);
436 FileData *file_data_new_simple(const gchar *path_utf8)
440 if (!stat_utf8(path_utf8, &st))
446 return file_data_new(path_utf8, &st, TRUE);
449 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
451 sfd->parent = target;
452 if (!g_list_find(target->sidecar_files, sfd))
453 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
454 file_data_increment_version(sfd); /* increments both sfd and target */
459 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
463 file_data_add_sidecar_file(target, source);
465 work = source->sidecar_files;
468 FileData *sfd = work->data;
469 file_data_add_sidecar_file(target, sfd);
473 g_list_free(source->sidecar_files);
474 source->sidecar_files = NULL;
476 target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
481 #ifdef DEBUG_FILEDATA
482 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
484 FileData *file_data_ref(FileData *fd)
487 if (fd == NULL) return NULL;
488 #ifdef DEBUG_FILEDATA
489 if (fd->magick != 0x12345678)
490 DEBUG_0("fd magick mismatch at %s:%d", file, line);
492 g_assert(fd->magick == 0x12345678);
495 #ifdef DEBUG_FILEDATA
496 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
498 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
503 static void file_data_free(FileData *fd)
505 g_assert(fd->magick == 0x12345678);
506 g_assert(fd->ref == 0);
508 g_hash_table_remove(file_data_pool, fd->original_path);
511 g_free(fd->original_path);
512 g_free(fd->collate_key_name);
513 g_free(fd->collate_key_name_nocase);
514 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
516 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
518 file_data_change_info_free(NULL, fd);
522 #ifdef DEBUG_FILEDATA
523 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
525 void file_data_unref(FileData *fd)
528 if (fd == NULL) return;
529 #ifdef DEBUG_FILEDATA
530 if (fd->magick != 0x12345678)
531 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
533 g_assert(fd->magick == 0x12345678);
536 #ifdef DEBUG_FILEDATA
537 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
540 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
545 FileData *parent = fd->parent ? fd->parent : fd;
550 work = parent->sidecar_files;
553 FileData *sfd = work->data;
559 /* none of parent/children is referenced, we can free everything */
561 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
563 work = parent->sidecar_files;
566 FileData *sfd = work->data;
571 g_list_free(parent->sidecar_files);
572 parent->sidecar_files = NULL;
574 file_data_free(parent);
578 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
580 sfd->parent = target;
581 g_assert(g_list_find(target->sidecar_files, sfd));
583 file_data_increment_version(sfd); /* increments both sfd and target */
585 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
597 /* disables / enables grouping for particular file, sends UPDATE notification */
598 void file_data_disable_grouping(FileData *fd, gboolean disable)
600 if (!fd->disable_grouping == !disable) return;
601 fd->disable_grouping = !!disable;
607 FileData *parent = file_data_ref(fd->parent);
608 file_data_disconnect_sidecar_file(parent, fd);
609 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
610 file_data_send_notification(parent, NOTIFY_TYPE_INTERNAL);
611 file_data_unref(parent);
613 else if (fd->sidecar_files)
615 GList *sidecar_files = filelist_copy(fd->sidecar_files);
616 GList *work = sidecar_files;
619 FileData *sfd = work->data;
621 file_data_disconnect_sidecar_file(fd, sfd);
622 file_data_send_notification(sfd, NOTIFY_TYPE_INTERNAL);
624 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
625 file_data_check_sidecars((FileData *)sidecar_files->data); /* this will group the sidecars back together */
626 filelist_free(sidecar_files);
631 file_data_check_sidecars(fd);
632 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
636 /* compare name without extension */
637 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
639 size_t len1 = fd1->extension - fd1->name;
640 size_t len2 = fd2->extension - fd2->name;
642 if (len1 < len2) return -1;
643 if (len1 > len2) return 1;
645 return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
648 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
656 g_free(fdci->source);
669 *-----------------------------------------------------------------------------
670 * sidecar file info struct
671 *-----------------------------------------------------------------------------
676 static gint sidecar_file_priority(const gchar *path)
678 const gchar *extension = extension_from_path(path);
682 if (extension == NULL)
685 work = sidecar_ext_get_list();
688 gchar *ext = work->data;
691 if (strcmp(extension, ext) == 0) return i;
699 *-----------------------------------------------------------------------------
701 *-----------------------------------------------------------------------------
704 static SortType filelist_sort_method = SORT_NONE;
705 static gint filelist_sort_ascend = TRUE;
708 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
710 if (!filelist_sort_ascend)
717 switch (filelist_sort_method)
722 if (fa->size < fb->size) return -1;
723 if (fa->size > fb->size) return 1;
724 /* fall back to name */
727 if (fa->date < fb->date) return -1;
728 if (fa->date > fb->date) return 1;
729 /* fall back to name */
731 #ifdef HAVE_STRVERSCMP
733 return strverscmp(fa->name, fb->name);
740 if (options->file_sort.case_sensitive)
741 return strcmp(fa->collate_key_name, fb->collate_key_name);
743 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
746 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gint ascend)
748 filelist_sort_method = method;
749 filelist_sort_ascend = ascend;
750 return filelist_sort_compare_filedata(fa, fb);
753 static gint filelist_sort_file_cb(gpointer a, gpointer b)
755 return filelist_sort_compare_filedata(a, b);
758 GList *filelist_sort_full(GList *list, SortType method, gint ascend, GCompareFunc cb)
760 filelist_sort_method = method;
761 filelist_sort_ascend = ascend;
762 return g_list_sort(list, cb);
765 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gint ascend, GCompareFunc cb)
767 filelist_sort_method = method;
768 filelist_sort_ascend = ascend;
769 return g_list_insert_sorted(list, data, cb);
772 GList *filelist_sort(GList *list, SortType method, gint ascend)
774 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
777 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gint ascend)
779 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
783 static GList *filelist_filter_out_sidecars(GList *flist)
786 GList *flist_filtered = NULL;
790 FileData *fd = work->data;
793 if (fd->parent) /* remove fd's that are children */
796 flist_filtered = g_list_prepend(flist_filtered, fd);
800 return flist_filtered;
803 static gint filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gint follow_symlinks)
810 gint (*stat_func)(const gchar *path, struct stat *buf);
812 g_assert(files || dirs);
814 if (files) *files = NULL;
815 if (dirs) *dirs = NULL;
817 pathl = path_from_utf8(dir_fd->path);
818 if (!pathl) return FALSE;
832 while ((dir = readdir(dp)) != NULL)
834 struct stat ent_sbuf;
835 const gchar *name = dir->d_name;
838 if (!options->file_filter.show_hidden_files && ishidden(name))
841 filepath = g_build_filename(pathl, name, NULL);
842 if (stat_func(filepath, &ent_sbuf) >= 0)
844 if (S_ISDIR(ent_sbuf.st_mode))
846 /* we ignore the .thumbnails dir for cleanliness */
848 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
849 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
850 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
851 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
853 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE));
858 if (files && filter_name_exists(name))
860 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE));
871 if (dirs) *dirs = dlist;
872 if (files) *files = filelist_filter_out_sidecars(flist);
877 gint filelist_read(FileData *dir_fd, GList **files, GList **dirs)
879 return filelist_read_real(dir_fd, files, dirs, TRUE);
882 gint filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
884 return filelist_read_real(dir_fd, files, dirs, FALSE);
887 void filelist_free(GList *list)
894 file_data_unref((FileData *)work->data);
902 GList *filelist_copy(GList *list)
904 GList *new_list = NULL;
915 new_list = g_list_prepend(new_list, file_data_ref(fd));
918 return g_list_reverse(new_list);
921 GList *filelist_from_path_list(GList *list)
923 GList *new_list = NULL;
934 new_list = g_list_prepend(new_list, file_data_new_simple(path));
937 return g_list_reverse(new_list);
940 GList *filelist_to_path_list(GList *list)
942 GList *new_list = NULL;
953 new_list = g_list_prepend(new_list, g_strdup(fd->path));
956 return g_list_reverse(new_list);
959 GList *filelist_filter(GList *list, gint is_dir_list)
963 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
968 FileData *fd = (FileData *)(work->data);
969 const gchar *name = fd->name;
971 if ((!options->file_filter.show_hidden_files && ishidden(name)) ||
972 (!is_dir_list && !filter_name_exists(name)) ||
973 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
974 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
978 list = g_list_remove_link(list, link);
990 *-----------------------------------------------------------------------------
992 *-----------------------------------------------------------------------------
995 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
997 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1000 GList *filelist_sort_path(GList *list)
1002 return g_list_sort(list, filelist_sort_path_cb);
1005 static void filelist_recursive_append(GList **list, GList *dirs)
1012 FileData *fd = (FileData *)(work->data);
1016 if (filelist_read(fd, &f, &d))
1018 f = filelist_filter(f, FALSE);
1019 f = filelist_sort_path(f);
1020 *list = g_list_concat(*list, f);
1022 d = filelist_filter(d, TRUE);
1023 d = filelist_sort_path(d);
1024 filelist_recursive_append(list, d);
1032 GList *filelist_recursive(FileData *dir_fd)
1037 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1038 list = filelist_filter(list, FALSE);
1039 list = filelist_sort_path(list);
1041 d = filelist_filter(d, TRUE);
1042 d = filelist_sort_path(d);
1043 filelist_recursive_append(&list, d);
1051 * marks and orientation
1055 gboolean file_data_get_mark(FileData *fd, gint n)
1057 return !!(fd->marks & (1 << n));
1060 guint file_data_get_marks(FileData *fd)
1065 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1067 guint old = fd->marks;
1068 if (!value == !(fd->marks & (1 << n))) return;
1070 fd->marks = fd->marks ^ (1 << n);
1072 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1074 file_data_unref(fd);
1076 else if (!old && fd->marks)
1081 file_data_increment_version(fd);
1082 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1085 gboolean file_data_filter_marks(FileData *fd, guint filter)
1087 return ((fd->marks & filter) == filter);
1090 GList *file_data_filter_marks_list(GList *list, guint filter)
1097 FileData *fd = work->data;
1101 if (!file_data_filter_marks(fd, filter))
1103 list = g_list_remove_link(list, link);
1104 file_data_unref(fd);
1112 gint file_data_get_user_orientation(FileData *fd)
1114 return fd->user_orientation;
1117 void file_data_set_user_orientation(FileData *fd, gint value)
1119 if (fd->user_orientation == value) return;
1121 fd->user_orientation = value;
1122 file_data_increment_version(fd);
1123 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1129 * file_data - operates on the given fd
1130 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1134 /* return list of sidecar file extensions in a string */
1135 gchar *file_data_sc_list_to_string(FileData *fd)
1138 GString *result = g_string_new("");
1140 work = fd->sidecar_files;
1143 FileData *sfd = work->data;
1145 result = g_string_append(result, "+ ");
1146 result = g_string_append(result, sfd->extension);
1148 if (work) result = g_string_append_c(result, ' ');
1151 return g_string_free(result, FALSE);
1157 * add FileDataChangeInfo (see typedefs.h) for the given operation
1158 * uses file_data_add_change_info
1160 * fails if the fd->change already exists - change operations can't run in parallel
1161 * fd->change_info works as a lock
1163 * dest can be NULL - in this case the current name is used for now, it will
1168 FileDataChangeInfo types:
1170 MOVE - path is changed, name may be changed too
1171 RENAME - path remains unchanged, name is changed
1172 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1173 sidecar names are changed too, extensions are not changed
1175 UPDATE - file size, date or grouping has been changed
1178 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1180 FileDataChangeInfo *fdci;
1182 if (fd->change) return FALSE;
1184 fdci = g_new0(FileDataChangeInfo, 1);
1189 fdci->source = g_strdup(src);
1191 fdci->source = g_strdup(fd->path);
1194 fdci->dest = g_strdup(dest);
1201 static void file_data_planned_change_remove(FileData *fd)
1203 if (file_data_planned_change_hash &&
1204 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1206 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1208 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1209 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1210 file_data_unref(fd);
1211 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1213 g_hash_table_destroy(file_data_planned_change_hash);
1214 file_data_planned_change_hash = NULL;
1215 DEBUG_1("planned change: empty");
1222 void file_data_free_ci(FileData *fd)
1224 FileDataChangeInfo *fdci = fd->change;
1229 file_data_planned_change_remove(fd);
1231 g_free(fdci->source);
1240 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1244 if (fd->parent) fd = fd->parent;
1246 if (fd->change) return FALSE;
1248 work = fd->sidecar_files;
1251 FileData *sfd = work->data;
1253 if (sfd->change) return FALSE;
1257 file_data_add_ci(fd, type, NULL, NULL);
1259 work = fd->sidecar_files;
1262 FileData *sfd = work->data;
1264 file_data_add_ci(sfd, type, NULL, NULL);
1271 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1275 if (fd->parent) fd = fd->parent;
1277 if (!fd->change || fd->change->type != type) return FALSE;
1279 work = fd->sidecar_files;
1282 FileData *sfd = work->data;
1284 if (!sfd->change || sfd->change->type != type) return FALSE;
1292 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1294 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1295 file_data_sc_update_ci_copy(fd, dest_path);
1299 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1301 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1302 file_data_sc_update_ci_move(fd, dest_path);
1306 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1308 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1309 file_data_sc_update_ci_rename(fd, dest_path);
1313 gboolean file_data_sc_add_ci_delete(FileData *fd)
1315 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1318 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1320 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1321 file_data_sc_update_ci_unspecified(fd, dest_path);
1325 void file_data_sc_free_ci(FileData *fd)
1329 if (fd->parent) fd = fd->parent;
1331 file_data_free_ci(fd);
1333 work = fd->sidecar_files;
1336 FileData *sfd = work->data;
1338 file_data_free_ci(sfd);
1343 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1346 gboolean ret = TRUE;
1351 FileData *fd = work->data;
1353 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1360 static void file_data_sc_revert_ci_list(GList *fd_list)
1367 FileData *fd = work->data;
1369 file_data_sc_free_ci(fd);
1374 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1381 FileData *fd = work->data;
1383 if (!func(fd, dest))
1385 file_data_sc_revert_ci_list(work->prev);
1394 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1396 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1399 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1401 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1404 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1406 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1409 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1411 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1414 void file_data_sc_free_ci_list(GList *fd_list)
1421 FileData *fd = work->data;
1423 file_data_sc_free_ci(fd);
1429 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1430 * fails if fd->change does not exist or the change type does not match
1433 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1435 FileDataChangeType type = fd->change->type;
1437 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1441 if (!file_data_planned_change_hash)
1442 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1444 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1446 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1447 g_hash_table_remove(file_data_planned_change_hash, old_path);
1448 file_data_unref(fd);
1451 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1456 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1457 g_hash_table_remove(file_data_planned_change_hash, new_path);
1458 file_data_unref(ofd);
1461 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1463 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1468 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1470 gchar *old_path = fd->change->dest;
1472 fd->change->dest = g_strdup(dest_path);
1473 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1477 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1479 const gchar *extension = extension_from_path(fd->change->source);
1480 gchar *base = remove_extension_from_path(dest_path);
1481 gchar *old_path = fd->change->dest;
1483 fd->change->dest = g_strconcat(base, extension, NULL);
1484 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1490 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1493 gchar *dest_path_full = NULL;
1495 if (fd->parent) fd = fd->parent;
1499 dest_path = fd->path;
1501 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1503 gchar *dir = remove_level_from_path(fd->path);
1505 dest_path_full = g_build_filename(dir, dest_path, NULL);
1507 dest_path = dest_path_full;
1509 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1511 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1512 dest_path = dest_path_full;
1515 file_data_update_ci_dest(fd, dest_path);
1517 work = fd->sidecar_files;
1520 FileData *sfd = work->data;
1522 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1526 g_free(dest_path_full);
1529 static gint file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1531 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1532 file_data_sc_update_ci(fd, dest_path);
1536 gint file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1538 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1541 gint file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1543 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1546 gint file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1548 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1551 gint file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1553 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1556 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1558 gboolean (*func)(FileData *, const gchar *))
1561 gboolean ret = TRUE;
1566 FileData *fd = work->data;
1568 if (!func(fd, dest)) ret = FALSE;
1575 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1577 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1580 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1582 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1585 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1587 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1592 * verify source and dest paths - dest image exists, etc.
1593 * it should detect all possible problems with the planned operation
1596 gint file_data_verify_ci(FileData *fd)
1598 gint ret = CHANGE_OK;
1603 DEBUG_1("Change checked: no change info: %s", fd->path);
1607 if (!isname(fd->path))
1609 /* this probably should not happen */
1610 ret |= CHANGE_NO_SRC;
1611 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1615 dir = remove_level_from_path(fd->path);
1617 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1618 !access_file(fd->path, R_OK))
1620 ret |= CHANGE_NO_READ_PERM;
1621 DEBUG_1("Change checked: no read permission: %s", fd->path);
1623 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1624 !access_file(dir, W_OK))
1626 ret |= CHANGE_NO_WRITE_PERM_DIR;
1627 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1629 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1630 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1631 !access_file(fd->path, W_OK))
1633 ret |= CHANGE_WARN_NO_WRITE_PERM;
1634 DEBUG_1("Change checked: no write permission: %s", fd->path);
1637 if (fd->change->dest)
1642 same = (strcmp(fd->path, fd->change->dest) == 0);
1646 const gchar *dest_ext = extension_from_path(fd->change->dest);
1647 if (!dest_ext) dest_ext = "";
1649 if (strcasecmp(fd->extension, dest_ext) != 0)
1651 ret |= CHANGE_WARN_CHANGED_EXT;
1652 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
1657 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
1659 ret |= CHANGE_WARN_SAME;
1660 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
1664 dest_dir = remove_level_from_path(fd->change->dest);
1666 if (!isdir(dest_dir))
1668 ret |= CHANGE_NO_DEST_DIR;
1669 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
1671 else if (!access_file(dest_dir, W_OK))
1673 ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
1674 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
1678 if (isfile(fd->change->dest))
1680 if (!access_file(fd->change->dest, W_OK))
1682 ret |= CHANGE_NO_WRITE_PERM_DEST;
1683 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
1687 ret |= CHANGE_WARN_DEST_EXISTS;
1688 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1691 else if (isdir(fd->change->dest))
1693 ret |= CHANGE_DEST_EXISTS;
1694 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1701 fd->change->error = ret;
1702 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
1709 gint file_data_sc_verify_ci(FileData *fd)
1714 ret = file_data_verify_ci(fd);
1716 work = fd->sidecar_files;
1719 FileData *sfd = work->data;
1721 ret |= file_data_verify_ci(sfd);
1728 gchar *file_data_get_error_string(gint error)
1730 GString *result = g_string_new("");
1732 if (error & CHANGE_NO_SRC)
1734 if (result->len > 0) g_string_append(result, ", ");
1735 g_string_append(result, _("file or directory does not exist"));
1738 if (error & CHANGE_DEST_EXISTS)
1740 if (result->len > 0) g_string_append(result, ", ");
1741 g_string_append(result, _("destination already exists"));
1744 if (error & CHANGE_NO_WRITE_PERM_DEST)
1746 if (result->len > 0) g_string_append(result, ", ");
1747 g_string_append(result, _("destination can't be overwritten"));
1750 if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
1752 if (result->len > 0) g_string_append(result, ", ");
1753 g_string_append(result, _("destination directory is not writable"));
1756 if (error & CHANGE_NO_DEST_DIR)
1758 if (result->len > 0) g_string_append(result, ", ");
1759 g_string_append(result, _("destination directory does not exist"));
1762 if (error & CHANGE_NO_WRITE_PERM_DIR)
1764 if (result->len > 0) g_string_append(result, ", ");
1765 g_string_append(result, _("source directory is not writable"));
1768 if (error & CHANGE_NO_READ_PERM)
1770 if (result->len > 0) g_string_append(result, ", ");
1771 g_string_append(result, _("no read permission"));
1774 if (error & CHANGE_WARN_NO_WRITE_PERM)
1776 if (result->len > 0) g_string_append(result, ", ");
1777 g_string_append(result, _("file is readonly"));
1780 if (error & CHANGE_WARN_DEST_EXISTS)
1782 if (result->len > 0) g_string_append(result, ", ");
1783 g_string_append(result, _("destination already exists and will be overwritten"));
1786 if (error & CHANGE_WARN_SAME)
1788 if (result->len > 0) g_string_append(result, ", ");
1789 g_string_append(result, _("source and destination are the same"));
1792 if (error & CHANGE_WARN_CHANGED_EXT)
1794 if (result->len > 0) g_string_append(result, ", ");
1795 g_string_append(result, _("source and destination have different extension"));
1798 return g_string_free(result, FALSE);
1801 gint file_data_sc_verify_ci_list(GList *list, gchar **desc)
1804 gint all_errors = 0;
1805 gint common_errors = ~0;
1810 if (!list) return 0;
1812 num = g_list_length(list);
1813 errors = g_new(int, num);
1824 error = file_data_sc_verify_ci(fd);
1825 all_errors |= error;
1826 common_errors &= error;
1833 if (desc && all_errors)
1836 GString *result = g_string_new("");
1840 gchar *str = file_data_get_error_string(common_errors);
1841 g_string_append(result, str);
1842 g_string_append(result, "\n");
1856 error = errors[i] & ~common_errors;
1860 gchar *str = file_data_get_error_string(error);
1861 g_string_append_printf(result, "%s: %s\n", fd->name, str);
1866 *desc = g_string_free(result, FALSE);
1875 * perform the change described by FileFataChangeInfo
1876 * it is used for internal operations,
1877 * this function actually operates with files on the filesystem
1878 * it should implement safe delete
1881 static gboolean file_data_perform_move(FileData *fd)
1883 g_assert(!strcmp(fd->change->source, fd->path));
1884 return move_file(fd->change->source, fd->change->dest);
1887 static gboolean file_data_perform_copy(FileData *fd)
1889 g_assert(!strcmp(fd->change->source, fd->path));
1890 return copy_file(fd->change->source, fd->change->dest);
1893 static gboolean file_data_perform_delete(FileData *fd)
1895 if (isdir(fd->path) && !islink(fd->path))
1896 return rmdir_utf8(fd->path);
1898 return unlink_file(fd->path);
1901 static gboolean file_data_perform_ci(FileData *fd)
1903 FileDataChangeType type = fd->change->type;
1906 case FILEDATA_CHANGE_MOVE:
1907 return file_data_perform_move(fd);
1908 case FILEDATA_CHANGE_COPY:
1909 return file_data_perform_copy(fd);
1910 case FILEDATA_CHANGE_RENAME:
1911 return file_data_perform_move(fd); /* the same as move */
1912 case FILEDATA_CHANGE_DELETE:
1913 return file_data_perform_delete(fd);
1914 case FILEDATA_CHANGE_UNSPECIFIED:
1915 /* nothing to do here */
1923 gboolean file_data_sc_perform_ci(FileData *fd)
1926 gboolean ret = TRUE;
1927 FileDataChangeType type = fd->change->type;
1929 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1931 work = fd->sidecar_files;
1934 FileData *sfd = work->data;
1936 if (!file_data_perform_ci(sfd)) ret = FALSE;
1940 if (!file_data_perform_ci(fd)) ret = FALSE;
1946 * updates FileData structure according to FileDataChangeInfo
1949 static void file_data_apply_ci(FileData *fd)
1951 FileDataChangeType type = fd->change->type;
1954 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1956 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
1957 file_data_planned_change_remove(fd);
1959 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
1961 /* this change overwrites another file which is already known to other modules
1962 renaming fd would create duplicate FileData structure
1963 the best thing we can do is nothing
1964 FIXME: maybe we could copy stuff like marks
1966 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
1970 file_data_set_path(fd, fd->change->dest);
1973 file_data_increment_version(fd);
1974 file_data_send_notification(fd, NOTIFY_TYPE_CHANGE);
1977 gint file_data_sc_apply_ci(FileData *fd)
1980 FileDataChangeType type = fd->change->type;
1982 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1984 work = fd->sidecar_files;
1987 FileData *sfd = work->data;
1989 file_data_apply_ci(sfd);
1993 file_data_apply_ci(fd);
1999 * notify other modules about the change described by FileFataChangeInfo
2002 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2003 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2004 implementation in view_file_list.c */
2009 typedef struct _NotifyData NotifyData;
2011 struct _NotifyData {
2012 FileDataNotifyFunc func;
2014 NotifyPriority priority;
2017 static GList *notify_func_list = NULL;
2019 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2021 NotifyData *nda = (NotifyData *)a;
2022 NotifyData *ndb = (NotifyData *)b;
2024 if (nda->priority < ndb->priority) return -1;
2025 if (nda->priority > ndb->priority) return 1;
2029 gint file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2033 nd = g_new(NotifyData, 1);
2036 nd->priority = priority;
2038 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2039 DEBUG_1("Notify func registered: %p", nd);
2044 gint file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2046 GList *work = notify_func_list;
2050 NotifyData *nd = (NotifyData *)work->data;
2052 if (nd->func == func && nd->data == data)
2054 notify_func_list = g_list_delete_link(notify_func_list, work);
2056 DEBUG_1("Notify func unregistered: %p", nd);
2066 void file_data_send_notification(FileData *fd, NotifyType type)
2068 GList *work = notify_func_list;
2072 NotifyData *nd = (NotifyData *)work->data;
2074 DEBUG_1("Notify func calling: %p %s", nd, fd->path);
2075 nd->func(fd, type, nd->data);
2080 static GHashTable *file_data_monitor_pool = NULL;
2081 static gint realtime_monitor_id = -1;
2083 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2087 file_data_check_changed_files(fd);
2089 DEBUG_1("monitor %s", fd->path);
2092 static gboolean realtime_monitor_cb(gpointer data)
2094 if (!options->update_on_time_change) return TRUE;
2095 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2099 gint file_data_register_real_time_monitor(FileData *fd)
2105 if (!file_data_monitor_pool)
2106 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2108 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2110 DEBUG_1("Register realtime %d %s", count, fd->path);
2113 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2115 if (realtime_monitor_id == -1)
2117 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2123 gint file_data_unregister_real_time_monitor(FileData *fd)
2127 g_assert(file_data_monitor_pool);
2129 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2131 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2133 g_assert(count > 0);
2138 g_hash_table_remove(file_data_monitor_pool, fd);
2140 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2142 file_data_unref(fd);
2144 if (g_hash_table_size(file_data_monitor_pool) == 0)
2146 g_source_remove(realtime_monitor_id);
2147 realtime_monitor_id = -1;