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);
182 fd->original_path = g_strdup(path);
183 g_hash_table_insert(file_data_pool, fd->original_path, fd);
185 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
187 fd->path = g_strdup(path);
189 fd->extension = fd->name + 1;
190 file_data_set_collate_keys(fd);
194 fd->path = g_strdup(path);
195 fd->name = filename_from_path(fd->path);
197 if (strcmp(fd->name, "..") == 0)
199 gchar *dir = remove_level_from_path(path);
201 fd->path = remove_level_from_path(dir);
204 fd->extension = fd->name + 2;
205 file_data_set_collate_keys(fd);
208 else if (strcmp(fd->name, ".") == 0)
211 fd->path = remove_level_from_path(path);
213 fd->extension = fd->name + 1;
214 file_data_set_collate_keys(fd);
218 fd->extension = extension_from_path(fd->path);
219 if (fd->extension == NULL)
220 fd->extension = fd->name + strlen(fd->name);
222 file_data_set_collate_keys(fd);
225 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
227 gboolean ret = FALSE;
230 if (fd->size != st->st_size ||
231 fd->date != st->st_mtime)
233 fd->size = st->st_size;
234 fd->date = st->st_mtime;
235 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
236 fd->thumb_pixbuf = NULL;
237 file_data_increment_version(fd);
238 file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
242 work = fd->sidecar_files;
245 FileData *sfd = work->data;
249 if (!stat_utf8(sfd->path, &st))
253 file_data_disconnect_sidecar_file(fd, sfd);
258 ret |= file_data_check_changed_files_recursive(sfd, &st);
264 gboolean file_data_check_changed_files(FileData *fd)
266 gboolean ret = FALSE;
269 if (fd->parent) fd = fd->parent;
271 if (!stat_utf8(fd->path, &st))
274 FileData *sfd = NULL;
276 /* parent is missing, we have to rebuild whole group */
281 work = fd->sidecar_files;
287 file_data_disconnect_sidecar_file(fd, sfd);
289 if (sfd) file_data_check_sidecars(sfd); /* this will group the sidecars back together */
290 file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
294 ret |= file_data_check_changed_files_recursive(fd, &st);
300 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean check_sidecars)
304 DEBUG_2("file_data_new: '%s' %d", path_utf8, check_sidecars);
307 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
309 if ((fd = g_hash_table_lookup(file_data_pool, path_utf8)))
314 if (!fd && file_data_planned_change_hash)
316 if ((fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8)))
318 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
320 file_data_apply_ci(fd);
329 changed = file_data_check_changed_files(fd);
331 changed = file_data_check_changed_files_recursive(fd, st);
332 if (changed && check_sidecars && sidecar_file_priority(fd->extension))
333 file_data_check_sidecars(fd);
334 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
339 fd = g_new0(FileData, 1);
343 fd->collate_key_name = NULL;
344 fd->collate_key_name_nocase = NULL;
345 fd->original_path = NULL;
347 fd->size = st->st_size;
348 fd->date = st->st_mtime;
349 fd->thumb_pixbuf = NULL;
350 fd->sidecar_files = NULL;
352 fd->magick = 0x12345678;
354 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
357 file_data_check_sidecars(fd);
362 static void file_data_check_sidecars(FileData *fd)
366 FileData *parent_fd = NULL;
369 if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
372 base_len = fd->extension - fd->path;
373 fname = g_string_new_len(fd->path, base_len);
374 work = sidecar_ext_get_list();
378 /* check for possible sidecar files;
379 the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
380 they have fd->ref set to 0 and file_data unref must chack and free them all together
381 (using fd->ref would cause loops and leaks)
385 gchar *ext = work->data;
389 if (strcmp(ext, fd->extension) == 0)
391 new_fd = fd; /* processing the original file */
396 g_string_truncate(fname, base_len);
397 g_string_append(fname, ext);
399 if (!stat_utf8(fname->str, &nst))
402 new_fd = file_data_new(fname->str, &nst, FALSE);
404 if (new_fd->disable_grouping)
406 file_data_unref(new_fd);
410 new_fd->ref--; /* do not use ref here */
414 parent_fd = new_fd; /* parent is the one with the highest prio, found first */
416 file_data_merge_sidecar_files(parent_fd, new_fd);
418 g_string_free(fname, TRUE);
422 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars)
424 gchar *path_utf8 = path_to_utf8(path);
425 FileData *ret = file_data_new(path_utf8, st, check_sidecars);
431 FileData *file_data_new_simple(const gchar *path_utf8)
435 if (!stat_utf8(path_utf8, &st))
441 return file_data_new(path_utf8, &st, TRUE);
444 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
446 sfd->parent = target;
447 if (!g_list_find(target->sidecar_files, sfd))
448 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
449 file_data_increment_version(sfd); /* increments both sfd and target */
454 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
458 file_data_add_sidecar_file(target, source);
460 work = source->sidecar_files;
463 FileData *sfd = work->data;
464 file_data_add_sidecar_file(target, sfd);
468 g_list_free(source->sidecar_files);
469 source->sidecar_files = NULL;
471 target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
476 #ifdef DEBUG_FILEDATA
477 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
479 FileData *file_data_ref(FileData *fd)
482 if (fd == NULL) return NULL;
483 #ifdef DEBUG_FILEDATA
484 if (fd->magick != 0x12345678)
485 DEBUG_0("fd magick mismatch at %s:%d", file, line);
487 g_assert(fd->magick == 0x12345678);
490 #ifdef DEBUG_FILEDATA
491 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
493 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
498 static void file_data_free(FileData *fd)
500 g_assert(fd->magick == 0x12345678);
501 g_assert(fd->ref == 0);
503 g_hash_table_remove(file_data_pool, fd->original_path);
506 g_free(fd->original_path);
507 g_free(fd->collate_key_name);
508 g_free(fd->collate_key_name_nocase);
509 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
511 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
513 file_data_change_info_free(NULL, fd);
517 #ifdef DEBUG_FILEDATA
518 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
520 void file_data_unref(FileData *fd)
523 if (fd == NULL) return;
524 #ifdef DEBUG_FILEDATA
525 if (fd->magick != 0x12345678)
526 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
528 g_assert(fd->magick == 0x12345678);
531 #ifdef DEBUG_FILEDATA
532 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
535 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
540 FileData *parent = fd->parent ? fd->parent : fd;
545 work = parent->sidecar_files;
548 FileData *sfd = work->data;
554 /* none of parent/children is referenced, we can free everything */
556 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
558 work = parent->sidecar_files;
561 FileData *sfd = work->data;
566 g_list_free(parent->sidecar_files);
567 parent->sidecar_files = NULL;
569 file_data_free(parent);
573 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
575 sfd->parent = target;
576 g_assert(g_list_find(target->sidecar_files, sfd));
578 file_data_increment_version(sfd); /* increments both sfd and target */
580 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
592 /* disables / enables grouping for particular file, sends UPDATE notification */
593 void file_data_disable_grouping(FileData *fd, gboolean disable)
595 if (!fd->disable_grouping == !disable) return;
596 fd->disable_grouping = !!disable;
602 FileData *parent = file_data_ref(fd->parent);
603 file_data_disconnect_sidecar_file(parent, fd);
604 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
605 file_data_send_notification(parent, NOTIFY_TYPE_INTERNAL);
606 file_data_unref(parent);
608 else if (fd->sidecar_files)
610 GList *sidecar_files = filelist_copy(fd->sidecar_files);
611 GList *work = sidecar_files;
614 FileData *sfd = work->data;
616 file_data_disconnect_sidecar_file(fd, sfd);
617 file_data_send_notification(sfd, NOTIFY_TYPE_INTERNAL);
619 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
620 file_data_check_sidecars((FileData *)sidecar_files->data); /* this will group the sidecars back together */
621 filelist_free(sidecar_files);
626 file_data_check_sidecars(fd);
627 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
631 /* compare name without extension */
632 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
634 size_t len1 = fd1->extension - fd1->name;
635 size_t len2 = fd2->extension - fd2->name;
637 if (len1 < len2) return -1;
638 if (len1 > len2) return 1;
640 return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
643 gboolean file_data_add_change_info(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
645 FileDataChangeInfo *fdci;
647 if (fd->change) return FALSE;
649 fdci = g_new0(FileDataChangeInfo, 1);
653 fdci->source = g_strdup(src);
655 fdci->source = g_strdup(fd->path);
658 fdci->dest = g_strdup(dest);
665 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
673 g_free(fdci->source);
686 *-----------------------------------------------------------------------------
687 * sidecar file info struct
688 *-----------------------------------------------------------------------------
693 static gint sidecar_file_priority(const gchar *path)
695 const char *extension = extension_from_path(path);
699 if (extension == NULL)
702 work = sidecar_ext_get_list();
705 gchar *ext = work->data;
708 if (strcmp(extension, ext) == 0) return i;
716 *-----------------------------------------------------------------------------
718 *-----------------------------------------------------------------------------
721 static SortType filelist_sort_method = SORT_NONE;
722 static gint filelist_sort_ascend = TRUE;
725 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
727 if (!filelist_sort_ascend)
734 switch (filelist_sort_method)
739 if (fa->size < fb->size) return -1;
740 if (fa->size > fb->size) return 1;
741 /* fall back to name */
744 if (fa->date < fb->date) return -1;
745 if (fa->date > fb->date) return 1;
746 /* fall back to name */
748 #ifdef HAVE_STRVERSCMP
750 return strverscmp(fa->name, fb->name);
757 if (options->file_sort.case_sensitive)
758 return strcmp(fa->collate_key_name, fb->collate_key_name);
760 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
763 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gint ascend)
765 filelist_sort_method = method;
766 filelist_sort_ascend = ascend;
767 return filelist_sort_compare_filedata(fa, fb);
770 static gint filelist_sort_file_cb(void *a, void *b)
772 return filelist_sort_compare_filedata(a, b);
775 GList *filelist_sort_full(GList *list, SortType method, gint ascend, GCompareFunc cb)
777 filelist_sort_method = method;
778 filelist_sort_ascend = ascend;
779 return g_list_sort(list, cb);
782 GList *filelist_insert_sort_full(GList *list, void *data, SortType method, gint ascend, GCompareFunc cb)
784 filelist_sort_method = method;
785 filelist_sort_ascend = ascend;
786 return g_list_insert_sorted(list, data, cb);
789 GList *filelist_sort(GList *list, SortType method, gint ascend)
791 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
794 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gint ascend)
796 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
800 static GList *filelist_filter_out_sidecars(GList *flist)
803 GList *flist_filtered = NULL;
807 FileData *fd = work->data;
810 if (fd->parent) /* remove fd's that are children */
813 flist_filtered = g_list_prepend(flist_filtered, fd);
817 return flist_filtered;
820 static gint filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gint follow_symlinks)
827 int (*stat_func)(const char *path, struct stat *buf);
829 g_assert(files || dirs);
831 if (files) *files = NULL;
832 if (dirs) *dirs = NULL;
834 pathl = path_from_utf8(dir_fd->path);
835 if (!pathl) return FALSE;
849 while ((dir = readdir(dp)) != NULL)
851 struct stat ent_sbuf;
852 const gchar *name = dir->d_name;
855 if (!options->file_filter.show_hidden_files && ishidden(name))
858 filepath = g_build_filename(pathl, name, NULL);
859 if (stat_func(filepath, &ent_sbuf) >= 0)
861 if (S_ISDIR(ent_sbuf.st_mode))
863 /* we ignore the .thumbnails dir for cleanliness */
865 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
866 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
867 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
868 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
870 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE));
875 if (files && filter_name_exists(name))
877 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE));
888 if (dirs) *dirs = dlist;
889 if (files) *files = filelist_filter_out_sidecars(flist);
894 gint filelist_read(FileData *dir_fd, GList **files, GList **dirs)
896 return filelist_read_real(dir_fd, files, dirs, TRUE);
899 gint filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
901 return filelist_read_real(dir_fd, files, dirs, FALSE);
904 void filelist_free(GList *list)
911 file_data_unref((FileData *)work->data);
919 GList *filelist_copy(GList *list)
921 GList *new_list = NULL;
932 new_list = g_list_prepend(new_list, file_data_ref(fd));
935 return g_list_reverse(new_list);
938 GList *filelist_from_path_list(GList *list)
940 GList *new_list = NULL;
951 new_list = g_list_prepend(new_list, file_data_new_simple(path));
954 return g_list_reverse(new_list);
957 GList *filelist_to_path_list(GList *list)
959 GList *new_list = NULL;
970 new_list = g_list_prepend(new_list, g_strdup(fd->path));
973 return g_list_reverse(new_list);
976 GList *filelist_filter(GList *list, gint is_dir_list)
980 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
985 FileData *fd = (FileData *)(work->data);
986 const gchar *name = fd->name;
988 if ((!options->file_filter.show_hidden_files && ishidden(name)) ||
989 (!is_dir_list && !filter_name_exists(name)) ||
990 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
991 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
995 list = g_list_remove_link(list, link);
1007 *-----------------------------------------------------------------------------
1008 * filelist recursive
1009 *-----------------------------------------------------------------------------
1012 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1014 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1017 GList *filelist_sort_path(GList *list)
1019 return g_list_sort(list, filelist_sort_path_cb);
1022 static void filelist_recursive_append(GList **list, GList *dirs)
1029 FileData *fd = (FileData *)(work->data);
1033 if (filelist_read(fd, &f, &d))
1035 f = filelist_filter(f, FALSE);
1036 f = filelist_sort_path(f);
1037 *list = g_list_concat(*list, f);
1039 d = filelist_filter(d, TRUE);
1040 d = filelist_sort_path(d);
1041 filelist_recursive_append(list, d);
1049 GList *filelist_recursive(FileData *dir_fd)
1054 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1055 list = filelist_filter(list, FALSE);
1056 list = filelist_sort_path(list);
1058 d = filelist_filter(d, TRUE);
1059 d = filelist_sort_path(d);
1060 filelist_recursive_append(&list, d);
1068 * marks and orientation
1072 gboolean file_data_get_mark(FileData *fd, gint n)
1074 return !!(fd->marks & (1 << n));
1077 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1079 if (!value == !(fd->marks & (1 << n))) return;
1081 fd->marks = fd->marks ^ (1 << n);
1082 file_data_increment_version(fd);
1083 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1086 gint file_data_get_user_orientation(FileData *fd)
1088 return fd->user_orientation;
1091 void file_data_set_user_orientation(FileData *fd, gint value)
1093 if (fd->user_orientation == value) return;
1095 fd->user_orientation = value;
1096 file_data_increment_version(fd);
1097 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1103 * file_data - operates on the given fd
1104 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1108 /* return list of sidecar file extensions in a string */
1109 gchar *file_data_sc_list_to_string(FileData *fd)
1112 GString *result = g_string_new("");
1114 work = fd->sidecar_files;
1117 FileData *sfd = work->data;
1119 result = g_string_append(result, "+ ");
1120 result = g_string_append(result, sfd->extension);
1122 if (work) result = g_string_append_c(result, ' ');
1125 return g_string_free(result, FALSE);
1131 * add FileDataChangeInfo (see typedefs.h) for the given operation
1132 * uses file_data_add_change_info
1134 * fails if the fd->change already exists - change operations can't run in parallel
1135 * fd->change_info works as a lock
1137 * dest can be NULL - in this case the current name is used for now, it will
1142 FileDataChangeInfo types:
1144 MOVE - patch is changed, name may be changed too
1145 RENAME - path remains unchanged, name is changed
1146 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1147 sidecar names are changed too, extensions are not changed
1149 UPDATE - file size, date or grouping has been changed
1152 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1154 FileDataChangeInfo *fdci;
1156 if (fd->change) return FALSE;
1158 fdci = g_new0(FileDataChangeInfo, 1);
1163 fdci->source = g_strdup(src);
1165 fdci->source = g_strdup(fd->path);
1168 fdci->dest = g_strdup(dest);
1175 void file_data_free_ci(FileData *fd)
1177 FileDataChangeInfo *fdci = fd->change;
1182 g_free(fdci->source);
1191 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1195 if (fd->parent) fd = fd->parent;
1197 if (fd->change) return FALSE;
1199 work = fd->sidecar_files;
1202 FileData *sfd = work->data;
1204 if (sfd->change) return FALSE;
1208 file_data_add_ci(fd, type, NULL, NULL);
1210 work = fd->sidecar_files;
1213 FileData *sfd = work->data;
1215 file_data_add_ci(sfd, type, NULL, NULL);
1222 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1226 if (fd->parent) fd = fd->parent;
1228 if (!fd->change || fd->change->type != type) return FALSE;
1230 work = fd->sidecar_files;
1233 FileData *sfd = work->data;
1235 if (!sfd->change || sfd->change->type != type) return FALSE;
1243 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1245 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1246 file_data_sc_update_ci_copy(fd, dest_path);
1250 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1252 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1253 file_data_sc_update_ci_move(fd, dest_path);
1257 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1259 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1260 file_data_sc_update_ci_rename(fd, dest_path);
1264 gboolean file_data_sc_add_ci_delete(FileData *fd)
1266 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1269 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1271 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1272 file_data_sc_update_ci_unspecified(fd, dest_path);
1276 void file_data_sc_free_ci(FileData *fd)
1280 if (fd->parent) fd = fd->parent;
1282 file_data_free_ci(fd);
1284 work = fd->sidecar_files;
1287 FileData *sfd = work->data;
1289 file_data_free_ci(sfd);
1294 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1297 gboolean ret = TRUE;
1302 FileData *fd = work->data;
1304 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1311 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1314 gboolean ret = TRUE;
1319 FileData *fd = work->data;
1321 if (!file_data_sc_add_ci_copy(fd, dest)) ret = FALSE;
1328 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1331 gboolean ret = TRUE;
1336 FileData *fd = work->data;
1338 if (!file_data_sc_add_ci_move(fd, dest)) ret = FALSE;
1345 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1348 gboolean ret = TRUE;
1353 FileData *fd = work->data;
1355 if (!file_data_sc_add_ci_rename(fd, dest)) ret = FALSE;
1362 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1365 gboolean ret = TRUE;
1370 FileData *fd = work->data;
1372 if (!file_data_sc_add_ci_unspecified(fd, dest)) ret = FALSE;
1379 void file_data_sc_free_ci_list(GList *fd_list)
1386 FileData *fd = work->data;
1388 file_data_sc_free_ci(fd);
1394 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1395 * fails if fd->change does not exist or the change type does not match
1398 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1400 FileDataChangeType type = fd->change->type;
1402 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1406 if (!file_data_planned_change_hash)
1407 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1409 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1411 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1412 g_hash_table_remove(file_data_planned_change_hash, old_path);
1413 file_data_unref(fd);
1416 if ((ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path)) != fd)
1420 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1421 g_hash_table_remove(file_data_planned_change_hash, new_path);
1422 file_data_unref(ofd);
1425 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1427 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1432 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1434 gchar *old_path = fd->change->dest;
1435 fd->change->dest = g_strdup(dest_path);
1436 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1440 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1442 const char *extension = extension_from_path(fd->change->source);
1443 gchar *base = remove_extension_from_path(dest_path);
1444 gchar *old_path = fd->change->dest;
1446 fd->change->dest = g_strdup_printf("%s%s", base, extension);
1447 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1453 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1456 gchar *dest_path_full = NULL;
1458 if (fd->parent) fd = fd->parent;
1460 if (!dest_path) dest_path = fd->path;
1462 if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1464 gchar *dir = remove_level_from_path(fd->path);
1466 dest_path_full = g_build_filename(dir, dest_path, NULL);
1468 dest_path = dest_path_full;
1471 if (isdir(dest_path))
1473 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1474 dest_path = dest_path_full;
1477 file_data_update_ci_dest(fd, dest_path);
1479 work = fd->sidecar_files;
1482 FileData *sfd = work->data;
1484 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1488 g_free(dest_path_full);
1491 gint file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1493 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1494 file_data_sc_update_ci(fd, dest_path);
1498 gint file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1500 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1501 file_data_sc_update_ci(fd, dest_path);
1505 gint file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1507 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1508 file_data_sc_update_ci(fd, dest_path);
1512 gint file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1514 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1515 file_data_sc_update_ci(fd, dest_path);
1520 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1523 gboolean ret = TRUE;
1528 FileData *fd = work->data;
1530 if (!file_data_sc_update_ci_move(fd, dest)) ret = FALSE;
1537 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1540 gboolean ret = TRUE;
1545 FileData *fd = work->data;
1547 if (!file_data_sc_update_ci_copy(fd, dest)) ret = FALSE;
1554 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1557 gboolean ret = TRUE;
1562 FileData *fd = work->data;
1564 if (!file_data_sc_update_ci_unspecified(fd, dest)) ret = FALSE;
1573 * check dest paths - dest image exists, etc.
1575 * it should detect all possible problems with the planned operation
1578 gint file_data_sc_check_ci_dest(FileData *fd)
1586 * perform the change described by FileFataChangeInfo
1587 * it is used for internal operations,
1588 * this function actually operates with files on the filesystem
1589 * it should implement safe delete
1592 static gboolean file_data_perform_move(FileData *fd)
1594 g_assert(!strcmp(fd->change->source, fd->path));
1595 return move_file(fd->change->source, fd->change->dest);
1598 static gboolean file_data_perform_copy(FileData *fd)
1600 g_assert(!strcmp(fd->change->source, fd->path));
1601 return copy_file(fd->change->source, fd->change->dest);
1604 static gboolean file_data_perform_delete(FileData *fd)
1606 if (isdir(fd->path) && !islink(fd->path))
1607 return rmdir_utf8(fd->path);
1609 return unlink_file(fd->path);
1612 static gboolean file_data_perform_ci(FileData *fd)
1614 FileDataChangeType type = fd->change->type;
1617 case FILEDATA_CHANGE_MOVE:
1618 return file_data_perform_move(fd);
1619 case FILEDATA_CHANGE_COPY:
1620 return file_data_perform_copy(fd);
1621 case FILEDATA_CHANGE_RENAME:
1622 return file_data_perform_move(fd); /* the same as move */
1623 case FILEDATA_CHANGE_DELETE:
1624 return file_data_perform_delete(fd);
1625 case FILEDATA_CHANGE_UNSPECIFIED:
1626 /* nothing to do here */
1634 gboolean file_data_sc_perform_ci(FileData *fd)
1637 gboolean ret = TRUE;
1638 FileDataChangeType type = fd->change->type;
1640 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1642 work = fd->sidecar_files;
1645 FileData *sfd = work->data;
1647 if (!file_data_perform_ci(sfd)) ret = FALSE;
1651 if (!file_data_perform_ci(fd)) ret = FALSE;
1657 * updates FileData structure according to FileDataChangeInfo
1660 static void file_data_apply_ci(FileData *fd)
1662 FileDataChangeType type = fd->change->type;
1665 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1667 if (file_data_planned_change_hash)
1669 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1671 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
1672 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1673 file_data_unref(fd);
1677 file_data_set_path(fd, fd->change->dest);
1679 file_data_increment_version(fd);
1680 file_data_send_notification(fd, NOTIFY_TYPE_CHANGE);
1683 gint file_data_sc_apply_ci(FileData *fd)
1686 FileDataChangeType type = fd->change->type;
1688 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1690 work = fd->sidecar_files;
1693 FileData *sfd = work->data;
1695 file_data_apply_ci(sfd);
1699 file_data_apply_ci(fd);
1705 * notify other modules about the change described by FileFataChangeInfo
1708 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
1709 FIXME do we need the ignore_list? It looks like a workaround for ineffective
1710 implementation in view_file_list.c */
1715 typedef struct _NotifyData NotifyData;
1717 struct _NotifyData {
1718 FileDataNotifyFunc func;
1720 NotifyPriority priority;
1723 static GList *notify_func_list = NULL;
1725 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
1727 NotifyData *nda = (NotifyData *)a;
1728 NotifyData *ndb = (NotifyData *)b;
1730 if (nda->priority < ndb->priority) return -1;
1731 if (nda->priority > ndb->priority) return 1;
1735 gint file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
1739 nd = g_new(NotifyData, 1);
1742 nd->priority = priority;
1744 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
1745 DEBUG_1("Notify func registered: %p", nd);
1750 gint file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
1752 GList *work = notify_func_list;
1756 NotifyData *nd = (NotifyData *)work->data;
1758 if (nd->func == func && nd->data == data)
1760 notify_func_list = g_list_delete_link(notify_func_list, work);
1762 DEBUG_1("Notify func unregistered: %p", nd);
1772 void file_data_send_notification(FileData *fd, NotifyType type)
1774 GList *work = notify_func_list;
1778 NotifyData *nd = (NotifyData *)work->data;
1780 DEBUG_1("Notify func calling: %p %s", nd, fd->path);
1781 nd->func(fd, type, nd->data);
1786 static GHashTable *file_data_monitor_pool = NULL;
1787 static gint realtime_monitor_id = -1;
1789 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
1793 file_data_check_changed_files(fd);
1795 DEBUG_1("monitor %s", fd->path);
1798 static gboolean realtime_monitor_cb(gpointer data)
1800 if (!options->update_on_time_change) return TRUE;
1801 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
1805 gint file_data_register_real_time_monitor(FileData *fd)
1811 if (!file_data_monitor_pool)
1812 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
1814 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
1816 DEBUG_1("Register realtime %d %s", count, fd->path);
1819 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
1821 if (realtime_monitor_id == -1)
1823 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
1829 gint file_data_unregister_real_time_monitor(FileData *fd)
1833 g_assert(file_data_monitor_pool);
1835 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
1837 DEBUG_1("Unregister realtime %d %s", count, fd->path);
1839 g_assert(count > 0);
1844 g_hash_table_remove(file_data_monitor_pool, fd);
1846 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
1848 file_data_unref(fd);
1850 if (g_hash_table_size(file_data_monitor_pool) == 0)
1852 g_source_remove(realtime_monitor_id);
1853 realtime_monitor_id = -1;