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 (strcmp(fd->path, fd->change->dest) == 0)
1625 ret |= CHANGE_WARN_SAME;
1626 DEBUG_1("Change checked: source and destination is the same: %s -> %s", fd->path, fd->change->dest);
1629 if (!isdir(dest_dir))
1631 ret |= CHANGE_NO_DEST_DIR;
1632 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
1634 else if (!access_file(dest_dir, W_OK))
1636 ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
1637 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
1639 else if (isfile(fd->change->dest) && !access_file(fd->change->dest, W_OK) && (strcmp(fd->change->dest, fd->path) != 0))
1641 ret |= CHANGE_NO_WRITE_PERM_DEST;
1642 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
1644 else if (isfile(fd->change->dest) && (strcmp(fd->change->dest, fd->path) != 0))
1646 ret |= CHANGE_WARN_DEST_EXISTS;
1647 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1649 else if (isdir(fd->change->dest) && (strcmp(fd->change->dest, fd->path) != 0))
1651 ret |= CHANGE_DEST_EXISTS;
1652 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1656 fd->change->error = ret;
1657 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
1665 gint file_data_sc_verify_ci(FileData *fd)
1670 ret = file_data_verify_ci(fd);
1672 work = fd->sidecar_files;
1675 FileData *sfd = work->data;
1677 ret |= file_data_verify_ci(sfd);
1684 gchar *file_data_get_error_string(gint error)
1686 GString *result = g_string_new("");
1688 if (error & CHANGE_NO_SRC)
1690 if (result->len > 0) g_string_append(result, ", ");
1691 g_string_append(result, _("file or directory does not exist"));
1694 if (error & CHANGE_DEST_EXISTS)
1696 if (result->len > 0) g_string_append(result, ", ");
1697 g_string_append(result, _("destination already exists"));
1700 if (error & CHANGE_NO_WRITE_PERM_DEST)
1702 if (result->len > 0) g_string_append(result, ", ");
1703 g_string_append(result, _("destination can't be overwritten"));
1706 if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
1708 if (result->len > 0) g_string_append(result, ", ");
1709 g_string_append(result, _("destination directory is not writable"));
1712 if (error & CHANGE_NO_DEST_DIR)
1714 if (result->len > 0) g_string_append(result, ", ");
1715 g_string_append(result, _("destination directory does not exist"));
1718 if (error & CHANGE_NO_WRITE_PERM_DIR)
1720 if (result->len > 0) g_string_append(result, ", ");
1721 g_string_append(result, _("source directory is not writable"));
1724 if (error & CHANGE_NO_READ_PERM)
1726 if (result->len > 0) g_string_append(result, ", ");
1727 g_string_append(result, _("no read permission"));
1730 if (error & CHANGE_WARN_NO_WRITE_PERM)
1732 if (result->len > 0) g_string_append(result, ", ");
1733 g_string_append(result, _("file is readonly"));
1736 if (error & CHANGE_WARN_DEST_EXISTS)
1738 if (result->len > 0) g_string_append(result, ", ");
1739 g_string_append(result, _("destination already exists and will be overwritten"));
1742 if (error & CHANGE_WARN_SAME)
1744 if (result->len > 0) g_string_append(result, ", ");
1745 g_string_append(result, _("source and destination is the same"));
1748 if (error & CHANGE_WARN_CHANGED_EXT)
1750 if (result->len > 0) g_string_append(result, ", ");
1751 g_string_append(result, _("source and destination have different extension"));
1754 return g_string_free(result, FALSE);
1757 gint file_data_sc_verify_ci_list(GList *list, gchar **desc)
1759 gint all_errors = 0;
1760 gint common_errors = ~0;
1765 if (!list) return 0;
1767 num = g_list_length(list);
1768 errors = g_new(int, num);
1779 error = file_data_sc_verify_ci(fd);
1780 all_errors |= error;
1781 common_errors &= error;
1788 if (desc && all_errors)
1790 GString *result = g_string_new("");
1794 gchar *str = file_data_get_error_string(common_errors);
1795 g_string_append(result, str);
1796 g_string_append(result, "\n");
1810 error = errors[i] & ~common_errors;
1814 gchar *str = file_data_get_error_string(error);
1815 g_string_append_printf(result, "%s: %s\n", fd->name, str);
1820 *desc = g_string_free(result, FALSE);
1829 * perform the change described by FileFataChangeInfo
1830 * it is used for internal operations,
1831 * this function actually operates with files on the filesystem
1832 * it should implement safe delete
1835 static gboolean file_data_perform_move(FileData *fd)
1837 g_assert(!strcmp(fd->change->source, fd->path));
1838 return move_file(fd->change->source, fd->change->dest);
1841 static gboolean file_data_perform_copy(FileData *fd)
1843 g_assert(!strcmp(fd->change->source, fd->path));
1844 return copy_file(fd->change->source, fd->change->dest);
1847 static gboolean file_data_perform_delete(FileData *fd)
1849 if (isdir(fd->path) && !islink(fd->path))
1850 return rmdir_utf8(fd->path);
1852 return unlink_file(fd->path);
1855 static gboolean file_data_perform_ci(FileData *fd)
1857 FileDataChangeType type = fd->change->type;
1860 case FILEDATA_CHANGE_MOVE:
1861 return file_data_perform_move(fd);
1862 case FILEDATA_CHANGE_COPY:
1863 return file_data_perform_copy(fd);
1864 case FILEDATA_CHANGE_RENAME:
1865 return file_data_perform_move(fd); /* the same as move */
1866 case FILEDATA_CHANGE_DELETE:
1867 return file_data_perform_delete(fd);
1868 case FILEDATA_CHANGE_UNSPECIFIED:
1869 /* nothing to do here */
1877 gboolean file_data_sc_perform_ci(FileData *fd)
1880 gboolean ret = TRUE;
1881 FileDataChangeType type = fd->change->type;
1883 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1885 work = fd->sidecar_files;
1888 FileData *sfd = work->data;
1890 if (!file_data_perform_ci(sfd)) ret = FALSE;
1894 if (!file_data_perform_ci(fd)) ret = FALSE;
1900 * updates FileData structure according to FileDataChangeInfo
1903 static void file_data_apply_ci(FileData *fd)
1905 FileDataChangeType type = fd->change->type;
1908 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1910 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
1911 file_data_planned_change_remove(fd);
1913 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
1915 /* this change overwrites another file which is already known to other modules
1916 renaming fd would create duplicate FileData structure
1917 the best thing we can do is nothing
1918 FIXME: maybe we could copy stuff like marks
1920 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
1924 file_data_set_path(fd, fd->change->dest);
1927 file_data_increment_version(fd);
1928 file_data_send_notification(fd, NOTIFY_TYPE_CHANGE);
1931 gint file_data_sc_apply_ci(FileData *fd)
1934 FileDataChangeType type = fd->change->type;
1936 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1938 work = fd->sidecar_files;
1941 FileData *sfd = work->data;
1943 file_data_apply_ci(sfd);
1947 file_data_apply_ci(fd);
1953 * notify other modules about the change described by FileFataChangeInfo
1956 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
1957 FIXME do we need the ignore_list? It looks like a workaround for ineffective
1958 implementation in view_file_list.c */
1963 typedef struct _NotifyData NotifyData;
1965 struct _NotifyData {
1966 FileDataNotifyFunc func;
1968 NotifyPriority priority;
1971 static GList *notify_func_list = NULL;
1973 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
1975 NotifyData *nda = (NotifyData *)a;
1976 NotifyData *ndb = (NotifyData *)b;
1978 if (nda->priority < ndb->priority) return -1;
1979 if (nda->priority > ndb->priority) return 1;
1983 gint file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
1987 nd = g_new(NotifyData, 1);
1990 nd->priority = priority;
1992 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
1993 DEBUG_1("Notify func registered: %p", nd);
1998 gint file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2000 GList *work = notify_func_list;
2004 NotifyData *nd = (NotifyData *)work->data;
2006 if (nd->func == func && nd->data == data)
2008 notify_func_list = g_list_delete_link(notify_func_list, work);
2010 DEBUG_1("Notify func unregistered: %p", nd);
2020 void file_data_send_notification(FileData *fd, NotifyType type)
2022 GList *work = notify_func_list;
2026 NotifyData *nd = (NotifyData *)work->data;
2028 DEBUG_1("Notify func calling: %p %s", nd, fd->path);
2029 nd->func(fd, type, nd->data);
2034 static GHashTable *file_data_monitor_pool = NULL;
2035 static gint realtime_monitor_id = -1;
2037 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2041 file_data_check_changed_files(fd);
2043 DEBUG_1("monitor %s", fd->path);
2046 static gboolean realtime_monitor_cb(gpointer data)
2048 if (!options->update_on_time_change) return TRUE;
2049 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2053 gint file_data_register_real_time_monitor(FileData *fd)
2059 if (!file_data_monitor_pool)
2060 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2062 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2064 DEBUG_1("Register realtime %d %s", count, fd->path);
2067 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2069 if (realtime_monitor_id == -1)
2071 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2077 gint file_data_unregister_real_time_monitor(FileData *fd)
2081 g_assert(file_data_monitor_pool);
2083 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2085 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2087 g_assert(count > 0);
2092 g_hash_table_remove(file_data_monitor_pool, fd);
2094 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2096 file_data_unref(fd);
2098 if (g_hash_table_size(file_data_monitor_pool) == 0)
2100 g_source_remove(realtime_monitor_id);
2101 realtime_monitor_id = -1;