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;
1578 g_assert(fd->change);
1580 if (!isname(fd->path))
1582 /* this probably should not happen */
1583 ret |= CHANGE_NO_SRC;
1584 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1588 dir = remove_level_from_path(fd->path);
1590 if (fd->change->dest) dest_dir = remove_level_from_path(fd->change->dest);
1592 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1593 !access_file(fd->path, R_OK))
1595 ret |= CHANGE_NO_READ_PERM;
1596 DEBUG_1("Change checked: no read permission: %s", fd->path);
1598 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1599 !access_file(dir, W_OK))
1601 ret |= CHANGE_NO_WRITE_PERM_DIR;
1602 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1604 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1605 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1606 !access_file(fd->path, W_OK))
1608 ret |= CHANGE_WARN_NO_WRITE_PERM;
1609 DEBUG_1("Change checked: no write permission: %s", fd->path);
1612 if (fd->change->dest)
1614 const gchar *dest_ext = extension_from_path(fd->change->dest);
1615 if (!dest_ext) dest_ext = "";
1617 if (strcasecmp(fd->extension, dest_ext) != 0)
1619 ret |= CHANGE_WARN_CHANGED_EXT;
1620 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
1623 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED && /* FIXME this is now needed for running editors */
1624 strcmp(fd->path, fd->change->dest) == 0)
1626 ret |= CHANGE_WARN_SAME;
1627 DEBUG_1("Change checked: source and destination is the same: %s -> %s", fd->path, fd->change->dest);
1630 if (!isdir(dest_dir))
1632 ret |= CHANGE_NO_DEST_DIR;
1633 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
1635 else if (!access_file(dest_dir, W_OK))
1637 ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
1638 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
1640 else if (isfile(fd->change->dest) && !access_file(fd->change->dest, W_OK) && (strcmp(fd->change->dest, fd->path) != 0))
1642 ret |= CHANGE_NO_WRITE_PERM_DEST;
1643 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
1645 else if (isfile(fd->change->dest) && (strcmp(fd->change->dest, fd->path) != 0))
1647 ret |= CHANGE_WARN_DEST_EXISTS;
1648 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1650 else if (isdir(fd->change->dest) && (strcmp(fd->change->dest, fd->path) != 0))
1652 ret |= CHANGE_DEST_EXISTS;
1653 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1657 fd->change->error = ret;
1658 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
1666 gint file_data_sc_verify_ci(FileData *fd)
1671 ret = file_data_verify_ci(fd);
1673 work = fd->sidecar_files;
1676 FileData *sfd = work->data;
1678 ret |= file_data_verify_ci(sfd);
1685 gchar *file_data_get_error_string(gint error)
1687 GString *result = g_string_new("");
1689 if (error & CHANGE_NO_SRC)
1691 if (result->len > 0) g_string_append(result, ", ");
1692 g_string_append(result, _("file or directory does not exist"));
1695 if (error & CHANGE_DEST_EXISTS)
1697 if (result->len > 0) g_string_append(result, ", ");
1698 g_string_append(result, _("destination already exists"));
1701 if (error & CHANGE_NO_WRITE_PERM_DEST)
1703 if (result->len > 0) g_string_append(result, ", ");
1704 g_string_append(result, _("destination can't be overwritten"));
1707 if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
1709 if (result->len > 0) g_string_append(result, ", ");
1710 g_string_append(result, _("destination directory is not writable"));
1713 if (error & CHANGE_NO_DEST_DIR)
1715 if (result->len > 0) g_string_append(result, ", ");
1716 g_string_append(result, _("destination directory does not exist"));
1719 if (error & CHANGE_NO_WRITE_PERM_DIR)
1721 if (result->len > 0) g_string_append(result, ", ");
1722 g_string_append(result, _("source directory is not writable"));
1725 if (error & CHANGE_NO_READ_PERM)
1727 if (result->len > 0) g_string_append(result, ", ");
1728 g_string_append(result, _("no read permission"));
1731 if (error & CHANGE_WARN_NO_WRITE_PERM)
1733 if (result->len > 0) g_string_append(result, ", ");
1734 g_string_append(result, _("file is readonly"));
1737 if (error & CHANGE_WARN_DEST_EXISTS)
1739 if (result->len > 0) g_string_append(result, ", ");
1740 g_string_append(result, _("destination already exists and will be overwritten"));
1743 if (error & CHANGE_WARN_SAME)
1745 if (result->len > 0) g_string_append(result, ", ");
1746 g_string_append(result, _("source and destination is the same"));
1749 if (error & CHANGE_WARN_CHANGED_EXT)
1751 if (result->len > 0) g_string_append(result, ", ");
1752 g_string_append(result, _("source and destination have different extension"));
1755 return g_string_free(result, FALSE);
1758 gint file_data_sc_verify_ci_list(GList *list, gchar **desc)
1760 gint all_errors = 0;
1761 gint common_errors = ~0;
1766 if (!list) return 0;
1768 num = g_list_length(list);
1769 errors = g_new(int, num);
1780 error = file_data_sc_verify_ci(fd);
1781 all_errors |= error;
1782 common_errors &= error;
1789 if (desc && all_errors)
1791 GString *result = g_string_new("");
1795 gchar *str = file_data_get_error_string(common_errors);
1796 g_string_append(result, str);
1797 g_string_append(result, "\n");
1811 error = errors[i] & ~common_errors;
1815 gchar *str = file_data_get_error_string(error);
1816 g_string_append_printf(result, "%s: %s\n", fd->name, str);
1821 *desc = g_string_free(result, FALSE);
1830 * perform the change described by FileFataChangeInfo
1831 * it is used for internal operations,
1832 * this function actually operates with files on the filesystem
1833 * it should implement safe delete
1836 static gboolean file_data_perform_move(FileData *fd)
1838 g_assert(!strcmp(fd->change->source, fd->path));
1839 return move_file(fd->change->source, fd->change->dest);
1842 static gboolean file_data_perform_copy(FileData *fd)
1844 g_assert(!strcmp(fd->change->source, fd->path));
1845 return copy_file(fd->change->source, fd->change->dest);
1848 static gboolean file_data_perform_delete(FileData *fd)
1850 if (isdir(fd->path) && !islink(fd->path))
1851 return rmdir_utf8(fd->path);
1853 return unlink_file(fd->path);
1856 static gboolean file_data_perform_ci(FileData *fd)
1858 FileDataChangeType type = fd->change->type;
1861 case FILEDATA_CHANGE_MOVE:
1862 return file_data_perform_move(fd);
1863 case FILEDATA_CHANGE_COPY:
1864 return file_data_perform_copy(fd);
1865 case FILEDATA_CHANGE_RENAME:
1866 return file_data_perform_move(fd); /* the same as move */
1867 case FILEDATA_CHANGE_DELETE:
1868 return file_data_perform_delete(fd);
1869 case FILEDATA_CHANGE_UNSPECIFIED:
1870 /* nothing to do here */
1878 gboolean file_data_sc_perform_ci(FileData *fd)
1881 gboolean ret = TRUE;
1882 FileDataChangeType type = fd->change->type;
1884 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1886 work = fd->sidecar_files;
1889 FileData *sfd = work->data;
1891 if (!file_data_perform_ci(sfd)) ret = FALSE;
1895 if (!file_data_perform_ci(fd)) ret = FALSE;
1901 * updates FileData structure according to FileDataChangeInfo
1904 static void file_data_apply_ci(FileData *fd)
1906 FileDataChangeType type = fd->change->type;
1909 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1911 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
1912 file_data_planned_change_remove(fd);
1914 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
1916 /* this change overwrites another file which is already known to other modules
1917 renaming fd would create duplicate FileData structure
1918 the best thing we can do is nothing
1919 FIXME: maybe we could copy stuff like marks
1921 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
1925 file_data_set_path(fd, fd->change->dest);
1928 file_data_increment_version(fd);
1929 file_data_send_notification(fd, NOTIFY_TYPE_CHANGE);
1932 gint file_data_sc_apply_ci(FileData *fd)
1935 FileDataChangeType type = fd->change->type;
1937 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1939 work = fd->sidecar_files;
1942 FileData *sfd = work->data;
1944 file_data_apply_ci(sfd);
1948 file_data_apply_ci(fd);
1954 * notify other modules about the change described by FileFataChangeInfo
1957 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
1958 FIXME do we need the ignore_list? It looks like a workaround for ineffective
1959 implementation in view_file_list.c */
1964 typedef struct _NotifyData NotifyData;
1966 struct _NotifyData {
1967 FileDataNotifyFunc func;
1969 NotifyPriority priority;
1972 static GList *notify_func_list = NULL;
1974 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
1976 NotifyData *nda = (NotifyData *)a;
1977 NotifyData *ndb = (NotifyData *)b;
1979 if (nda->priority < ndb->priority) return -1;
1980 if (nda->priority > ndb->priority) return 1;
1984 gint file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
1988 nd = g_new(NotifyData, 1);
1991 nd->priority = priority;
1993 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
1994 DEBUG_1("Notify func registered: %p", nd);
1999 gint file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2001 GList *work = notify_func_list;
2005 NotifyData *nd = (NotifyData *)work->data;
2007 if (nd->func == func && nd->data == data)
2009 notify_func_list = g_list_delete_link(notify_func_list, work);
2011 DEBUG_1("Notify func unregistered: %p", nd);
2021 void file_data_send_notification(FileData *fd, NotifyType type)
2023 GList *work = notify_func_list;
2027 NotifyData *nd = (NotifyData *)work->data;
2029 DEBUG_1("Notify func calling: %p %s", nd, fd->path);
2030 nd->func(fd, type, nd->data);
2035 static GHashTable *file_data_monitor_pool = NULL;
2036 static gint realtime_monitor_id = -1;
2038 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2042 file_data_check_changed_files(fd);
2044 DEBUG_1("monitor %s", fd->path);
2047 static gboolean realtime_monitor_cb(gpointer data)
2049 if (!options->update_on_time_change) return TRUE;
2050 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2054 gint file_data_register_real_time_monitor(FileData *fd)
2060 if (!file_data_monitor_pool)
2061 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2063 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2065 DEBUG_1("Register realtime %d %s", count, fd->path);
2068 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2070 if (realtime_monitor_id == -1)
2072 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2078 gint file_data_unregister_real_time_monitor(FileData *fd)
2082 g_assert(file_data_monitor_pool);
2084 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2086 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2088 g_assert(count > 0);
2093 g_hash_table_remove(file_data_monitor_pool, fd);
2095 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2097 file_data_unref(fd);
2099 if (g_hash_table_size(file_data_monitor_pool) == 0)
2101 g_source_remove(realtime_monitor_id);
2102 realtime_monitor_id = -1;