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 fd = g_hash_table_lookup(file_data_pool, path_utf8);
318 if (!fd && file_data_planned_change_hash)
320 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
323 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
325 file_data_apply_ci(fd);
334 changed = file_data_check_changed_files(fd);
336 changed = file_data_check_changed_files_recursive(fd, st);
337 if (changed && check_sidecars && sidecar_file_priority(fd->extension))
338 file_data_check_sidecars(fd);
339 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
344 fd = g_new0(FileData, 1);
348 fd->collate_key_name = NULL;
349 fd->collate_key_name_nocase = NULL;
350 fd->original_path = NULL;
352 fd->size = st->st_size;
353 fd->date = st->st_mtime;
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 char *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(void *a, void *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, void *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 int (*stat_func)(const char *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 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1062 if (!value == !(fd->marks & (1 << n))) return;
1064 fd->marks = fd->marks ^ (1 << n);
1065 file_data_increment_version(fd);
1066 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1069 gint file_data_get_user_orientation(FileData *fd)
1071 return fd->user_orientation;
1074 void file_data_set_user_orientation(FileData *fd, gint value)
1076 if (fd->user_orientation == value) return;
1078 fd->user_orientation = value;
1079 file_data_increment_version(fd);
1080 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1086 * file_data - operates on the given fd
1087 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1091 /* return list of sidecar file extensions in a string */
1092 gchar *file_data_sc_list_to_string(FileData *fd)
1095 GString *result = g_string_new("");
1097 work = fd->sidecar_files;
1100 FileData *sfd = work->data;
1102 result = g_string_append(result, "+ ");
1103 result = g_string_append(result, sfd->extension);
1105 if (work) result = g_string_append_c(result, ' ');
1108 return g_string_free(result, FALSE);
1114 * add FileDataChangeInfo (see typedefs.h) for the given operation
1115 * uses file_data_add_change_info
1117 * fails if the fd->change already exists - change operations can't run in parallel
1118 * fd->change_info works as a lock
1120 * dest can be NULL - in this case the current name is used for now, it will
1125 FileDataChangeInfo types:
1127 MOVE - patch is changed, name may be changed too
1128 RENAME - path remains unchanged, name is changed
1129 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1130 sidecar names are changed too, extensions are not changed
1132 UPDATE - file size, date or grouping has been changed
1135 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1137 FileDataChangeInfo *fdci;
1139 if (fd->change) return FALSE;
1141 fdci = g_new0(FileDataChangeInfo, 1);
1146 fdci->source = g_strdup(src);
1148 fdci->source = g_strdup(fd->path);
1151 fdci->dest = g_strdup(dest);
1158 static void file_data_planned_change_remove(FileData *fd)
1160 if (file_data_planned_change_hash &&
1161 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1163 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1165 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1166 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1167 file_data_unref(fd);
1168 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1170 g_hash_table_destroy(file_data_planned_change_hash);
1171 file_data_planned_change_hash = NULL;
1172 DEBUG_1("planned change: empty");
1179 void file_data_free_ci(FileData *fd)
1181 FileDataChangeInfo *fdci = fd->change;
1186 file_data_planned_change_remove(fd);
1188 g_free(fdci->source);
1197 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1201 if (fd->parent) fd = fd->parent;
1203 if (fd->change) return FALSE;
1205 work = fd->sidecar_files;
1208 FileData *sfd = work->data;
1210 if (sfd->change) return FALSE;
1214 file_data_add_ci(fd, type, NULL, NULL);
1216 work = fd->sidecar_files;
1219 FileData *sfd = work->data;
1221 file_data_add_ci(sfd, type, NULL, NULL);
1228 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1232 if (fd->parent) fd = fd->parent;
1234 if (!fd->change || fd->change->type != type) return FALSE;
1236 work = fd->sidecar_files;
1239 FileData *sfd = work->data;
1241 if (!sfd->change || sfd->change->type != type) return FALSE;
1249 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1251 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1252 file_data_sc_update_ci_copy(fd, dest_path);
1256 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1258 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1259 file_data_sc_update_ci_move(fd, dest_path);
1263 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1265 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1266 file_data_sc_update_ci_rename(fd, dest_path);
1270 gboolean file_data_sc_add_ci_delete(FileData *fd)
1272 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1275 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1277 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1278 file_data_sc_update_ci_unspecified(fd, dest_path);
1282 void file_data_sc_free_ci(FileData *fd)
1286 if (fd->parent) fd = fd->parent;
1288 file_data_free_ci(fd);
1290 work = fd->sidecar_files;
1293 FileData *sfd = work->data;
1295 file_data_free_ci(sfd);
1300 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1303 gboolean ret = TRUE;
1308 FileData *fd = work->data;
1310 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1317 static void file_data_sc_revert_ci_list(GList *fd_list)
1324 FileData *fd = work->data;
1326 file_data_sc_free_ci(fd);
1331 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1338 FileData *fd = work->data;
1340 if (!func(fd, dest))
1342 file_data_sc_revert_ci_list(work->prev);
1351 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1353 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1356 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1358 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1361 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1363 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1366 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1368 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1371 void file_data_sc_free_ci_list(GList *fd_list)
1378 FileData *fd = work->data;
1380 file_data_sc_free_ci(fd);
1386 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1387 * fails if fd->change does not exist or the change type does not match
1390 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1392 FileDataChangeType type = fd->change->type;
1394 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1398 if (!file_data_planned_change_hash)
1399 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1401 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1403 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1404 g_hash_table_remove(file_data_planned_change_hash, old_path);
1405 file_data_unref(fd);
1408 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1413 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1414 g_hash_table_remove(file_data_planned_change_hash, new_path);
1415 file_data_unref(ofd);
1418 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1420 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1425 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1427 gchar *old_path = fd->change->dest;
1429 fd->change->dest = g_strdup(dest_path);
1430 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1434 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1436 const char *extension = extension_from_path(fd->change->source);
1437 gchar *base = remove_extension_from_path(dest_path);
1438 gchar *old_path = fd->change->dest;
1440 fd->change->dest = g_strconcat(base, extension, NULL);
1441 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1447 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1450 gchar *dest_path_full = NULL;
1452 if (fd->parent) fd = fd->parent;
1454 if (!dest_path) dest_path = fd->path;
1456 if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1458 gchar *dir = remove_level_from_path(fd->path);
1460 dest_path_full = g_build_filename(dir, dest_path, NULL);
1462 dest_path = dest_path_full;
1465 if (isdir(dest_path))
1467 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1468 dest_path = dest_path_full;
1471 file_data_update_ci_dest(fd, dest_path);
1473 work = fd->sidecar_files;
1476 FileData *sfd = work->data;
1478 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1482 g_free(dest_path_full);
1485 gint file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1487 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1488 file_data_sc_update_ci(fd, dest_path);
1492 gint file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1494 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1495 file_data_sc_update_ci(fd, dest_path);
1499 gint file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1501 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1502 file_data_sc_update_ci(fd, dest_path);
1506 gint file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1508 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1509 file_data_sc_update_ci(fd, dest_path);
1514 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1517 gboolean ret = TRUE;
1522 FileData *fd = work->data;
1524 if (!file_data_sc_update_ci_move(fd, dest)) ret = FALSE;
1531 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1534 gboolean ret = TRUE;
1539 FileData *fd = work->data;
1541 if (!file_data_sc_update_ci_copy(fd, dest)) ret = FALSE;
1548 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1551 gboolean ret = TRUE;
1556 FileData *fd = work->data;
1558 if (!file_data_sc_update_ci_unspecified(fd, dest)) ret = FALSE;
1567 * check dest paths - dest image exists, etc.
1568 * it should detect all possible problems with the planned operation
1569 * FIXME: add more tests
1572 gint file_data_check_ci_dest(FileData *fd)
1574 gint ret = CHANGE_OK;
1576 g_assert(fd->change);
1578 if (fd->change->dest &&
1579 strcmp(fd->change->dest, fd->path) != 0 &&
1580 isname(fd->change->dest))
1582 ret |= CHANGE_DEST_EXISTS;
1583 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1586 if (!access_file(fd->path, R_OK))
1588 ret |= CHANGE_NO_PERM;
1589 DEBUG_1("Change checked: no read permission: %s", fd->path);
1592 fd->change->error = ret;
1593 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
1599 gint file_data_sc_check_ci_dest(FileData *fd)
1604 ret = file_data_check_ci_dest(fd);
1606 work = fd->sidecar_files;
1609 FileData *sfd = work->data;
1611 ret |= file_data_check_ci_dest(sfd);
1622 * perform the change described by FileFataChangeInfo
1623 * it is used for internal operations,
1624 * this function actually operates with files on the filesystem
1625 * it should implement safe delete
1628 static gboolean file_data_perform_move(FileData *fd)
1630 g_assert(!strcmp(fd->change->source, fd->path));
1631 return move_file(fd->change->source, fd->change->dest);
1634 static gboolean file_data_perform_copy(FileData *fd)
1636 g_assert(!strcmp(fd->change->source, fd->path));
1637 return copy_file(fd->change->source, fd->change->dest);
1640 static gboolean file_data_perform_delete(FileData *fd)
1642 if (isdir(fd->path) && !islink(fd->path))
1643 return rmdir_utf8(fd->path);
1645 return unlink_file(fd->path);
1648 static gboolean file_data_perform_ci(FileData *fd)
1650 FileDataChangeType type = fd->change->type;
1653 case FILEDATA_CHANGE_MOVE:
1654 return file_data_perform_move(fd);
1655 case FILEDATA_CHANGE_COPY:
1656 return file_data_perform_copy(fd);
1657 case FILEDATA_CHANGE_RENAME:
1658 return file_data_perform_move(fd); /* the same as move */
1659 case FILEDATA_CHANGE_DELETE:
1660 return file_data_perform_delete(fd);
1661 case FILEDATA_CHANGE_UNSPECIFIED:
1662 /* nothing to do here */
1670 gboolean file_data_sc_perform_ci(FileData *fd)
1673 gboolean ret = TRUE;
1674 FileDataChangeType type = fd->change->type;
1676 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1678 work = fd->sidecar_files;
1681 FileData *sfd = work->data;
1683 if (!file_data_perform_ci(sfd)) ret = FALSE;
1687 if (!file_data_perform_ci(fd)) ret = FALSE;
1693 * updates FileData structure according to FileDataChangeInfo
1696 static void file_data_apply_ci(FileData *fd)
1698 FileDataChangeType type = fd->change->type;
1701 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1703 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
1704 file_data_planned_change_remove(fd);
1706 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
1708 /* this change overwrites another file which is already known to other modules
1709 renaming fd would create duplicate FileData structure
1710 the best thing we can do is nothing
1711 FIXME: maybe we could copy stuff like marks
1713 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
1717 file_data_set_path(fd, fd->change->dest);
1720 file_data_increment_version(fd);
1721 file_data_send_notification(fd, NOTIFY_TYPE_CHANGE);
1724 gint file_data_sc_apply_ci(FileData *fd)
1727 FileDataChangeType type = fd->change->type;
1729 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1731 work = fd->sidecar_files;
1734 FileData *sfd = work->data;
1736 file_data_apply_ci(sfd);
1740 file_data_apply_ci(fd);
1746 * notify other modules about the change described by FileFataChangeInfo
1749 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
1750 FIXME do we need the ignore_list? It looks like a workaround for ineffective
1751 implementation in view_file_list.c */
1756 typedef struct _NotifyData NotifyData;
1758 struct _NotifyData {
1759 FileDataNotifyFunc func;
1761 NotifyPriority priority;
1764 static GList *notify_func_list = NULL;
1766 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
1768 NotifyData *nda = (NotifyData *)a;
1769 NotifyData *ndb = (NotifyData *)b;
1771 if (nda->priority < ndb->priority) return -1;
1772 if (nda->priority > ndb->priority) return 1;
1776 gint file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
1780 nd = g_new(NotifyData, 1);
1783 nd->priority = priority;
1785 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
1786 DEBUG_1("Notify func registered: %p", nd);
1791 gint file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
1793 GList *work = notify_func_list;
1797 NotifyData *nd = (NotifyData *)work->data;
1799 if (nd->func == func && nd->data == data)
1801 notify_func_list = g_list_delete_link(notify_func_list, work);
1803 DEBUG_1("Notify func unregistered: %p", nd);
1813 void file_data_send_notification(FileData *fd, NotifyType type)
1815 GList *work = notify_func_list;
1819 NotifyData *nd = (NotifyData *)work->data;
1821 DEBUG_1("Notify func calling: %p %s", nd, fd->path);
1822 nd->func(fd, type, nd->data);
1827 static GHashTable *file_data_monitor_pool = NULL;
1828 static gint realtime_monitor_id = -1;
1830 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
1834 file_data_check_changed_files(fd);
1836 DEBUG_1("monitor %s", fd->path);
1839 static gboolean realtime_monitor_cb(gpointer data)
1841 if (!options->update_on_time_change) return TRUE;
1842 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
1846 gint file_data_register_real_time_monitor(FileData *fd)
1852 if (!file_data_monitor_pool)
1853 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
1855 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
1857 DEBUG_1("Register realtime %d %s", count, fd->path);
1860 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
1862 if (realtime_monitor_id == -1)
1864 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
1870 gint file_data_unregister_real_time_monitor(FileData *fd)
1874 g_assert(file_data_monitor_pool);
1876 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
1878 DEBUG_1("Unregister realtime %d %s", count, fd->path);
1880 g_assert(count > 0);
1885 g_hash_table_remove(file_data_monitor_pool, fd);
1887 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
1889 file_data_unref(fd);
1891 if (g_hash_table_size(file_data_monitor_pool) == 0)
1893 g_source_remove(realtime_monitor_id);
1894 realtime_monitor_id = -1;