4 * Copyright (C) 2008 - 2012 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"
19 #include "thumb_standard.h"
20 #include "ui_fileops.h"
23 #include "histogram.h"
27 static GHashTable *file_data_pool = NULL;
28 static GHashTable *file_data_planned_change_hash = NULL;
30 static gint sidecar_file_priority(const gchar *extension);
31 static void file_data_check_sidecars(const GList *basename_list);
32 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
37 *-----------------------------------------------------------------------------
38 * text conversion utils
39 *-----------------------------------------------------------------------------
42 gchar *text_from_size(gint64 size)
48 /* what I would like to use is printf("%'d", size)
49 * BUT: not supported on every libc :(
53 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
54 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
58 a = g_strdup_printf("%d", (guint)size);
64 b = g_new(gchar, l + n + 1);
89 gchar *text_from_size_abrev(gint64 size)
91 if (size < (gint64)1024)
93 return g_strdup_printf(_("%d bytes"), (gint)size);
95 if (size < (gint64)1048576)
97 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
99 if (size < (gint64)1073741824)
101 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
104 /* to avoid overflowing the gdouble, do division in two steps */
106 return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
109 /* note: returned string is valid until next call to text_from_time() */
110 const gchar *text_from_time(time_t t)
112 static gchar *ret = NULL;
116 GError *error = NULL;
118 btime = localtime(&t);
120 /* the %x warning about 2 digit years is not an error */
121 buflen = strftime(buf, sizeof(buf), "%x %X", btime);
122 if (buflen < 1) return "";
125 ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
128 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
137 *-----------------------------------------------------------------------------
138 * changed files detection and notification
139 *-----------------------------------------------------------------------------
142 void file_data_increment_version(FileData *fd)
148 fd->parent->version++;
149 fd->parent->valid_marks = 0;
153 static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st)
155 if (fd->size != st->st_size ||
156 fd->date != st->st_mtime)
158 fd->size = st->st_size;
159 fd->date = st->st_mtime;
160 fd->mode = st->st_mode;
161 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
162 fd->thumb_pixbuf = NULL;
163 file_data_increment_version(fd);
164 file_data_send_notification(fd, NOTIFY_REREAD);
170 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
172 gboolean ret = FALSE;
175 ret = file_data_check_changed_single_file(fd, st);
177 work = fd->sidecar_files;
180 FileData *sfd = work->data;
184 if (!stat_utf8(sfd->path, &st))
189 file_data_disconnect_sidecar_file(fd, sfd);
191 file_data_increment_version(sfd);
192 file_data_send_notification(sfd, NOTIFY_REREAD);
193 file_data_unref(sfd);
197 ret |= file_data_check_changed_files_recursive(sfd, &st);
203 gboolean file_data_check_changed_files(FileData *fd)
205 gboolean ret = FALSE;
208 if (fd->parent) fd = fd->parent;
210 if (!stat_utf8(fd->path, &st))
214 FileData *sfd = NULL;
216 /* parent is missing, we have to rebuild whole group */
221 /* file_data_disconnect_sidecar_file might delete the file,
222 we have to keep the reference to prevent this */
223 sidecars = filelist_copy(fd->sidecar_files);
231 file_data_disconnect_sidecar_file(fd, sfd);
233 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
234 /* now we can release the sidecars */
235 filelist_free(sidecars);
236 file_data_increment_version(fd);
237 file_data_send_notification(fd, NOTIFY_REREAD);
242 ret |= file_data_check_changed_files_recursive(fd, &st);
249 *-----------------------------------------------------------------------------
250 * file name, extension, sorting, ...
251 *-----------------------------------------------------------------------------
254 static void file_data_set_collate_keys(FileData *fd)
256 gchar *caseless_name;
259 valid_name = g_filename_display_name(fd->name);
260 caseless_name = g_utf8_casefold(valid_name, -1);
262 g_free(fd->collate_key_name);
263 g_free(fd->collate_key_name_nocase);
265 #if 0 && GLIB_CHECK_VERSION(2, 8, 0)
266 fd->collate_key_name = g_utf8_collate_key_for_filename(valid_name, -1);
267 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
269 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
270 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
273 g_free(caseless_name);
276 static void file_data_set_path(FileData *fd, const gchar *path)
278 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
279 g_assert(file_data_pool);
283 if (fd->original_path)
285 g_hash_table_remove(file_data_pool, fd->original_path);
286 g_free(fd->original_path);
289 g_assert(!g_hash_table_lookup(file_data_pool, path));
291 fd->original_path = g_strdup(path);
292 g_hash_table_insert(file_data_pool, fd->original_path, fd);
294 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
296 fd->path = g_strdup(path);
298 fd->extension = fd->name + 1;
299 file_data_set_collate_keys(fd);
303 fd->path = g_strdup(path);
304 fd->name = filename_from_path(fd->path);
306 if (strcmp(fd->name, "..") == 0)
308 gchar *dir = remove_level_from_path(path);
310 fd->path = remove_level_from_path(dir);
313 fd->extension = fd->name + 2;
314 file_data_set_collate_keys(fd);
317 else if (strcmp(fd->name, ".") == 0)
320 fd->path = remove_level_from_path(path);
322 fd->extension = fd->name + 1;
323 file_data_set_collate_keys(fd);
327 fd->extension = registered_extension_from_path(fd->path);
328 if (fd->extension == NULL)
330 fd->extension = fd->name + strlen(fd->name);
333 fd->sidecar_priority = sidecar_file_priority(fd->extension);
334 file_data_set_collate_keys(fd);
338 *-----------------------------------------------------------------------------
339 * create or reuse Filedata
340 *-----------------------------------------------------------------------------
343 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
347 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
349 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
352 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
354 fd = g_hash_table_lookup(file_data_pool, path_utf8);
360 if (!fd && file_data_planned_change_hash)
362 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
365 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
367 file_data_apply_ci(fd);
375 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
378 changed = file_data_check_changed_single_file(fd, st);
380 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
385 fd = g_new0(FileData, 1);
387 fd->size = st->st_size;
388 fd->date = st->st_mtime;
389 fd->mode = st->st_mode;
391 fd->magick = 0x12345678;
393 if (disable_sidecars) fd->disable_grouping = TRUE;
395 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
400 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
402 gchar *path_utf8 = path_to_utf8(path);
403 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
409 FileData *file_data_new_no_grouping(const gchar *path_utf8)
413 if (!stat_utf8(path_utf8, &st))
419 return file_data_new(path_utf8, &st, TRUE);
422 FileData *file_data_new_dir(const gchar *path_utf8)
426 if (!stat_utf8(path_utf8, &st))
432 /* dir or non-existing yet */
433 g_assert(S_ISDIR(st.st_mode));
435 return file_data_new(path_utf8, &st, TRUE);
439 *-----------------------------------------------------------------------------
441 *-----------------------------------------------------------------------------
444 #ifdef DEBUG_FILEDATA
445 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
447 FileData *file_data_ref(FileData *fd)
450 if (fd == NULL) return NULL;
451 #ifdef DEBUG_FILEDATA
452 if (fd->magick != 0x12345678)
453 DEBUG_0("fd magick mismatch at %s:%d", file, line);
455 g_assert(fd->magick == 0x12345678);
458 #ifdef DEBUG_FILEDATA
459 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
461 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
466 static void file_data_free(FileData *fd)
468 g_assert(fd->magick == 0x12345678);
469 g_assert(fd->ref == 0);
471 metadata_cache_free(fd);
472 g_hash_table_remove(file_data_pool, fd->original_path);
475 g_free(fd->original_path);
476 g_free(fd->collate_key_name);
477 g_free(fd->collate_key_name_nocase);
478 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
479 histmap_free(fd->histmap);
481 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
483 file_data_change_info_free(NULL, fd);
487 #ifdef DEBUG_FILEDATA
488 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
490 void file_data_unref(FileData *fd)
493 if (fd == NULL) return;
494 #ifdef DEBUG_FILEDATA
495 if (fd->magick != 0x12345678)
496 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
498 g_assert(fd->magick == 0x12345678);
501 #ifdef DEBUG_FILEDATA
502 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
504 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
509 FileData *parent = fd->parent ? fd->parent : fd;
511 if (parent->ref > 0) return;
513 work = parent->sidecar_files;
516 FileData *sfd = work->data;
517 if (sfd->ref > 0) return;
521 /* none of parent/children is referenced, we can free everything */
523 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
525 work = parent->sidecar_files;
528 FileData *sfd = work->data;
533 g_list_free(parent->sidecar_files);
534 parent->sidecar_files = NULL;
536 file_data_free(parent);
543 *-----------------------------------------------------------------------------
544 * sidecar file info struct
545 *-----------------------------------------------------------------------------
548 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
550 const FileData *fda = a;
551 const FileData *fdb = b;
553 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
554 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
556 return strcmp(fdb->extension, fda->extension);
560 static gint sidecar_file_priority(const gchar *extension)
565 if (extension == NULL)
568 work = sidecar_ext_get_list();
571 gchar *ext = work->data;
574 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
580 static void file_data_check_sidecars(const GList *basename_list)
582 /* basename_list contains the new group - first is the parent, then sorted sidecars */
583 /* all files in the list have ref count > 0 */
586 GList *s_work, *new_sidecars;
589 if (!basename_list) return;
592 DEBUG_2("basename start");
593 work = basename_list;
596 FileData *fd = work->data;
598 g_assert(fd->magick == 0x12345678);
599 DEBUG_2("basename: %p %s", fd, fd->name);
602 g_assert(fd->parent->magick == 0x12345678);
603 DEBUG_2(" parent: %p", fd->parent);
605 s_work = fd->sidecar_files;
608 FileData *sfd = s_work->data;
609 s_work = s_work->next;
610 g_assert(sfd->magick == 0x12345678);
611 DEBUG_2(" sidecar: %p %s", sfd, sfd->name);
614 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
617 parent_fd = basename_list->data;
619 /* check if the second and next entries of basename_list are already connected
620 as sidecars of the first entry (parent_fd) */
621 work = basename_list->next;
622 s_work = parent_fd->sidecar_files;
624 while (work && s_work)
626 if (work->data != s_work->data) break;
628 s_work = s_work->next;
631 if (!work && !s_work)
633 DEBUG_2("basename no change");
634 return; /* no change in grouping */
637 /* we have to regroup it */
639 /* first, disconnect everything and send notification*/
641 work = basename_list;
644 FileData *fd = work->data;
646 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
650 FileData *old_parent = fd->parent;
651 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
652 file_data_ref(old_parent);
653 file_data_disconnect_sidecar_file(old_parent, fd);
654 file_data_send_notification(old_parent, NOTIFY_REREAD);
655 file_data_unref(old_parent);
658 while (fd->sidecar_files)
660 FileData *sfd = fd->sidecar_files->data;
661 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
663 file_data_disconnect_sidecar_file(fd, sfd);
664 file_data_send_notification(sfd, NOTIFY_REREAD);
665 file_data_unref(sfd);
667 file_data_send_notification(fd, NOTIFY_GROUPING);
669 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
672 /* now we can form the new group */
673 work = basename_list->next;
677 FileData *sfd = work->data;
678 g_assert(sfd->magick == 0x12345678);
679 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
680 sfd->parent = parent_fd;
681 new_sidecars = g_list_prepend(new_sidecars, sfd);
684 g_assert(parent_fd->sidecar_files == NULL);
685 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
686 DEBUG_1("basename group changed for %s", parent_fd->path);
690 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
692 g_assert(target->magick == 0x12345678);
693 g_assert(sfd->magick == 0x12345678);
694 g_assert(g_list_find(target->sidecar_files, sfd));
696 file_data_ref(target);
699 g_assert(sfd->parent == target);
701 file_data_increment_version(sfd); /* increments both sfd and target */
703 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
706 file_data_unref(target);
707 file_data_unref(sfd);
710 /* disables / enables grouping for particular file, sends UPDATE notification */
711 void file_data_disable_grouping(FileData *fd, gboolean disable)
713 if (!fd->disable_grouping == !disable) return;
715 fd->disable_grouping = !!disable;
721 FileData *parent = file_data_ref(fd->parent);
722 file_data_disconnect_sidecar_file(parent, fd);
723 file_data_send_notification(parent, NOTIFY_GROUPING);
724 file_data_unref(parent);
726 else if (fd->sidecar_files)
728 GList *sidecar_files = filelist_copy(fd->sidecar_files);
729 GList *work = sidecar_files;
732 FileData *sfd = work->data;
734 file_data_disconnect_sidecar_file(fd, sfd);
735 file_data_send_notification(sfd, NOTIFY_GROUPING);
737 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
738 filelist_free(sidecar_files);
742 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
747 file_data_increment_version(fd);
748 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
750 file_data_send_notification(fd, NOTIFY_GROUPING);
753 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
760 FileData *fd = work->data;
762 file_data_disable_grouping(fd, disable);
770 *-----------------------------------------------------------------------------
772 *-----------------------------------------------------------------------------
775 static SortType filelist_sort_method = SORT_NONE;
776 static gboolean filelist_sort_ascend = TRUE;
779 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
782 if (!filelist_sort_ascend)
789 switch (filelist_sort_method)
794 if (fa->size < fb->size) return -1;
795 if (fa->size > fb->size) return 1;
796 /* fall back to name */
799 if (fa->date < fb->date) return -1;
800 if (fa->date > fb->date) return 1;
801 /* fall back to name */
803 #ifdef HAVE_STRVERSCMP
805 ret = strverscmp(fa->name, fb->name);
806 if (ret != 0) return ret;
813 if (options->file_sort.case_sensitive)
814 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
816 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
818 if (ret != 0) return ret;
820 /* do not return 0 unless the files are really the same
821 file_data_pool ensures that original_path is unique
823 return strcmp(fa->original_path, fb->original_path);
826 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
828 filelist_sort_method = method;
829 filelist_sort_ascend = ascend;
830 return filelist_sort_compare_filedata(fa, fb);
833 static gint filelist_sort_file_cb(gpointer a, gpointer b)
835 return filelist_sort_compare_filedata(a, b);
838 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
840 filelist_sort_method = method;
841 filelist_sort_ascend = ascend;
842 return g_list_sort(list, cb);
845 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
847 filelist_sort_method = method;
848 filelist_sort_ascend = ascend;
849 return g_list_insert_sorted(list, data, cb);
852 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
854 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
857 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
859 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
863 *-----------------------------------------------------------------------------
864 * basename hash - grouping of sidecars in filelist
865 *-----------------------------------------------------------------------------
869 static GHashTable *file_data_basename_hash_new(void)
871 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
874 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
877 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
879 list = g_hash_table_lookup(basename_hash, basename);
881 if (!g_list_find(list, fd))
883 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
884 g_hash_table_insert(basename_hash, basename, list);
894 static void file_data_basename_hash_remove(GHashTable *basename_hash, FileData *fd)
897 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
899 list = g_hash_table_lookup(basename_hash, basename);
901 if (!g_list_find(list, fd)) return;
903 list = g_list_remove(list, fd);
908 g_hash_table_insert(basename_hash, basename, list);
912 g_hash_table_remove(basename_hash, basename);
918 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
920 filelist_free((GList *)value);
923 static void file_data_basename_hash_free(GHashTable *basename_hash)
925 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
926 g_hash_table_destroy(basename_hash);
930 *-----------------------------------------------------------------------------
931 * handling sidecars in filelist
932 *-----------------------------------------------------------------------------
935 static GList *filelist_filter_out_sidecars(GList *flist)
938 GList *flist_filtered = NULL;
942 FileData *fd = work->data;
945 if (fd->parent) /* remove fd's that are children */
948 flist_filtered = g_list_prepend(flist_filtered, fd);
952 return flist_filtered;
955 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
957 GList *basename_list = (GList *)value;
958 file_data_check_sidecars(basename_list);
962 static gboolean is_hidden_file(const gchar *name)
964 if (name[0] != '.') return FALSE;
965 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
970 *-----------------------------------------------------------------------------
971 * the main filelist function
972 *-----------------------------------------------------------------------------
975 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
982 gint (*stat_func)(const gchar *path, struct stat *buf);
983 GHashTable *basename_hash = NULL;
985 g_assert(files || dirs);
987 if (files) *files = NULL;
988 if (dirs) *dirs = NULL;
990 pathl = path_from_utf8(dir_path);
991 if (!pathl) return FALSE;
1000 if (files) basename_hash = file_data_basename_hash_new();
1002 if (follow_symlinks)
1007 while ((dir = readdir(dp)) != NULL)
1009 struct stat ent_sbuf;
1010 const gchar *name = dir->d_name;
1013 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1016 filepath = g_build_filename(pathl, name, NULL);
1017 if (stat_func(filepath, &ent_sbuf) >= 0)
1019 if (S_ISDIR(ent_sbuf.st_mode))
1021 /* we ignore the .thumbnails dir for cleanliness */
1023 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1024 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1025 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1026 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1028 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1033 if (files && filter_name_exists(name))
1035 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1036 flist = g_list_prepend(flist, fd);
1037 if (fd->sidecar_priority && !fd->disable_grouping)
1039 file_data_basename_hash_insert(basename_hash, fd);
1046 if (errno == EOVERFLOW)
1048 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1058 if (dirs) *dirs = dlist;
1061 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1063 *files = filelist_filter_out_sidecars(flist);
1065 if (basename_hash) file_data_basename_hash_free(basename_hash);
1070 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1072 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1075 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1077 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1080 FileData *file_data_new_group(const gchar *path_utf8)
1087 if (!stat_utf8(path_utf8, &st))
1093 if (S_ISDIR(st.st_mode))
1094 return file_data_new(path_utf8, &st, TRUE);
1096 dir = remove_level_from_path(path_utf8);
1098 filelist_read_real(dir, &files, NULL, TRUE);
1100 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1104 filelist_free(files);
1110 void filelist_free(GList *list)
1117 file_data_unref((FileData *)work->data);
1125 GList *filelist_copy(GList *list)
1127 GList *new_list = NULL;
1138 new_list = g_list_prepend(new_list, file_data_ref(fd));
1141 return g_list_reverse(new_list);
1144 GList *filelist_from_path_list(GList *list)
1146 GList *new_list = NULL;
1157 new_list = g_list_prepend(new_list, file_data_new_group(path));
1160 return g_list_reverse(new_list);
1163 GList *filelist_to_path_list(GList *list)
1165 GList *new_list = NULL;
1176 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1179 return g_list_reverse(new_list);
1182 GList *filelist_filter(GList *list, gboolean is_dir_list)
1186 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1191 FileData *fd = (FileData *)(work->data);
1192 const gchar *name = fd->name;
1194 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1195 (!is_dir_list && !filter_name_exists(name)) ||
1196 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1197 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1201 list = g_list_remove_link(list, link);
1202 file_data_unref(fd);
1213 *-----------------------------------------------------------------------------
1214 * filelist recursive
1215 *-----------------------------------------------------------------------------
1218 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1220 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1223 GList *filelist_sort_path(GList *list)
1225 return g_list_sort(list, filelist_sort_path_cb);
1228 static void filelist_recursive_append(GList **list, GList *dirs)
1235 FileData *fd = (FileData *)(work->data);
1239 if (filelist_read(fd, &f, &d))
1241 f = filelist_filter(f, FALSE);
1242 f = filelist_sort_path(f);
1243 *list = g_list_concat(*list, f);
1245 d = filelist_filter(d, TRUE);
1246 d = filelist_sort_path(d);
1247 filelist_recursive_append(list, d);
1255 GList *filelist_recursive(FileData *dir_fd)
1260 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1261 list = filelist_filter(list, FALSE);
1262 list = filelist_sort_path(list);
1264 d = filelist_filter(d, TRUE);
1265 d = filelist_sort_path(d);
1266 filelist_recursive_append(&list, d);
1273 *-----------------------------------------------------------------------------
1274 * file modification support
1275 *-----------------------------------------------------------------------------
1279 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1281 if (!fdci && fd) fdci = fd->change;
1285 g_free(fdci->source);
1290 if (fd) fd->change = NULL;
1293 static gboolean file_data_can_write_directly(FileData *fd)
1295 return filter_name_is_writable(fd->extension);
1298 static gboolean file_data_can_write_sidecar(FileData *fd)
1300 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1303 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1305 gchar *sidecar_path = NULL;
1308 if (!file_data_can_write_sidecar(fd)) return NULL;
1310 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1313 FileData *sfd = work->data;
1315 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
1317 sidecar_path = g_strdup(sfd->path);
1322 if (!existing_only && !sidecar_path)
1324 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1325 sidecar_path = g_strconcat(base, ".xmp", NULL);
1329 return sidecar_path;
1333 * marks and orientation
1336 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1337 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1338 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1339 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1341 gboolean file_data_get_mark(FileData *fd, gint n)
1343 gboolean valid = (fd->valid_marks & (1 << n));
1345 if (file_data_get_mark_func[n] && !valid)
1347 guint old = fd->marks;
1348 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1350 if (!value != !(fd->marks & (1 << n)))
1352 fd->marks = fd->marks ^ (1 << n);
1355 fd->valid_marks |= (1 << n);
1356 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1358 file_data_unref(fd);
1360 else if (!old && fd->marks)
1366 return !!(fd->marks & (1 << n));
1369 guint file_data_get_marks(FileData *fd)
1372 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1376 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1379 if (!value == !file_data_get_mark(fd, n)) return;
1381 if (file_data_set_mark_func[n])
1383 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1388 fd->marks = fd->marks ^ (1 << n);
1390 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1392 file_data_unref(fd);
1394 else if (!old && fd->marks)
1399 file_data_increment_version(fd);
1400 file_data_send_notification(fd, NOTIFY_MARKS);
1403 gboolean file_data_filter_marks(FileData *fd, guint filter)
1406 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1407 return ((fd->marks & filter) == filter);
1410 GList *file_data_filter_marks_list(GList *list, guint filter)
1417 FileData *fd = work->data;
1421 if (!file_data_filter_marks(fd, filter))
1423 list = g_list_remove_link(list, link);
1424 file_data_unref(fd);
1432 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1434 FileData *fd = value;
1435 file_data_increment_version(fd);
1436 file_data_send_notification(fd, NOTIFY_MARKS);
1439 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1441 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1443 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1445 file_data_get_mark_func[n] = get_mark_func;
1446 file_data_set_mark_func[n] = set_mark_func;
1447 file_data_mark_func_data[n] = data;
1448 file_data_destroy_mark_func[n] = notify;
1452 /* this effectively changes all known files */
1453 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1459 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1461 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1462 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1463 if (data) *data = file_data_mark_func_data[n];
1466 gint file_data_get_user_orientation(FileData *fd)
1468 return fd->user_orientation;
1471 void file_data_set_user_orientation(FileData *fd, gint value)
1473 if (fd->user_orientation == value) return;
1475 fd->user_orientation = value;
1476 file_data_increment_version(fd);
1477 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1482 * file_data - operates on the given fd
1483 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1487 /* return list of sidecar file extensions in a string */
1488 gchar *file_data_sc_list_to_string(FileData *fd)
1491 GString *result = g_string_new("");
1493 work = fd->sidecar_files;
1496 FileData *sfd = work->data;
1498 result = g_string_append(result, "+ ");
1499 result = g_string_append(result, sfd->extension);
1501 if (work) result = g_string_append_c(result, ' ');
1504 return g_string_free(result, FALSE);
1510 * add FileDataChangeInfo (see typedefs.h) for the given operation
1511 * uses file_data_add_change_info
1513 * fails if the fd->change already exists - change operations can't run in parallel
1514 * fd->change_info works as a lock
1516 * dest can be NULL - in this case the current name is used for now, it will
1521 FileDataChangeInfo types:
1523 MOVE - path is changed, name may be changed too
1524 RENAME - path remains unchanged, name is changed
1525 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1526 sidecar names are changed too, extensions are not changed
1528 UPDATE - file size, date or grouping has been changed
1531 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1533 FileDataChangeInfo *fdci;
1535 if (fd->change) return FALSE;
1537 fdci = g_new0(FileDataChangeInfo, 1);
1542 fdci->source = g_strdup(src);
1544 fdci->source = g_strdup(fd->path);
1547 fdci->dest = g_strdup(dest);
1554 static void file_data_planned_change_remove(FileData *fd)
1556 if (file_data_planned_change_hash &&
1557 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1559 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1561 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1562 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1563 file_data_unref(fd);
1564 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1566 g_hash_table_destroy(file_data_planned_change_hash);
1567 file_data_planned_change_hash = NULL;
1568 DEBUG_1("planned change: empty");
1575 void file_data_free_ci(FileData *fd)
1577 FileDataChangeInfo *fdci = fd->change;
1581 file_data_planned_change_remove(fd);
1583 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1585 g_free(fdci->source);
1593 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1595 FileDataChangeInfo *fdci = fd->change;
1597 fdci->regroup_when_finished = enable;
1600 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1604 if (fd->parent) fd = fd->parent;
1606 if (fd->change) return FALSE;
1608 work = fd->sidecar_files;
1611 FileData *sfd = work->data;
1613 if (sfd->change) return FALSE;
1617 file_data_add_ci(fd, type, NULL, NULL);
1619 work = fd->sidecar_files;
1622 FileData *sfd = work->data;
1624 file_data_add_ci(sfd, type, NULL, NULL);
1631 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1635 if (fd->parent) fd = fd->parent;
1637 if (!fd->change || fd->change->type != type) return FALSE;
1639 work = fd->sidecar_files;
1642 FileData *sfd = work->data;
1644 if (!sfd->change || sfd->change->type != type) return FALSE;
1652 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1654 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1655 file_data_sc_update_ci_copy(fd, dest_path);
1659 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1661 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1662 file_data_sc_update_ci_move(fd, dest_path);
1666 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1668 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1669 file_data_sc_update_ci_rename(fd, dest_path);
1673 gboolean file_data_sc_add_ci_delete(FileData *fd)
1675 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1678 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1680 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1681 file_data_sc_update_ci_unspecified(fd, dest_path);
1685 gboolean file_data_add_ci_write_metadata(FileData *fd)
1687 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1690 void file_data_sc_free_ci(FileData *fd)
1694 if (fd->parent) fd = fd->parent;
1696 file_data_free_ci(fd);
1698 work = fd->sidecar_files;
1701 FileData *sfd = work->data;
1703 file_data_free_ci(sfd);
1708 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1711 gboolean ret = TRUE;
1716 FileData *fd = work->data;
1718 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1725 static void file_data_sc_revert_ci_list(GList *fd_list)
1732 FileData *fd = work->data;
1734 file_data_sc_free_ci(fd);
1739 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1746 FileData *fd = work->data;
1748 if (!func(fd, dest))
1750 file_data_sc_revert_ci_list(work->prev);
1759 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1761 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1764 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1766 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1769 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1771 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1774 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1776 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1779 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1782 gboolean ret = TRUE;
1787 FileData *fd = work->data;
1789 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1796 void file_data_free_ci_list(GList *fd_list)
1803 FileData *fd = work->data;
1805 file_data_free_ci(fd);
1810 void file_data_sc_free_ci_list(GList *fd_list)
1817 FileData *fd = work->data;
1819 file_data_sc_free_ci(fd);
1825 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1826 * fails if fd->change does not exist or the change type does not match
1829 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1831 FileDataChangeType type = fd->change->type;
1833 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1837 if (!file_data_planned_change_hash)
1838 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1840 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1842 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1843 g_hash_table_remove(file_data_planned_change_hash, old_path);
1844 file_data_unref(fd);
1847 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1852 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1853 g_hash_table_remove(file_data_planned_change_hash, new_path);
1854 file_data_unref(ofd);
1857 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1859 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1864 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1866 gchar *old_path = fd->change->dest;
1868 fd->change->dest = g_strdup(dest_path);
1869 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1873 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1875 const gchar *extension = extension_from_path(fd->change->source);
1876 gchar *base = remove_extension_from_path(dest_path);
1877 gchar *old_path = fd->change->dest;
1879 fd->change->dest = g_strconcat(base, extension, NULL);
1880 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1886 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1889 gchar *dest_path_full = NULL;
1891 if (fd->parent) fd = fd->parent;
1895 dest_path = fd->path;
1897 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1899 gchar *dir = remove_level_from_path(fd->path);
1901 dest_path_full = g_build_filename(dir, dest_path, NULL);
1903 dest_path = dest_path_full;
1905 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1907 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1908 dest_path = dest_path_full;
1911 file_data_update_ci_dest(fd, dest_path);
1913 work = fd->sidecar_files;
1916 FileData *sfd = work->data;
1918 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1922 g_free(dest_path_full);
1925 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1927 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1928 file_data_sc_update_ci(fd, dest_path);
1932 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1934 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1937 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1939 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1942 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1944 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1947 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1949 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1952 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1954 gboolean (*func)(FileData *, const gchar *))
1957 gboolean ret = TRUE;
1962 FileData *fd = work->data;
1964 if (!func(fd, dest)) ret = FALSE;
1971 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1973 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1976 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1978 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1981 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1983 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1988 * verify source and dest paths - dest image exists, etc.
1989 * it should detect all possible problems with the planned operation
1992 gint file_data_verify_ci(FileData *fd)
1994 gint ret = CHANGE_OK;
1999 DEBUG_1("Change checked: no change info: %s", fd->path);
2003 if (!isname(fd->path))
2005 /* this probably should not happen */
2006 ret |= CHANGE_NO_SRC;
2007 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2011 dir = remove_level_from_path(fd->path);
2013 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2014 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2015 fd->change->type != FILEDATA_CHANGE_RENAME &&
2016 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2019 ret |= CHANGE_WARN_UNSAVED_META;
2020 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2023 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2024 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2025 !access_file(fd->path, R_OK))
2027 ret |= CHANGE_NO_READ_PERM;
2028 DEBUG_1("Change checked: no read permission: %s", fd->path);
2030 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2031 !access_file(dir, W_OK))
2033 ret |= CHANGE_NO_WRITE_PERM_DIR;
2034 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2036 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2037 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2038 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2039 !access_file(fd->path, W_OK))
2041 ret |= CHANGE_WARN_NO_WRITE_PERM;
2042 DEBUG_1("Change checked: no write permission: %s", fd->path);
2044 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2045 - that means that there are no hard errors and warnings can be disabled
2046 - the destination is determined during the check
2048 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2050 /* determine destination file */
2051 gboolean have_dest = FALSE;
2052 gchar *dest_dir = NULL;
2054 if (options->metadata.save_in_image_file)
2056 if (file_data_can_write_directly(fd))
2058 /* we can write the file directly */
2059 if (access_file(fd->path, W_OK))
2065 if (options->metadata.warn_on_write_problems)
2067 ret |= CHANGE_WARN_NO_WRITE_PERM;
2068 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2072 else if (file_data_can_write_sidecar(fd))
2074 /* we can write sidecar */
2075 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2076 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2078 file_data_update_ci_dest(fd, sidecar);
2083 if (options->metadata.warn_on_write_problems)
2085 ret |= CHANGE_WARN_NO_WRITE_PERM;
2086 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2095 /* write private metadata file under ~/.geeqie */
2097 /* If an existing metadata file exists, we will try writing to
2098 * it's location regardless of the user's preference.
2100 gchar *metadata_path = NULL;
2102 /* but ignore XMP if we are not able to write it */
2103 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2105 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2107 if (metadata_path && !access_file(metadata_path, W_OK))
2109 g_free(metadata_path);
2110 metadata_path = NULL;
2117 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2118 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2120 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2122 metadata_path = g_build_filename(dest_dir, filename, NULL);
2126 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2128 file_data_update_ci_dest(fd, metadata_path);
2133 ret |= CHANGE_NO_WRITE_PERM_DEST;
2134 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2136 g_free(metadata_path);
2141 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2146 same = (strcmp(fd->path, fd->change->dest) == 0);
2150 const gchar *dest_ext = extension_from_path(fd->change->dest);
2151 if (!dest_ext) dest_ext = "";
2153 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2155 ret |= CHANGE_WARN_CHANGED_EXT;
2156 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2161 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2163 ret |= CHANGE_WARN_SAME;
2164 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2168 dest_dir = remove_level_from_path(fd->change->dest);
2170 if (!isdir(dest_dir))
2172 ret |= CHANGE_NO_DEST_DIR;
2173 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2175 else if (!access_file(dest_dir, W_OK))
2177 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2178 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2182 if (isfile(fd->change->dest))
2184 if (!access_file(fd->change->dest, W_OK))
2186 ret |= CHANGE_NO_WRITE_PERM_DEST;
2187 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2191 ret |= CHANGE_WARN_DEST_EXISTS;
2192 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2195 else if (isdir(fd->change->dest))
2197 ret |= CHANGE_DEST_EXISTS;
2198 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2205 fd->change->error = ret;
2206 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2213 gint file_data_sc_verify_ci(FileData *fd)
2218 ret = file_data_verify_ci(fd);
2220 work = fd->sidecar_files;
2223 FileData *sfd = work->data;
2225 ret |= file_data_verify_ci(sfd);
2232 gchar *file_data_get_error_string(gint error)
2234 GString *result = g_string_new("");
2236 if (error & CHANGE_NO_SRC)
2238 if (result->len > 0) g_string_append(result, ", ");
2239 g_string_append(result, _("file or directory does not exist"));
2242 if (error & CHANGE_DEST_EXISTS)
2244 if (result->len > 0) g_string_append(result, ", ");
2245 g_string_append(result, _("destination already exists"));
2248 if (error & CHANGE_NO_WRITE_PERM_DEST)
2250 if (result->len > 0) g_string_append(result, ", ");
2251 g_string_append(result, _("destination can't be overwritten"));
2254 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2256 if (result->len > 0) g_string_append(result, ", ");
2257 g_string_append(result, _("destination directory is not writable"));
2260 if (error & CHANGE_NO_DEST_DIR)
2262 if (result->len > 0) g_string_append(result, ", ");
2263 g_string_append(result, _("destination directory does not exist"));
2266 if (error & CHANGE_NO_WRITE_PERM_DIR)
2268 if (result->len > 0) g_string_append(result, ", ");
2269 g_string_append(result, _("source directory is not writable"));
2272 if (error & CHANGE_NO_READ_PERM)
2274 if (result->len > 0) g_string_append(result, ", ");
2275 g_string_append(result, _("no read permission"));
2278 if (error & CHANGE_WARN_NO_WRITE_PERM)
2280 if (result->len > 0) g_string_append(result, ", ");
2281 g_string_append(result, _("file is readonly"));
2284 if (error & CHANGE_WARN_DEST_EXISTS)
2286 if (result->len > 0) g_string_append(result, ", ");
2287 g_string_append(result, _("destination already exists and will be overwritten"));
2290 if (error & CHANGE_WARN_SAME)
2292 if (result->len > 0) g_string_append(result, ", ");
2293 g_string_append(result, _("source and destination are the same"));
2296 if (error & CHANGE_WARN_CHANGED_EXT)
2298 if (result->len > 0) g_string_append(result, ", ");
2299 g_string_append(result, _("source and destination have different extension"));
2302 if (error & CHANGE_WARN_UNSAVED_META)
2304 if (result->len > 0) g_string_append(result, ", ");
2305 g_string_append(result, _("there are unsaved metadata changes for the file"));
2308 return g_string_free(result, FALSE);
2311 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2314 gint all_errors = 0;
2315 gint common_errors = ~0;
2320 if (!list) return 0;
2322 num = g_list_length(list);
2323 errors = g_new(int, num);
2334 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2335 all_errors |= error;
2336 common_errors &= error;
2343 if (desc && all_errors)
2346 GString *result = g_string_new("");
2350 gchar *str = file_data_get_error_string(common_errors);
2351 g_string_append(result, str);
2352 g_string_append(result, "\n");
2366 error = errors[i] & ~common_errors;
2370 gchar *str = file_data_get_error_string(error);
2371 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2376 *desc = g_string_free(result, FALSE);
2385 * perform the change described by FileFataChangeInfo
2386 * it is used for internal operations,
2387 * this function actually operates with files on the filesystem
2388 * it should implement safe delete
2391 static gboolean file_data_perform_move(FileData *fd)
2393 g_assert(!strcmp(fd->change->source, fd->path));
2394 return move_file(fd->change->source, fd->change->dest);
2397 static gboolean file_data_perform_copy(FileData *fd)
2399 g_assert(!strcmp(fd->change->source, fd->path));
2400 return copy_file(fd->change->source, fd->change->dest);
2403 static gboolean file_data_perform_delete(FileData *fd)
2405 if (isdir(fd->path) && !islink(fd->path))
2406 return rmdir_utf8(fd->path);
2408 if (options->file_ops.safe_delete_enable)
2409 return file_util_safe_unlink(fd->path);
2411 return unlink_file(fd->path);
2414 gboolean file_data_perform_ci(FileData *fd)
2416 FileDataChangeType type = fd->change->type;
2420 case FILEDATA_CHANGE_MOVE:
2421 return file_data_perform_move(fd);
2422 case FILEDATA_CHANGE_COPY:
2423 return file_data_perform_copy(fd);
2424 case FILEDATA_CHANGE_RENAME:
2425 return file_data_perform_move(fd); /* the same as move */
2426 case FILEDATA_CHANGE_DELETE:
2427 return file_data_perform_delete(fd);
2428 case FILEDATA_CHANGE_WRITE_METADATA:
2429 return metadata_write_perform(fd);
2430 case FILEDATA_CHANGE_UNSPECIFIED:
2431 /* nothing to do here */
2439 gboolean file_data_sc_perform_ci(FileData *fd)
2442 gboolean ret = TRUE;
2443 FileDataChangeType type = fd->change->type;
2445 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2447 work = fd->sidecar_files;
2450 FileData *sfd = work->data;
2452 if (!file_data_perform_ci(sfd)) ret = FALSE;
2456 if (!file_data_perform_ci(fd)) ret = FALSE;
2462 * updates FileData structure according to FileDataChangeInfo
2465 gboolean file_data_apply_ci(FileData *fd)
2467 FileDataChangeType type = fd->change->type;
2470 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2472 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2473 file_data_planned_change_remove(fd);
2475 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2477 /* this change overwrites another file which is already known to other modules
2478 renaming fd would create duplicate FileData structure
2479 the best thing we can do is nothing
2480 FIXME: maybe we could copy stuff like marks
2482 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2486 file_data_set_path(fd, fd->change->dest);
2489 file_data_increment_version(fd);
2490 file_data_send_notification(fd, NOTIFY_CHANGE);
2495 gboolean file_data_sc_apply_ci(FileData *fd)
2498 FileDataChangeType type = fd->change->type;
2500 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2502 work = fd->sidecar_files;
2505 FileData *sfd = work->data;
2507 file_data_apply_ci(sfd);
2511 file_data_apply_ci(fd);
2516 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2519 if (fd->parent) fd = fd->parent;
2520 if (!g_list_find(list, fd)) return FALSE;
2522 work = fd->sidecar_files;
2525 if (!g_list_find(list, work->data)) return FALSE;
2532 static gboolean file_data_list_dump(GList *list)
2534 GList *work, *work2;
2539 FileData *fd = work->data;
2540 printf("%s\n", fd->name);
2541 work2 = fd->sidecar_files;
2544 FileData *fd = work2->data;
2545 printf(" %s\n", fd->name);
2546 work2 = work2->next;
2554 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2559 /* change partial groups to independent files */
2564 FileData *fd = work->data;
2567 if (!file_data_list_contains_whole_group(list, fd))
2569 file_data_disable_grouping(fd, TRUE);
2572 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2578 /* remove sidecars from the list,
2579 they can be still acessed via main_fd->sidecar_files */
2583 FileData *fd = work->data;
2587 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2589 out = g_list_prepend(out, file_data_ref(fd));
2593 filelist_free(list);
2594 out = g_list_reverse(out);
2604 * notify other modules about the change described by FileDataChangeInfo
2607 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2608 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2609 implementation in view_file_list.c */
2612 typedef struct _NotifyIdleData NotifyIdleData;
2614 struct _NotifyIdleData {
2620 typedef struct _NotifyData NotifyData;
2622 struct _NotifyData {
2623 FileDataNotifyFunc func;
2625 NotifyPriority priority;
2628 static GList *notify_func_list = NULL;
2630 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2632 NotifyData *nda = (NotifyData *)a;
2633 NotifyData *ndb = (NotifyData *)b;
2635 if (nda->priority < ndb->priority) return -1;
2636 if (nda->priority > ndb->priority) return 1;
2640 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2643 GList *work = notify_func_list;
2647 NotifyData *nd = (NotifyData *)work->data;
2649 if (nd->func == func && nd->data == data)
2651 g_warning("Notify func already registered");
2657 nd = g_new(NotifyData, 1);
2660 nd->priority = priority;
2662 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2663 DEBUG_2("Notify func registered: %p", nd);
2668 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2670 GList *work = notify_func_list;
2674 NotifyData *nd = (NotifyData *)work->data;
2676 if (nd->func == func && nd->data == data)
2678 notify_func_list = g_list_delete_link(notify_func_list, work);
2680 DEBUG_2("Notify func unregistered: %p", nd);
2686 g_warning("Notify func not found");
2691 gboolean file_data_send_notification_idle_cb(gpointer data)
2693 NotifyIdleData *nid = (NotifyIdleData *)data;
2694 GList *work = notify_func_list;
2698 NotifyData *nd = (NotifyData *)work->data;
2700 nd->func(nid->fd, nid->type, nd->data);
2703 file_data_unref(nid->fd);
2708 void file_data_send_notification(FileData *fd, NotifyType type)
2710 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
2711 nid->fd = file_data_ref(fd);
2713 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
2716 static GHashTable *file_data_monitor_pool = NULL;
2717 static guint realtime_monitor_id = 0; /* event source id */
2719 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2723 file_data_check_changed_files(fd);
2725 DEBUG_1("monitor %s", fd->path);
2728 static gboolean realtime_monitor_cb(gpointer data)
2730 if (!options->update_on_time_change) return TRUE;
2731 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2735 gboolean file_data_register_real_time_monitor(FileData *fd)
2741 if (!file_data_monitor_pool)
2742 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2744 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2746 DEBUG_1("Register realtime %d %s", count, fd->path);
2749 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2751 if (!realtime_monitor_id)
2753 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2759 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2763 g_assert(file_data_monitor_pool);
2765 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2767 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2769 g_assert(count > 0);
2774 g_hash_table_remove(file_data_monitor_pool, fd);
2776 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2778 file_data_unref(fd);
2780 if (g_hash_table_size(file_data_monitor_pool) == 0)
2782 g_source_remove(realtime_monitor_id);
2783 realtime_monitor_id = 0;
2789 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */