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 fd->mode = st->st_mode;
239 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
240 fd->thumb_pixbuf = NULL;
241 file_data_increment_version(fd);
242 file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
246 work = fd->sidecar_files;
249 FileData *sfd = work->data;
253 if (!stat_utf8(sfd->path, &st))
257 file_data_disconnect_sidecar_file(fd, sfd);
262 ret |= file_data_check_changed_files_recursive(sfd, &st);
268 gboolean file_data_check_changed_files(FileData *fd)
270 gboolean ret = FALSE;
273 if (fd->parent) fd = fd->parent;
275 if (!stat_utf8(fd->path, &st))
278 FileData *sfd = NULL;
280 /* parent is missing, we have to rebuild whole group */
285 work = fd->sidecar_files;
291 file_data_disconnect_sidecar_file(fd, sfd);
293 if (sfd) file_data_check_sidecars(sfd); /* this will group the sidecars back together */
294 file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
298 ret |= file_data_check_changed_files_recursive(fd, &st);
304 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean check_sidecars)
308 DEBUG_2("file_data_new: '%s' %d", path_utf8, check_sidecars);
311 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
313 fd = g_hash_table_lookup(file_data_pool, path_utf8);
319 if (!fd && file_data_planned_change_hash)
321 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
324 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
326 file_data_apply_ci(fd);
335 changed = file_data_check_changed_files(fd);
337 changed = file_data_check_changed_files_recursive(fd, st);
338 if (changed && check_sidecars && sidecar_file_priority(fd->extension))
339 file_data_check_sidecars(fd);
340 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
345 fd = g_new0(FileData, 1);
349 fd->collate_key_name = NULL;
350 fd->collate_key_name_nocase = NULL;
351 fd->original_path = NULL;
353 fd->size = st->st_size;
354 fd->date = st->st_mtime;
355 fd->mode = st->st_mode;
356 fd->thumb_pixbuf = NULL;
357 fd->sidecar_files = NULL;
359 fd->magick = 0x12345678;
361 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
364 file_data_check_sidecars(fd);
369 static void file_data_check_sidecars(FileData *fd)
373 FileData *parent_fd = NULL;
376 if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
379 base_len = fd->extension - fd->path;
380 fname = g_string_new_len(fd->path, base_len);
381 work = sidecar_ext_get_list();
385 /* check for possible sidecar files;
386 the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
387 they have fd->ref set to 0 and file_data unref must chack and free them all together
388 (using fd->ref would cause loops and leaks)
392 gchar *ext = work->data;
396 if (strcmp(ext, fd->extension) == 0)
398 new_fd = fd; /* processing the original file */
403 g_string_truncate(fname, base_len);
404 g_string_append(fname, ext);
406 if (!stat_utf8(fname->str, &nst))
409 new_fd = file_data_new(fname->str, &nst, FALSE);
411 if (new_fd->disable_grouping)
413 file_data_unref(new_fd);
417 new_fd->ref--; /* do not use ref here */
421 parent_fd = new_fd; /* parent is the one with the highest prio, found first */
423 file_data_merge_sidecar_files(parent_fd, new_fd);
425 g_string_free(fname, TRUE);
429 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars)
431 gchar *path_utf8 = path_to_utf8(path);
432 FileData *ret = file_data_new(path_utf8, st, check_sidecars);
438 FileData *file_data_new_simple(const gchar *path_utf8)
442 if (!stat_utf8(path_utf8, &st))
448 return file_data_new(path_utf8, &st, TRUE);
451 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
453 sfd->parent = target;
454 if (!g_list_find(target->sidecar_files, sfd))
455 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
456 file_data_increment_version(sfd); /* increments both sfd and target */
461 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
465 file_data_add_sidecar_file(target, source);
467 work = source->sidecar_files;
470 FileData *sfd = work->data;
471 file_data_add_sidecar_file(target, sfd);
475 g_list_free(source->sidecar_files);
476 source->sidecar_files = NULL;
478 target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
483 #ifdef DEBUG_FILEDATA
484 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
486 FileData *file_data_ref(FileData *fd)
489 if (fd == NULL) return NULL;
490 #ifdef DEBUG_FILEDATA
491 if (fd->magick != 0x12345678)
492 DEBUG_0("fd magick mismatch at %s:%d", file, line);
494 g_assert(fd->magick == 0x12345678);
497 #ifdef DEBUG_FILEDATA
498 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
500 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
505 static void file_data_free(FileData *fd)
507 g_assert(fd->magick == 0x12345678);
508 g_assert(fd->ref == 0);
510 g_hash_table_remove(file_data_pool, fd->original_path);
513 g_free(fd->original_path);
514 g_free(fd->collate_key_name);
515 g_free(fd->collate_key_name_nocase);
516 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
518 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
520 file_data_change_info_free(NULL, fd);
524 #ifdef DEBUG_FILEDATA
525 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
527 void file_data_unref(FileData *fd)
530 if (fd == NULL) return;
531 #ifdef DEBUG_FILEDATA
532 if (fd->magick != 0x12345678)
533 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
535 g_assert(fd->magick == 0x12345678);
538 #ifdef DEBUG_FILEDATA
539 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
542 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
547 FileData *parent = fd->parent ? fd->parent : fd;
552 work = parent->sidecar_files;
555 FileData *sfd = work->data;
561 /* none of parent/children is referenced, we can free everything */
563 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
565 work = parent->sidecar_files;
568 FileData *sfd = work->data;
573 g_list_free(parent->sidecar_files);
574 parent->sidecar_files = NULL;
576 file_data_free(parent);
580 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
582 sfd->parent = target;
583 g_assert(g_list_find(target->sidecar_files, sfd));
585 file_data_increment_version(sfd); /* increments both sfd and target */
587 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
599 /* disables / enables grouping for particular file, sends UPDATE notification */
600 void file_data_disable_grouping(FileData *fd, gboolean disable)
602 if (!fd->disable_grouping == !disable) return;
603 fd->disable_grouping = !!disable;
609 FileData *parent = file_data_ref(fd->parent);
610 file_data_disconnect_sidecar_file(parent, fd);
611 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
612 file_data_send_notification(parent, NOTIFY_TYPE_INTERNAL);
613 file_data_unref(parent);
615 else if (fd->sidecar_files)
617 GList *sidecar_files = filelist_copy(fd->sidecar_files);
618 GList *work = sidecar_files;
621 FileData *sfd = work->data;
623 file_data_disconnect_sidecar_file(fd, sfd);
624 file_data_send_notification(sfd, NOTIFY_TYPE_INTERNAL);
626 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
627 file_data_check_sidecars((FileData *)sidecar_files->data); /* this will group the sidecars back together */
628 filelist_free(sidecar_files);
633 file_data_check_sidecars(fd);
634 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
638 /* compare name without extension */
639 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
641 size_t len1 = fd1->extension - fd1->name;
642 size_t len2 = fd2->extension - fd2->name;
644 if (len1 < len2) return -1;
645 if (len1 > len2) return 1;
647 return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
650 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
658 g_free(fdci->source);
671 *-----------------------------------------------------------------------------
672 * sidecar file info struct
673 *-----------------------------------------------------------------------------
678 static gint sidecar_file_priority(const gchar *path)
680 const char *extension = extension_from_path(path);
684 if (extension == NULL)
687 work = sidecar_ext_get_list();
690 gchar *ext = work->data;
693 if (strcmp(extension, ext) == 0) return i;
701 *-----------------------------------------------------------------------------
703 *-----------------------------------------------------------------------------
706 static SortType filelist_sort_method = SORT_NONE;
707 static gint filelist_sort_ascend = TRUE;
710 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
712 if (!filelist_sort_ascend)
719 switch (filelist_sort_method)
724 if (fa->size < fb->size) return -1;
725 if (fa->size > fb->size) return 1;
726 /* fall back to name */
729 if (fa->date < fb->date) return -1;
730 if (fa->date > fb->date) return 1;
731 /* fall back to name */
733 #ifdef HAVE_STRVERSCMP
735 return strverscmp(fa->name, fb->name);
742 if (options->file_sort.case_sensitive)
743 return strcmp(fa->collate_key_name, fb->collate_key_name);
745 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
748 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gint ascend)
750 filelist_sort_method = method;
751 filelist_sort_ascend = ascend;
752 return filelist_sort_compare_filedata(fa, fb);
755 static gint filelist_sort_file_cb(void *a, void *b)
757 return filelist_sort_compare_filedata(a, b);
760 GList *filelist_sort_full(GList *list, SortType method, gint ascend, GCompareFunc cb)
762 filelist_sort_method = method;
763 filelist_sort_ascend = ascend;
764 return g_list_sort(list, cb);
767 GList *filelist_insert_sort_full(GList *list, void *data, SortType method, gint ascend, GCompareFunc cb)
769 filelist_sort_method = method;
770 filelist_sort_ascend = ascend;
771 return g_list_insert_sorted(list, data, cb);
774 GList *filelist_sort(GList *list, SortType method, gint ascend)
776 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
779 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gint ascend)
781 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
785 static GList *filelist_filter_out_sidecars(GList *flist)
788 GList *flist_filtered = NULL;
792 FileData *fd = work->data;
795 if (fd->parent) /* remove fd's that are children */
798 flist_filtered = g_list_prepend(flist_filtered, fd);
802 return flist_filtered;
805 static gint filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gint follow_symlinks)
812 int (*stat_func)(const char *path, struct stat *buf);
814 g_assert(files || dirs);
816 if (files) *files = NULL;
817 if (dirs) *dirs = NULL;
819 pathl = path_from_utf8(dir_fd->path);
820 if (!pathl) return FALSE;
834 while ((dir = readdir(dp)) != NULL)
836 struct stat ent_sbuf;
837 const gchar *name = dir->d_name;
840 if (!options->file_filter.show_hidden_files && ishidden(name))
843 filepath = g_build_filename(pathl, name, NULL);
844 if (stat_func(filepath, &ent_sbuf) >= 0)
846 if (S_ISDIR(ent_sbuf.st_mode))
848 /* we ignore the .thumbnails dir for cleanliness */
850 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
851 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
852 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
853 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
855 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE));
860 if (files && filter_name_exists(name))
862 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE));
873 if (dirs) *dirs = dlist;
874 if (files) *files = filelist_filter_out_sidecars(flist);
879 gint filelist_read(FileData *dir_fd, GList **files, GList **dirs)
881 return filelist_read_real(dir_fd, files, dirs, TRUE);
884 gint filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
886 return filelist_read_real(dir_fd, files, dirs, FALSE);
889 void filelist_free(GList *list)
896 file_data_unref((FileData *)work->data);
904 GList *filelist_copy(GList *list)
906 GList *new_list = NULL;
917 new_list = g_list_prepend(new_list, file_data_ref(fd));
920 return g_list_reverse(new_list);
923 GList *filelist_from_path_list(GList *list)
925 GList *new_list = NULL;
936 new_list = g_list_prepend(new_list, file_data_new_simple(path));
939 return g_list_reverse(new_list);
942 GList *filelist_to_path_list(GList *list)
944 GList *new_list = NULL;
955 new_list = g_list_prepend(new_list, g_strdup(fd->path));
958 return g_list_reverse(new_list);
961 GList *filelist_filter(GList *list, gint is_dir_list)
965 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
970 FileData *fd = (FileData *)(work->data);
971 const gchar *name = fd->name;
973 if ((!options->file_filter.show_hidden_files && ishidden(name)) ||
974 (!is_dir_list && !filter_name_exists(name)) ||
975 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
976 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
980 list = g_list_remove_link(list, link);
992 *-----------------------------------------------------------------------------
994 *-----------------------------------------------------------------------------
997 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
999 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1002 GList *filelist_sort_path(GList *list)
1004 return g_list_sort(list, filelist_sort_path_cb);
1007 static void filelist_recursive_append(GList **list, GList *dirs)
1014 FileData *fd = (FileData *)(work->data);
1018 if (filelist_read(fd, &f, &d))
1020 f = filelist_filter(f, FALSE);
1021 f = filelist_sort_path(f);
1022 *list = g_list_concat(*list, f);
1024 d = filelist_filter(d, TRUE);
1025 d = filelist_sort_path(d);
1026 filelist_recursive_append(list, d);
1034 GList *filelist_recursive(FileData *dir_fd)
1039 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1040 list = filelist_filter(list, FALSE);
1041 list = filelist_sort_path(list);
1043 d = filelist_filter(d, TRUE);
1044 d = filelist_sort_path(d);
1045 filelist_recursive_append(&list, d);
1053 * marks and orientation
1057 gboolean file_data_get_mark(FileData *fd, gint n)
1059 return !!(fd->marks & (1 << n));
1062 guint file_data_get_marks(FileData *fd)
1067 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1069 guint old = fd->marks;
1070 if (!value == !(fd->marks & (1 << n))) return;
1072 fd->marks = fd->marks ^ (1 << n);
1074 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1076 file_data_unref(fd);
1078 else if (!old && fd->marks)
1083 file_data_increment_version(fd);
1084 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1087 gboolean file_data_filter_marks(FileData *fd, guint filter)
1089 return ((fd->marks & filter) == filter);
1092 GList *file_data_filter_marks_list(GList *list, guint filter)
1099 FileData *fd = work->data;
1103 if (!file_data_filter_marks(fd, filter))
1105 list = g_list_remove_link(list, link);
1106 file_data_unref(fd);
1114 gint file_data_get_user_orientation(FileData *fd)
1116 return fd->user_orientation;
1119 void file_data_set_user_orientation(FileData *fd, gint value)
1121 if (fd->user_orientation == value) return;
1123 fd->user_orientation = value;
1124 file_data_increment_version(fd);
1125 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1131 * file_data - operates on the given fd
1132 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1136 /* return list of sidecar file extensions in a string */
1137 gchar *file_data_sc_list_to_string(FileData *fd)
1140 GString *result = g_string_new("");
1142 work = fd->sidecar_files;
1145 FileData *sfd = work->data;
1147 result = g_string_append(result, "+ ");
1148 result = g_string_append(result, sfd->extension);
1150 if (work) result = g_string_append_c(result, ' ');
1153 return g_string_free(result, FALSE);
1159 * add FileDataChangeInfo (see typedefs.h) for the given operation
1160 * uses file_data_add_change_info
1162 * fails if the fd->change already exists - change operations can't run in parallel
1163 * fd->change_info works as a lock
1165 * dest can be NULL - in this case the current name is used for now, it will
1170 FileDataChangeInfo types:
1172 MOVE - path is changed, name may be changed too
1173 RENAME - path remains unchanged, name is changed
1174 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1175 sidecar names are changed too, extensions are not changed
1177 UPDATE - file size, date or grouping has been changed
1180 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1182 FileDataChangeInfo *fdci;
1184 if (fd->change) return FALSE;
1186 fdci = g_new0(FileDataChangeInfo, 1);
1191 fdci->source = g_strdup(src);
1193 fdci->source = g_strdup(fd->path);
1196 fdci->dest = g_strdup(dest);
1203 static void file_data_planned_change_remove(FileData *fd)
1205 if (file_data_planned_change_hash &&
1206 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1208 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1210 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1211 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1212 file_data_unref(fd);
1213 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1215 g_hash_table_destroy(file_data_planned_change_hash);
1216 file_data_planned_change_hash = NULL;
1217 DEBUG_1("planned change: empty");
1224 void file_data_free_ci(FileData *fd)
1226 FileDataChangeInfo *fdci = fd->change;
1231 file_data_planned_change_remove(fd);
1233 g_free(fdci->source);
1242 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1246 if (fd->parent) fd = fd->parent;
1248 if (fd->change) return FALSE;
1250 work = fd->sidecar_files;
1253 FileData *sfd = work->data;
1255 if (sfd->change) return FALSE;
1259 file_data_add_ci(fd, type, NULL, NULL);
1261 work = fd->sidecar_files;
1264 FileData *sfd = work->data;
1266 file_data_add_ci(sfd, type, NULL, NULL);
1273 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1277 if (fd->parent) fd = fd->parent;
1279 if (!fd->change || fd->change->type != type) return FALSE;
1281 work = fd->sidecar_files;
1284 FileData *sfd = work->data;
1286 if (!sfd->change || sfd->change->type != type) return FALSE;
1294 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1296 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1297 file_data_sc_update_ci_copy(fd, dest_path);
1301 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1303 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1304 file_data_sc_update_ci_move(fd, dest_path);
1308 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1310 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1311 file_data_sc_update_ci_rename(fd, dest_path);
1315 gboolean file_data_sc_add_ci_delete(FileData *fd)
1317 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1320 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1322 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1323 file_data_sc_update_ci_unspecified(fd, dest_path);
1327 void file_data_sc_free_ci(FileData *fd)
1331 if (fd->parent) fd = fd->parent;
1333 file_data_free_ci(fd);
1335 work = fd->sidecar_files;
1338 FileData *sfd = work->data;
1340 file_data_free_ci(sfd);
1345 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1348 gboolean ret = TRUE;
1353 FileData *fd = work->data;
1355 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1362 static void file_data_sc_revert_ci_list(GList *fd_list)
1369 FileData *fd = work->data;
1371 file_data_sc_free_ci(fd);
1376 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1383 FileData *fd = work->data;
1385 if (!func(fd, dest))
1387 file_data_sc_revert_ci_list(work->prev);
1396 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1398 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1401 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1403 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1406 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1408 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1411 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1413 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1416 void file_data_sc_free_ci_list(GList *fd_list)
1423 FileData *fd = work->data;
1425 file_data_sc_free_ci(fd);
1431 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1432 * fails if fd->change does not exist or the change type does not match
1435 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1437 FileDataChangeType type = fd->change->type;
1439 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1443 if (!file_data_planned_change_hash)
1444 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1446 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1448 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1449 g_hash_table_remove(file_data_planned_change_hash, old_path);
1450 file_data_unref(fd);
1453 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1458 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1459 g_hash_table_remove(file_data_planned_change_hash, new_path);
1460 file_data_unref(ofd);
1463 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1465 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1470 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1472 gchar *old_path = fd->change->dest;
1474 fd->change->dest = g_strdup(dest_path);
1475 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1479 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1481 const char *extension = extension_from_path(fd->change->source);
1482 gchar *base = remove_extension_from_path(dest_path);
1483 gchar *old_path = fd->change->dest;
1485 fd->change->dest = g_strconcat(base, extension, NULL);
1486 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1492 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1495 gchar *dest_path_full = NULL;
1497 if (fd->parent) fd = fd->parent;
1501 dest_path = fd->path;
1503 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1505 gchar *dir = remove_level_from_path(fd->path);
1507 dest_path_full = g_build_filename(dir, dest_path, NULL);
1509 dest_path = dest_path_full;
1511 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1513 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1514 dest_path = dest_path_full;
1517 file_data_update_ci_dest(fd, dest_path);
1519 work = fd->sidecar_files;
1522 FileData *sfd = work->data;
1524 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1528 g_free(dest_path_full);
1531 static gint file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1533 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1534 file_data_sc_update_ci(fd, dest_path);
1538 gint file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1540 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1543 gint file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1545 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1548 gint file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1550 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1553 gint file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1555 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1558 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1560 gboolean (*func)(FileData *, const gchar *))
1563 gboolean ret = TRUE;
1568 FileData *fd = work->data;
1570 if (!func(fd, dest)) ret = FALSE;
1577 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1579 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1582 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1584 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1587 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1589 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1594 * verify source and dest paths - dest image exists, etc.
1595 * it should detect all possible problems with the planned operation
1598 gint file_data_verify_ci(FileData *fd)
1600 gint ret = CHANGE_OK;
1605 DEBUG_1("Change checked: no change info: %s", fd->path);
1609 if (!isname(fd->path))
1611 /* this probably should not happen */
1612 ret |= CHANGE_NO_SRC;
1613 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1617 dir = remove_level_from_path(fd->path);
1619 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1620 !access_file(fd->path, R_OK))
1622 ret |= CHANGE_NO_READ_PERM;
1623 DEBUG_1("Change checked: no read permission: %s", fd->path);
1625 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1626 !access_file(dir, W_OK))
1628 ret |= CHANGE_NO_WRITE_PERM_DIR;
1629 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1631 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1632 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1633 !access_file(fd->path, W_OK))
1635 ret |= CHANGE_WARN_NO_WRITE_PERM;
1636 DEBUG_1("Change checked: no write permission: %s", fd->path);
1639 if (fd->change->dest)
1644 same = (strcmp(fd->path, fd->change->dest) == 0);
1648 const gchar *dest_ext = extension_from_path(fd->change->dest);
1649 if (!dest_ext) dest_ext = "";
1651 if (strcasecmp(fd->extension, dest_ext) != 0)
1653 ret |= CHANGE_WARN_CHANGED_EXT;
1654 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
1659 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
1661 ret |= CHANGE_WARN_SAME;
1662 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
1666 dest_dir = remove_level_from_path(fd->change->dest);
1668 if (!isdir(dest_dir))
1670 ret |= CHANGE_NO_DEST_DIR;
1671 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
1673 else if (!access_file(dest_dir, W_OK))
1675 ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
1676 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
1680 if (isfile(fd->change->dest))
1682 if (!access_file(fd->change->dest, W_OK))
1684 ret |= CHANGE_NO_WRITE_PERM_DEST;
1685 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
1689 ret |= CHANGE_WARN_DEST_EXISTS;
1690 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1693 else if (isdir(fd->change->dest))
1695 ret |= CHANGE_DEST_EXISTS;
1696 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1703 fd->change->error = ret;
1704 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
1711 gint file_data_sc_verify_ci(FileData *fd)
1716 ret = file_data_verify_ci(fd);
1718 work = fd->sidecar_files;
1721 FileData *sfd = work->data;
1723 ret |= file_data_verify_ci(sfd);
1730 gchar *file_data_get_error_string(gint error)
1732 GString *result = g_string_new("");
1734 if (error & CHANGE_NO_SRC)
1736 if (result->len > 0) g_string_append(result, ", ");
1737 g_string_append(result, _("file or directory does not exist"));
1740 if (error & CHANGE_DEST_EXISTS)
1742 if (result->len > 0) g_string_append(result, ", ");
1743 g_string_append(result, _("destination already exists"));
1746 if (error & CHANGE_NO_WRITE_PERM_DEST)
1748 if (result->len > 0) g_string_append(result, ", ");
1749 g_string_append(result, _("destination can't be overwritten"));
1752 if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
1754 if (result->len > 0) g_string_append(result, ", ");
1755 g_string_append(result, _("destination directory is not writable"));
1758 if (error & CHANGE_NO_DEST_DIR)
1760 if (result->len > 0) g_string_append(result, ", ");
1761 g_string_append(result, _("destination directory does not exist"));
1764 if (error & CHANGE_NO_WRITE_PERM_DIR)
1766 if (result->len > 0) g_string_append(result, ", ");
1767 g_string_append(result, _("source directory is not writable"));
1770 if (error & CHANGE_NO_READ_PERM)
1772 if (result->len > 0) g_string_append(result, ", ");
1773 g_string_append(result, _("no read permission"));
1776 if (error & CHANGE_WARN_NO_WRITE_PERM)
1778 if (result->len > 0) g_string_append(result, ", ");
1779 g_string_append(result, _("file is readonly"));
1782 if (error & CHANGE_WARN_DEST_EXISTS)
1784 if (result->len > 0) g_string_append(result, ", ");
1785 g_string_append(result, _("destination already exists and will be overwritten"));
1788 if (error & CHANGE_WARN_SAME)
1790 if (result->len > 0) g_string_append(result, ", ");
1791 g_string_append(result, _("source and destination are the same"));
1794 if (error & CHANGE_WARN_CHANGED_EXT)
1796 if (result->len > 0) g_string_append(result, ", ");
1797 g_string_append(result, _("source and destination have different extension"));
1800 return g_string_free(result, FALSE);
1803 gint file_data_sc_verify_ci_list(GList *list, gchar **desc)
1806 gint all_errors = 0;
1807 gint common_errors = ~0;
1812 if (!list) return 0;
1814 num = g_list_length(list);
1815 errors = g_new(int, num);
1826 error = file_data_sc_verify_ci(fd);
1827 all_errors |= error;
1828 common_errors &= error;
1835 if (desc && all_errors)
1838 GString *result = g_string_new("");
1842 gchar *str = file_data_get_error_string(common_errors);
1843 g_string_append(result, str);
1844 g_string_append(result, "\n");
1858 error = errors[i] & ~common_errors;
1862 gchar *str = file_data_get_error_string(error);
1863 g_string_append_printf(result, "%s: %s\n", fd->name, str);
1868 *desc = g_string_free(result, FALSE);
1877 * perform the change described by FileFataChangeInfo
1878 * it is used for internal operations,
1879 * this function actually operates with files on the filesystem
1880 * it should implement safe delete
1883 static gboolean file_data_perform_move(FileData *fd)
1885 g_assert(!strcmp(fd->change->source, fd->path));
1886 return move_file(fd->change->source, fd->change->dest);
1889 static gboolean file_data_perform_copy(FileData *fd)
1891 g_assert(!strcmp(fd->change->source, fd->path));
1892 return copy_file(fd->change->source, fd->change->dest);
1895 static gboolean file_data_perform_delete(FileData *fd)
1897 if (isdir(fd->path) && !islink(fd->path))
1898 return rmdir_utf8(fd->path);
1900 return unlink_file(fd->path);
1903 static gboolean file_data_perform_ci(FileData *fd)
1905 FileDataChangeType type = fd->change->type;
1908 case FILEDATA_CHANGE_MOVE:
1909 return file_data_perform_move(fd);
1910 case FILEDATA_CHANGE_COPY:
1911 return file_data_perform_copy(fd);
1912 case FILEDATA_CHANGE_RENAME:
1913 return file_data_perform_move(fd); /* the same as move */
1914 case FILEDATA_CHANGE_DELETE:
1915 return file_data_perform_delete(fd);
1916 case FILEDATA_CHANGE_UNSPECIFIED:
1917 /* nothing to do here */
1925 gboolean file_data_sc_perform_ci(FileData *fd)
1928 gboolean ret = TRUE;
1929 FileDataChangeType type = fd->change->type;
1931 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1933 work = fd->sidecar_files;
1936 FileData *sfd = work->data;
1938 if (!file_data_perform_ci(sfd)) ret = FALSE;
1942 if (!file_data_perform_ci(fd)) ret = FALSE;
1948 * updates FileData structure according to FileDataChangeInfo
1951 static void file_data_apply_ci(FileData *fd)
1953 FileDataChangeType type = fd->change->type;
1956 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1958 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
1959 file_data_planned_change_remove(fd);
1961 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
1963 /* this change overwrites another file which is already known to other modules
1964 renaming fd would create duplicate FileData structure
1965 the best thing we can do is nothing
1966 FIXME: maybe we could copy stuff like marks
1968 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
1972 file_data_set_path(fd, fd->change->dest);
1975 file_data_increment_version(fd);
1976 file_data_send_notification(fd, NOTIFY_TYPE_CHANGE);
1979 gint file_data_sc_apply_ci(FileData *fd)
1982 FileDataChangeType type = fd->change->type;
1984 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1986 work = fd->sidecar_files;
1989 FileData *sfd = work->data;
1991 file_data_apply_ci(sfd);
1995 file_data_apply_ci(fd);
2001 * notify other modules about the change described by FileFataChangeInfo
2004 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2005 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2006 implementation in view_file_list.c */
2011 typedef struct _NotifyData NotifyData;
2013 struct _NotifyData {
2014 FileDataNotifyFunc func;
2016 NotifyPriority priority;
2019 static GList *notify_func_list = NULL;
2021 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2023 NotifyData *nda = (NotifyData *)a;
2024 NotifyData *ndb = (NotifyData *)b;
2026 if (nda->priority < ndb->priority) return -1;
2027 if (nda->priority > ndb->priority) return 1;
2031 gint file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2035 nd = g_new(NotifyData, 1);
2038 nd->priority = priority;
2040 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2041 DEBUG_1("Notify func registered: %p", nd);
2046 gint file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2048 GList *work = notify_func_list;
2052 NotifyData *nd = (NotifyData *)work->data;
2054 if (nd->func == func && nd->data == data)
2056 notify_func_list = g_list_delete_link(notify_func_list, work);
2058 DEBUG_1("Notify func unregistered: %p", nd);
2068 void file_data_send_notification(FileData *fd, NotifyType type)
2070 GList *work = notify_func_list;
2074 NotifyData *nd = (NotifyData *)work->data;
2076 DEBUG_1("Notify func calling: %p %s", nd, fd->path);
2077 nd->func(fd, type, nd->data);
2082 static GHashTable *file_data_monitor_pool = NULL;
2083 static gint realtime_monitor_id = -1;
2085 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2089 file_data_check_changed_files(fd);
2091 DEBUG_1("monitor %s", fd->path);
2094 static gboolean realtime_monitor_cb(gpointer data)
2096 if (!options->update_on_time_change) return TRUE;
2097 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2101 gint file_data_register_real_time_monitor(FileData *fd)
2107 if (!file_data_monitor_pool)
2108 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2110 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2112 DEBUG_1("Register realtime %d %s", count, fd->path);
2115 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2117 if (realtime_monitor_id == -1)
2119 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2125 gint file_data_unregister_real_time_monitor(FileData *fd)
2129 g_assert(file_data_monitor_pool);
2131 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2133 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2135 g_assert(count > 0);
2140 g_hash_table_remove(file_data_monitor_pool, fd);
2142 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2144 file_data_unref(fd);
2146 if (g_hash_table_size(file_data_monitor_pool) == 0)
2148 g_source_remove(realtime_monitor_id);
2149 realtime_monitor_id = -1;