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;
1456 dest_path = fd->path;
1458 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1460 gchar *dir = remove_level_from_path(fd->path);
1462 dest_path_full = g_build_filename(dir, dest_path, NULL);
1464 dest_path = dest_path_full;
1466 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1468 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1469 dest_path = dest_path_full;
1472 file_data_update_ci_dest(fd, dest_path);
1474 work = fd->sidecar_files;
1477 FileData *sfd = work->data;
1479 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1483 g_free(dest_path_full);
1486 gint file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1488 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1489 file_data_sc_update_ci(fd, dest_path);
1493 gint file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1495 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1496 file_data_sc_update_ci(fd, dest_path);
1500 gint file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1502 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1503 file_data_sc_update_ci(fd, dest_path);
1507 gint file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1509 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1510 file_data_sc_update_ci(fd, dest_path);
1515 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1518 gboolean ret = TRUE;
1523 FileData *fd = work->data;
1525 if (!file_data_sc_update_ci_move(fd, dest)) ret = FALSE;
1532 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1535 gboolean ret = TRUE;
1540 FileData *fd = work->data;
1542 if (!file_data_sc_update_ci_copy(fd, dest)) ret = FALSE;
1549 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1552 gboolean ret = TRUE;
1557 FileData *fd = work->data;
1559 if (!file_data_sc_update_ci_unspecified(fd, dest)) ret = FALSE;
1568 * verify source and dest paths - dest image exists, etc.
1569 * it should detect all possible problems with the planned operation
1572 gint file_data_verify_ci(FileData *fd)
1574 gint ret = CHANGE_OK;
1576 gchar *dest_dir = NULL;
1580 DEBUG_1("Change checked: no change info: %s", fd->path);
1584 if (!isname(fd->path))
1586 /* this probably should not happen */
1587 ret |= CHANGE_NO_SRC;
1588 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1592 dir = remove_level_from_path(fd->path);
1594 if (fd->change->dest) dest_dir = remove_level_from_path(fd->change->dest);
1596 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1597 !access_file(fd->path, R_OK))
1599 ret |= CHANGE_NO_READ_PERM;
1600 DEBUG_1("Change checked: no read permission: %s", fd->path);
1602 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1603 !access_file(dir, W_OK))
1605 ret |= CHANGE_NO_WRITE_PERM_DIR;
1606 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1608 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1609 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1610 !access_file(fd->path, W_OK))
1612 ret |= CHANGE_WARN_NO_WRITE_PERM;
1613 DEBUG_1("Change checked: no write permission: %s", fd->path);
1616 if (fd->change->dest)
1618 const gchar *dest_ext = extension_from_path(fd->change->dest);
1619 if (!dest_ext) dest_ext = "";
1621 if (strcasecmp(fd->extension, dest_ext) != 0)
1623 ret |= CHANGE_WARN_CHANGED_EXT;
1624 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
1627 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED && /* FIXME this is now needed for running editors */
1628 strcmp(fd->path, fd->change->dest) == 0)
1630 ret |= CHANGE_WARN_SAME;
1631 DEBUG_1("Change checked: source and destination is the same: %s -> %s", fd->path, fd->change->dest);
1634 if (!isdir(dest_dir))
1636 ret |= CHANGE_NO_DEST_DIR;
1637 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
1639 else if (!access_file(dest_dir, W_OK))
1641 ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
1642 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
1644 else if (isfile(fd->change->dest) && !access_file(fd->change->dest, W_OK) && (strcmp(fd->change->dest, fd->path) != 0))
1646 ret |= CHANGE_NO_WRITE_PERM_DEST;
1647 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
1649 else if (isfile(fd->change->dest) && (strcmp(fd->change->dest, fd->path) != 0))
1651 ret |= CHANGE_WARN_DEST_EXISTS;
1652 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1654 else if (isdir(fd->change->dest) && (strcmp(fd->change->dest, fd->path) != 0))
1656 ret |= CHANGE_DEST_EXISTS;
1657 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1661 fd->change->error = ret;
1662 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
1670 gint file_data_sc_verify_ci(FileData *fd)
1675 ret = file_data_verify_ci(fd);
1677 work = fd->sidecar_files;
1680 FileData *sfd = work->data;
1682 ret |= file_data_verify_ci(sfd);
1689 gchar *file_data_get_error_string(gint error)
1691 GString *result = g_string_new("");
1693 if (error & CHANGE_NO_SRC)
1695 if (result->len > 0) g_string_append(result, ", ");
1696 g_string_append(result, _("file or directory does not exist"));
1699 if (error & CHANGE_DEST_EXISTS)
1701 if (result->len > 0) g_string_append(result, ", ");
1702 g_string_append(result, _("destination already exists"));
1705 if (error & CHANGE_NO_WRITE_PERM_DEST)
1707 if (result->len > 0) g_string_append(result, ", ");
1708 g_string_append(result, _("destination can't be overwritten"));
1711 if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
1713 if (result->len > 0) g_string_append(result, ", ");
1714 g_string_append(result, _("destination directory is not writable"));
1717 if (error & CHANGE_NO_DEST_DIR)
1719 if (result->len > 0) g_string_append(result, ", ");
1720 g_string_append(result, _("destination directory does not exist"));
1723 if (error & CHANGE_NO_WRITE_PERM_DIR)
1725 if (result->len > 0) g_string_append(result, ", ");
1726 g_string_append(result, _("source directory is not writable"));
1729 if (error & CHANGE_NO_READ_PERM)
1731 if (result->len > 0) g_string_append(result, ", ");
1732 g_string_append(result, _("no read permission"));
1735 if (error & CHANGE_WARN_NO_WRITE_PERM)
1737 if (result->len > 0) g_string_append(result, ", ");
1738 g_string_append(result, _("file is readonly"));
1741 if (error & CHANGE_WARN_DEST_EXISTS)
1743 if (result->len > 0) g_string_append(result, ", ");
1744 g_string_append(result, _("destination already exists and will be overwritten"));
1747 if (error & CHANGE_WARN_SAME)
1749 if (result->len > 0) g_string_append(result, ", ");
1750 g_string_append(result, _("source and destination is the same"));
1753 if (error & CHANGE_WARN_CHANGED_EXT)
1755 if (result->len > 0) g_string_append(result, ", ");
1756 g_string_append(result, _("source and destination have different extension"));
1759 return g_string_free(result, FALSE);
1762 gint file_data_sc_verify_ci_list(GList *list, gchar **desc)
1764 gint all_errors = 0;
1765 gint common_errors = ~0;
1770 if (!list) return 0;
1772 num = g_list_length(list);
1773 errors = g_new(int, num);
1784 error = file_data_sc_verify_ci(fd);
1785 all_errors |= error;
1786 common_errors &= error;
1793 if (desc && all_errors)
1795 GString *result = g_string_new("");
1799 gchar *str = file_data_get_error_string(common_errors);
1800 g_string_append(result, str);
1801 g_string_append(result, "\n");
1815 error = errors[i] & ~common_errors;
1819 gchar *str = file_data_get_error_string(error);
1820 g_string_append_printf(result, "%s: %s\n", fd->name, str);
1825 *desc = g_string_free(result, FALSE);
1834 * perform the change described by FileFataChangeInfo
1835 * it is used for internal operations,
1836 * this function actually operates with files on the filesystem
1837 * it should implement safe delete
1840 static gboolean file_data_perform_move(FileData *fd)
1842 g_assert(!strcmp(fd->change->source, fd->path));
1843 return move_file(fd->change->source, fd->change->dest);
1846 static gboolean file_data_perform_copy(FileData *fd)
1848 g_assert(!strcmp(fd->change->source, fd->path));
1849 return copy_file(fd->change->source, fd->change->dest);
1852 static gboolean file_data_perform_delete(FileData *fd)
1854 if (isdir(fd->path) && !islink(fd->path))
1855 return rmdir_utf8(fd->path);
1857 return unlink_file(fd->path);
1860 static gboolean file_data_perform_ci(FileData *fd)
1862 FileDataChangeType type = fd->change->type;
1865 case FILEDATA_CHANGE_MOVE:
1866 return file_data_perform_move(fd);
1867 case FILEDATA_CHANGE_COPY:
1868 return file_data_perform_copy(fd);
1869 case FILEDATA_CHANGE_RENAME:
1870 return file_data_perform_move(fd); /* the same as move */
1871 case FILEDATA_CHANGE_DELETE:
1872 return file_data_perform_delete(fd);
1873 case FILEDATA_CHANGE_UNSPECIFIED:
1874 /* nothing to do here */
1882 gboolean file_data_sc_perform_ci(FileData *fd)
1885 gboolean ret = TRUE;
1886 FileDataChangeType type = fd->change->type;
1888 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1890 work = fd->sidecar_files;
1893 FileData *sfd = work->data;
1895 if (!file_data_perform_ci(sfd)) ret = FALSE;
1899 if (!file_data_perform_ci(fd)) ret = FALSE;
1905 * updates FileData structure according to FileDataChangeInfo
1908 static void file_data_apply_ci(FileData *fd)
1910 FileDataChangeType type = fd->change->type;
1913 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1915 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
1916 file_data_planned_change_remove(fd);
1918 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
1920 /* this change overwrites another file which is already known to other modules
1921 renaming fd would create duplicate FileData structure
1922 the best thing we can do is nothing
1923 FIXME: maybe we could copy stuff like marks
1925 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
1929 file_data_set_path(fd, fd->change->dest);
1932 file_data_increment_version(fd);
1933 file_data_send_notification(fd, NOTIFY_TYPE_CHANGE);
1936 gint file_data_sc_apply_ci(FileData *fd)
1939 FileDataChangeType type = fd->change->type;
1941 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1943 work = fd->sidecar_files;
1946 FileData *sfd = work->data;
1948 file_data_apply_ci(sfd);
1952 file_data_apply_ci(fd);
1958 * notify other modules about the change described by FileFataChangeInfo
1961 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
1962 FIXME do we need the ignore_list? It looks like a workaround for ineffective
1963 implementation in view_file_list.c */
1968 typedef struct _NotifyData NotifyData;
1970 struct _NotifyData {
1971 FileDataNotifyFunc func;
1973 NotifyPriority priority;
1976 static GList *notify_func_list = NULL;
1978 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
1980 NotifyData *nda = (NotifyData *)a;
1981 NotifyData *ndb = (NotifyData *)b;
1983 if (nda->priority < ndb->priority) return -1;
1984 if (nda->priority > ndb->priority) return 1;
1988 gint file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
1992 nd = g_new(NotifyData, 1);
1995 nd->priority = priority;
1997 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
1998 DEBUG_1("Notify func registered: %p", nd);
2003 gint file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2005 GList *work = notify_func_list;
2009 NotifyData *nd = (NotifyData *)work->data;
2011 if (nd->func == func && nd->data == data)
2013 notify_func_list = g_list_delete_link(notify_func_list, work);
2015 DEBUG_1("Notify func unregistered: %p", nd);
2025 void file_data_send_notification(FileData *fd, NotifyType type)
2027 GList *work = notify_func_list;
2031 NotifyData *nd = (NotifyData *)work->data;
2033 DEBUG_1("Notify func calling: %p %s", nd, fd->path);
2034 nd->func(fd, type, nd->data);
2039 static GHashTable *file_data_monitor_pool = NULL;
2040 static gint realtime_monitor_id = -1;
2042 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2046 file_data_check_changed_files(fd);
2048 DEBUG_1("monitor %s", fd->path);
2051 static gboolean realtime_monitor_cb(gpointer data)
2053 if (!options->update_on_time_change) return TRUE;
2054 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2058 gint file_data_register_real_time_monitor(FileData *fd)
2064 if (!file_data_monitor_pool)
2065 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2067 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2069 DEBUG_1("Register realtime %d %s", count, fd->path);
2072 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2074 if (realtime_monitor_id == -1)
2076 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2082 gint file_data_unregister_real_time_monitor(FileData *fd)
2086 g_assert(file_data_monitor_pool);
2088 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2090 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2092 g_assert(count > 0);
2097 g_hash_table_remove(file_data_monitor_pool, fd);
2099 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2101 file_data_unref(fd);
2103 if (g_hash_table_size(file_data_monitor_pool) == 0)
2105 g_source_remove(realtime_monitor_id);
2106 realtime_monitor_id = -1;