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 gboolean file_data_add_change_info(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
650 FileDataChangeInfo *fdci;
652 if (fd->change) return FALSE;
654 fdci = g_new0(FileDataChangeInfo, 1);
658 fdci->source = g_strdup(src);
660 fdci->source = g_strdup(fd->path);
663 fdci->dest = g_strdup(dest);
670 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
678 g_free(fdci->source);
691 *-----------------------------------------------------------------------------
692 * sidecar file info struct
693 *-----------------------------------------------------------------------------
698 static gint sidecar_file_priority(const gchar *path)
700 const char *extension = extension_from_path(path);
704 if (extension == NULL)
707 work = sidecar_ext_get_list();
710 gchar *ext = work->data;
713 if (strcmp(extension, ext) == 0) return i;
721 *-----------------------------------------------------------------------------
723 *-----------------------------------------------------------------------------
726 static SortType filelist_sort_method = SORT_NONE;
727 static gint filelist_sort_ascend = TRUE;
730 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
732 if (!filelist_sort_ascend)
739 switch (filelist_sort_method)
744 if (fa->size < fb->size) return -1;
745 if (fa->size > fb->size) return 1;
746 /* fall back to name */
749 if (fa->date < fb->date) return -1;
750 if (fa->date > fb->date) return 1;
751 /* fall back to name */
753 #ifdef HAVE_STRVERSCMP
755 return strverscmp(fa->name, fb->name);
762 if (options->file_sort.case_sensitive)
763 return strcmp(fa->collate_key_name, fb->collate_key_name);
765 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
768 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gint ascend)
770 filelist_sort_method = method;
771 filelist_sort_ascend = ascend;
772 return filelist_sort_compare_filedata(fa, fb);
775 static gint filelist_sort_file_cb(void *a, void *b)
777 return filelist_sort_compare_filedata(a, b);
780 GList *filelist_sort_full(GList *list, SortType method, gint ascend, GCompareFunc cb)
782 filelist_sort_method = method;
783 filelist_sort_ascend = ascend;
784 return g_list_sort(list, cb);
787 GList *filelist_insert_sort_full(GList *list, void *data, SortType method, gint ascend, GCompareFunc cb)
789 filelist_sort_method = method;
790 filelist_sort_ascend = ascend;
791 return g_list_insert_sorted(list, data, cb);
794 GList *filelist_sort(GList *list, SortType method, gint ascend)
796 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
799 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gint ascend)
801 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
805 static GList *filelist_filter_out_sidecars(GList *flist)
808 GList *flist_filtered = NULL;
812 FileData *fd = work->data;
815 if (fd->parent) /* remove fd's that are children */
818 flist_filtered = g_list_prepend(flist_filtered, fd);
822 return flist_filtered;
825 static gint filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gint follow_symlinks)
832 int (*stat_func)(const char *path, struct stat *buf);
834 g_assert(files || dirs);
836 if (files) *files = NULL;
837 if (dirs) *dirs = NULL;
839 pathl = path_from_utf8(dir_fd->path);
840 if (!pathl) return FALSE;
854 while ((dir = readdir(dp)) != NULL)
856 struct stat ent_sbuf;
857 const gchar *name = dir->d_name;
860 if (!options->file_filter.show_hidden_files && ishidden(name))
863 filepath = g_build_filename(pathl, name, NULL);
864 if (stat_func(filepath, &ent_sbuf) >= 0)
866 if (S_ISDIR(ent_sbuf.st_mode))
868 /* we ignore the .thumbnails dir for cleanliness */
870 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
871 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
872 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
873 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
875 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE));
880 if (files && filter_name_exists(name))
882 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE));
893 if (dirs) *dirs = dlist;
894 if (files) *files = filelist_filter_out_sidecars(flist);
899 gint filelist_read(FileData *dir_fd, GList **files, GList **dirs)
901 return filelist_read_real(dir_fd, files, dirs, TRUE);
904 gint filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
906 return filelist_read_real(dir_fd, files, dirs, FALSE);
909 void filelist_free(GList *list)
916 file_data_unref((FileData *)work->data);
924 GList *filelist_copy(GList *list)
926 GList *new_list = NULL;
937 new_list = g_list_prepend(new_list, file_data_ref(fd));
940 return g_list_reverse(new_list);
943 GList *filelist_from_path_list(GList *list)
945 GList *new_list = NULL;
956 new_list = g_list_prepend(new_list, file_data_new_simple(path));
959 return g_list_reverse(new_list);
962 GList *filelist_to_path_list(GList *list)
964 GList *new_list = NULL;
975 new_list = g_list_prepend(new_list, g_strdup(fd->path));
978 return g_list_reverse(new_list);
981 GList *filelist_filter(GList *list, gint is_dir_list)
985 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
990 FileData *fd = (FileData *)(work->data);
991 const gchar *name = fd->name;
993 if ((!options->file_filter.show_hidden_files && ishidden(name)) ||
994 (!is_dir_list && !filter_name_exists(name)) ||
995 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
996 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1000 list = g_list_remove_link(list, link);
1001 file_data_unref(fd);
1012 *-----------------------------------------------------------------------------
1013 * filelist recursive
1014 *-----------------------------------------------------------------------------
1017 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1019 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1022 GList *filelist_sort_path(GList *list)
1024 return g_list_sort(list, filelist_sort_path_cb);
1027 static void filelist_recursive_append(GList **list, GList *dirs)
1034 FileData *fd = (FileData *)(work->data);
1038 if (filelist_read(fd, &f, &d))
1040 f = filelist_filter(f, FALSE);
1041 f = filelist_sort_path(f);
1042 *list = g_list_concat(*list, f);
1044 d = filelist_filter(d, TRUE);
1045 d = filelist_sort_path(d);
1046 filelist_recursive_append(list, d);
1054 GList *filelist_recursive(FileData *dir_fd)
1059 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1060 list = filelist_filter(list, FALSE);
1061 list = filelist_sort_path(list);
1063 d = filelist_filter(d, TRUE);
1064 d = filelist_sort_path(d);
1065 filelist_recursive_append(&list, d);
1073 * marks and orientation
1077 gboolean file_data_get_mark(FileData *fd, gint n)
1079 return !!(fd->marks & (1 << n));
1082 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1084 if (!value == !(fd->marks & (1 << n))) return;
1086 fd->marks = fd->marks ^ (1 << n);
1087 file_data_increment_version(fd);
1088 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1091 gint file_data_get_user_orientation(FileData *fd)
1093 return fd->user_orientation;
1096 void file_data_set_user_orientation(FileData *fd, gint value)
1098 if (fd->user_orientation == value) return;
1100 fd->user_orientation = value;
1101 file_data_increment_version(fd);
1102 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1108 * file_data - operates on the given fd
1109 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1113 /* return list of sidecar file extensions in a string */
1114 gchar *file_data_sc_list_to_string(FileData *fd)
1117 GString *result = g_string_new("");
1119 work = fd->sidecar_files;
1122 FileData *sfd = work->data;
1124 result = g_string_append(result, "+ ");
1125 result = g_string_append(result, sfd->extension);
1127 if (work) result = g_string_append_c(result, ' ');
1130 return g_string_free(result, FALSE);
1136 * add FileDataChangeInfo (see typedefs.h) for the given operation
1137 * uses file_data_add_change_info
1139 * fails if the fd->change already exists - change operations can't run in parallel
1140 * fd->change_info works as a lock
1142 * dest can be NULL - in this case the current name is used for now, it will
1147 FileDataChangeInfo types:
1149 MOVE - patch is changed, name may be changed too
1150 RENAME - path remains unchanged, name is changed
1151 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1152 sidecar names are changed too, extensions are not changed
1154 UPDATE - file size, date or grouping has been changed
1157 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1159 FileDataChangeInfo *fdci;
1161 if (fd->change) return FALSE;
1163 fdci = g_new0(FileDataChangeInfo, 1);
1168 fdci->source = g_strdup(src);
1170 fdci->source = g_strdup(fd->path);
1173 fdci->dest = g_strdup(dest);
1180 static void file_data_planned_change_remove(FileData *fd)
1182 if (file_data_planned_change_hash &&
1183 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1185 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1187 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1188 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1189 file_data_unref(fd);
1190 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1192 g_hash_table_destroy(file_data_planned_change_hash);
1193 file_data_planned_change_hash = NULL;
1194 DEBUG_1("planned change: empty");
1201 void file_data_free_ci(FileData *fd)
1203 FileDataChangeInfo *fdci = fd->change;
1208 file_data_planned_change_remove(fd);
1210 g_free(fdci->source);
1219 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1223 if (fd->parent) fd = fd->parent;
1225 if (fd->change) return FALSE;
1227 work = fd->sidecar_files;
1230 FileData *sfd = work->data;
1232 if (sfd->change) return FALSE;
1236 file_data_add_ci(fd, type, NULL, NULL);
1238 work = fd->sidecar_files;
1241 FileData *sfd = work->data;
1243 file_data_add_ci(sfd, type, NULL, NULL);
1250 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1254 if (fd->parent) fd = fd->parent;
1256 if (!fd->change || fd->change->type != type) return FALSE;
1258 work = fd->sidecar_files;
1261 FileData *sfd = work->data;
1263 if (!sfd->change || sfd->change->type != type) return FALSE;
1271 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1273 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1274 file_data_sc_update_ci_copy(fd, dest_path);
1278 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1280 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1281 file_data_sc_update_ci_move(fd, dest_path);
1285 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1287 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1288 file_data_sc_update_ci_rename(fd, dest_path);
1292 gboolean file_data_sc_add_ci_delete(FileData *fd)
1294 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1297 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1299 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1300 file_data_sc_update_ci_unspecified(fd, dest_path);
1304 void file_data_sc_free_ci(FileData *fd)
1308 if (fd->parent) fd = fd->parent;
1310 file_data_free_ci(fd);
1312 work = fd->sidecar_files;
1315 FileData *sfd = work->data;
1317 file_data_free_ci(sfd);
1322 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1325 gboolean ret = TRUE;
1330 FileData *fd = work->data;
1332 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1339 static void file_data_sc_revert_ci_list(GList *fd_list)
1346 FileData *fd = work->data;
1348 file_data_sc_free_ci(fd);
1354 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1361 FileData *fd = work->data;
1363 if (!file_data_sc_add_ci_copy(fd, dest))
1365 file_data_sc_revert_ci_list(work->prev);
1374 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1381 FileData *fd = work->data;
1383 if (!file_data_sc_add_ci_move(fd, dest))
1385 file_data_sc_revert_ci_list(work->prev);
1394 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1401 FileData *fd = work->data;
1403 if (!file_data_sc_add_ci_rename(fd, dest))
1405 file_data_sc_revert_ci_list(work->prev);
1414 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1421 FileData *fd = work->data;
1423 if (!file_data_sc_add_ci_unspecified(fd, dest))
1425 file_data_sc_revert_ci_list(work->prev);
1434 void file_data_sc_free_ci_list(GList *fd_list)
1441 FileData *fd = work->data;
1443 file_data_sc_free_ci(fd);
1449 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1450 * fails if fd->change does not exist or the change type does not match
1453 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1455 FileDataChangeType type = fd->change->type;
1457 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1461 if (!file_data_planned_change_hash)
1462 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1464 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1466 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1467 g_hash_table_remove(file_data_planned_change_hash, old_path);
1468 file_data_unref(fd);
1471 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1476 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1477 g_hash_table_remove(file_data_planned_change_hash, new_path);
1478 file_data_unref(ofd);
1481 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1483 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1488 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1490 gchar *old_path = fd->change->dest;
1492 fd->change->dest = g_strdup(dest_path);
1493 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1497 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1499 const char *extension = extension_from_path(fd->change->source);
1500 gchar *base = remove_extension_from_path(dest_path);
1501 gchar *old_path = fd->change->dest;
1503 fd->change->dest = g_strconcat(base, extension, NULL);
1504 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1510 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1513 gchar *dest_path_full = NULL;
1515 if (fd->parent) fd = fd->parent;
1517 if (!dest_path) dest_path = fd->path;
1519 if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1521 gchar *dir = remove_level_from_path(fd->path);
1523 dest_path_full = g_build_filename(dir, dest_path, NULL);
1525 dest_path = dest_path_full;
1528 if (isdir(dest_path))
1530 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1531 dest_path = dest_path_full;
1534 file_data_update_ci_dest(fd, dest_path);
1536 work = fd->sidecar_files;
1539 FileData *sfd = work->data;
1541 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1545 g_free(dest_path_full);
1548 gint file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1550 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1551 file_data_sc_update_ci(fd, dest_path);
1555 gint file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1557 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1558 file_data_sc_update_ci(fd, dest_path);
1562 gint file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1564 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1565 file_data_sc_update_ci(fd, dest_path);
1569 gint file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1571 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1572 file_data_sc_update_ci(fd, dest_path);
1577 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1580 gboolean ret = TRUE;
1585 FileData *fd = work->data;
1587 if (!file_data_sc_update_ci_move(fd, dest)) ret = FALSE;
1594 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1597 gboolean ret = TRUE;
1602 FileData *fd = work->data;
1604 if (!file_data_sc_update_ci_copy(fd, dest)) ret = FALSE;
1611 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1614 gboolean ret = TRUE;
1619 FileData *fd = work->data;
1621 if (!file_data_sc_update_ci_unspecified(fd, dest)) ret = FALSE;
1630 * check dest paths - dest image exists, etc.
1631 * it should detect all possible problems with the planned operation
1632 * FIXME: add more tests
1635 gint file_data_check_ci_dest(FileData *fd)
1637 gint ret = CHANGE_OK;
1639 g_assert(fd->change);
1641 if (fd->change->dest &&
1642 strcmp(fd->change->dest, fd->path) != 0 &&
1643 isname(fd->change->dest))
1645 ret |= CHANGE_DEST_EXISTS;
1646 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1649 if (!access_file(fd->path, R_OK))
1651 ret |= CHANGE_NO_PERM;
1652 DEBUG_1("Change checked: no read permission: %s", fd->path);
1655 fd->change->error = ret;
1656 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
1662 gint file_data_sc_check_ci_dest(FileData *fd)
1667 ret = file_data_check_ci_dest(fd);
1669 work = fd->sidecar_files;
1672 FileData *sfd = work->data;
1674 ret |= file_data_check_ci_dest(sfd);
1685 * perform the change described by FileFataChangeInfo
1686 * it is used for internal operations,
1687 * this function actually operates with files on the filesystem
1688 * it should implement safe delete
1691 static gboolean file_data_perform_move(FileData *fd)
1693 g_assert(!strcmp(fd->change->source, fd->path));
1694 return move_file(fd->change->source, fd->change->dest);
1697 static gboolean file_data_perform_copy(FileData *fd)
1699 g_assert(!strcmp(fd->change->source, fd->path));
1700 return copy_file(fd->change->source, fd->change->dest);
1703 static gboolean file_data_perform_delete(FileData *fd)
1705 if (isdir(fd->path) && !islink(fd->path))
1706 return rmdir_utf8(fd->path);
1708 return unlink_file(fd->path);
1711 static gboolean file_data_perform_ci(FileData *fd)
1713 FileDataChangeType type = fd->change->type;
1716 case FILEDATA_CHANGE_MOVE:
1717 return file_data_perform_move(fd);
1718 case FILEDATA_CHANGE_COPY:
1719 return file_data_perform_copy(fd);
1720 case FILEDATA_CHANGE_RENAME:
1721 return file_data_perform_move(fd); /* the same as move */
1722 case FILEDATA_CHANGE_DELETE:
1723 return file_data_perform_delete(fd);
1724 case FILEDATA_CHANGE_UNSPECIFIED:
1725 /* nothing to do here */
1733 gboolean file_data_sc_perform_ci(FileData *fd)
1736 gboolean ret = TRUE;
1737 FileDataChangeType type = fd->change->type;
1739 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1741 work = fd->sidecar_files;
1744 FileData *sfd = work->data;
1746 if (!file_data_perform_ci(sfd)) ret = FALSE;
1750 if (!file_data_perform_ci(fd)) ret = FALSE;
1756 * updates FileData structure according to FileDataChangeInfo
1759 static void file_data_apply_ci(FileData *fd)
1761 FileDataChangeType type = fd->change->type;
1764 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1766 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
1767 file_data_planned_change_remove(fd);
1769 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
1771 /* this change overwrites another file which is already known to other modules
1772 renaming fd would create duplicate FileData structure
1773 the best thing we can do is nothing
1774 FIXME: maybe we could copy stuff like marks
1776 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
1780 file_data_set_path(fd, fd->change->dest);
1783 file_data_increment_version(fd);
1784 file_data_send_notification(fd, NOTIFY_TYPE_CHANGE);
1787 gint file_data_sc_apply_ci(FileData *fd)
1790 FileDataChangeType type = fd->change->type;
1792 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1794 work = fd->sidecar_files;
1797 FileData *sfd = work->data;
1799 file_data_apply_ci(sfd);
1803 file_data_apply_ci(fd);
1809 * notify other modules about the change described by FileFataChangeInfo
1812 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
1813 FIXME do we need the ignore_list? It looks like a workaround for ineffective
1814 implementation in view_file_list.c */
1819 typedef struct _NotifyData NotifyData;
1821 struct _NotifyData {
1822 FileDataNotifyFunc func;
1824 NotifyPriority priority;
1827 static GList *notify_func_list = NULL;
1829 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
1831 NotifyData *nda = (NotifyData *)a;
1832 NotifyData *ndb = (NotifyData *)b;
1834 if (nda->priority < ndb->priority) return -1;
1835 if (nda->priority > ndb->priority) return 1;
1839 gint file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
1843 nd = g_new(NotifyData, 1);
1846 nd->priority = priority;
1848 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
1849 DEBUG_1("Notify func registered: %p", nd);
1854 gint file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
1856 GList *work = notify_func_list;
1860 NotifyData *nd = (NotifyData *)work->data;
1862 if (nd->func == func && nd->data == data)
1864 notify_func_list = g_list_delete_link(notify_func_list, work);
1866 DEBUG_1("Notify func unregistered: %p", nd);
1876 void file_data_send_notification(FileData *fd, NotifyType type)
1878 GList *work = notify_func_list;
1882 NotifyData *nd = (NotifyData *)work->data;
1884 DEBUG_1("Notify func calling: %p %s", nd, fd->path);
1885 nd->func(fd, type, nd->data);
1890 static GHashTable *file_data_monitor_pool = NULL;
1891 static gint realtime_monitor_id = -1;
1893 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
1897 file_data_check_changed_files(fd);
1899 DEBUG_1("monitor %s", fd->path);
1902 static gboolean realtime_monitor_cb(gpointer data)
1904 if (!options->update_on_time_change) return TRUE;
1905 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
1909 gint file_data_register_real_time_monitor(FileData *fd)
1915 if (!file_data_monitor_pool)
1916 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
1918 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
1920 DEBUG_1("Register realtime %d %s", count, fd->path);
1923 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
1925 if (realtime_monitor_id == -1)
1927 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
1933 gint file_data_unregister_real_time_monitor(FileData *fd)
1937 g_assert(file_data_monitor_pool);
1939 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
1941 DEBUG_1("Unregister realtime %d %s", count, fd->path);
1943 g_assert(count > 0);
1948 g_hash_table_remove(file_data_monitor_pool, fd);
1950 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
1952 file_data_unref(fd);
1954 if (g_hash_table_size(file_data_monitor_pool) == 0)
1956 g_source_remove(realtime_monitor_id);
1957 realtime_monitor_id = -1;