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"
29 static GHashTable *file_data_pool = NULL;
30 static GHashTable *file_data_planned_change_hash = NULL;
32 static gint sidecar_file_priority(const gchar *extension);
33 static void file_data_check_sidecars(const GList *basename_list);
34 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
37 static SortType filelist_sort_method = SORT_NONE;
38 static gboolean filelist_sort_ascend = TRUE;
41 *-----------------------------------------------------------------------------
42 * text conversion utils
43 *-----------------------------------------------------------------------------
46 gchar *text_from_size(gint64 size)
52 /* what I would like to use is printf("%'d", size)
53 * BUT: not supported on every libc :(
57 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
58 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
62 a = g_strdup_printf("%d", (guint)size);
68 b = g_new(gchar, l + n + 1);
93 gchar *text_from_size_abrev(gint64 size)
95 if (size < (gint64)1024)
97 return g_strdup_printf(_("%d bytes"), (gint)size);
99 if (size < (gint64)1048576)
101 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
103 if (size < (gint64)1073741824)
105 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
108 /* to avoid overflowing the gdouble, do division in two steps */
110 return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
113 /* note: returned string is valid until next call to text_from_time() */
114 const gchar *text_from_time(time_t t)
116 static gchar *ret = NULL;
120 GError *error = NULL;
122 btime = localtime(&t);
124 /* the %x warning about 2 digit years is not an error */
125 buflen = strftime(buf, sizeof(buf), "%x %X", btime);
126 if (buflen < 1) return "";
129 ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
132 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
141 *-----------------------------------------------------------------------------
142 * changed files detection and notification
143 *-----------------------------------------------------------------------------
146 void file_data_increment_version(FileData *fd)
152 fd->parent->version++;
153 fd->parent->valid_marks = 0;
157 static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st)
159 if (fd->size != st->st_size ||
160 fd->date != st->st_mtime)
162 fd->size = st->st_size;
163 fd->date = st->st_mtime;
164 fd->mode = st->st_mode;
165 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
166 fd->thumb_pixbuf = NULL;
167 file_data_increment_version(fd);
168 file_data_send_notification(fd, NOTIFY_REREAD);
174 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
176 gboolean ret = FALSE;
179 ret = file_data_check_changed_single_file(fd, st);
181 work = fd->sidecar_files;
184 FileData *sfd = work->data;
188 if (!stat_utf8(sfd->path, &st))
193 file_data_disconnect_sidecar_file(fd, sfd);
195 file_data_increment_version(sfd);
196 file_data_send_notification(sfd, NOTIFY_REREAD);
197 file_data_unref(sfd);
201 ret |= file_data_check_changed_files_recursive(sfd, &st);
207 gboolean file_data_check_changed_files(FileData *fd)
209 gboolean ret = FALSE;
212 if (fd->parent) fd = fd->parent;
214 if (!stat_utf8(fd->path, &st))
218 FileData *sfd = NULL;
220 /* parent is missing, we have to rebuild whole group */
225 /* file_data_disconnect_sidecar_file might delete the file,
226 we have to keep the reference to prevent this */
227 sidecars = filelist_copy(fd->sidecar_files);
235 file_data_disconnect_sidecar_file(fd, sfd);
237 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
238 /* now we can release the sidecars */
239 filelist_free(sidecars);
240 file_data_increment_version(fd);
241 file_data_send_notification(fd, NOTIFY_REREAD);
246 ret |= file_data_check_changed_files_recursive(fd, &st);
253 *-----------------------------------------------------------------------------
254 * file name, extension, sorting, ...
255 *-----------------------------------------------------------------------------
258 static void file_data_set_collate_keys(FileData *fd)
260 gchar *caseless_name;
263 valid_name = g_filename_display_name(fd->name);
264 caseless_name = g_utf8_casefold(valid_name, -1);
266 g_free(fd->collate_key_name);
267 g_free(fd->collate_key_name_nocase);
269 #if 0 && GLIB_CHECK_VERSION(2, 8, 0)
270 fd->collate_key_name = g_utf8_collate_key_for_filename(valid_name, -1);
271 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
273 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
274 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
277 g_free(caseless_name);
280 static void file_data_set_path(FileData *fd, const gchar *path)
282 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
283 g_assert(file_data_pool);
287 if (fd->original_path)
289 g_hash_table_remove(file_data_pool, fd->original_path);
290 g_free(fd->original_path);
293 g_assert(!g_hash_table_lookup(file_data_pool, path));
295 fd->original_path = g_strdup(path);
296 g_hash_table_insert(file_data_pool, fd->original_path, fd);
298 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
300 fd->path = g_strdup(path);
302 fd->extension = fd->name + 1;
303 file_data_set_collate_keys(fd);
307 fd->path = g_strdup(path);
308 fd->name = filename_from_path(fd->path);
310 if (strcmp(fd->name, "..") == 0)
312 gchar *dir = remove_level_from_path(path);
314 fd->path = remove_level_from_path(dir);
317 fd->extension = fd->name + 2;
318 file_data_set_collate_keys(fd);
321 else if (strcmp(fd->name, ".") == 0)
324 fd->path = remove_level_from_path(path);
326 fd->extension = fd->name + 1;
327 file_data_set_collate_keys(fd);
331 fd->extension = registered_extension_from_path(fd->path);
332 if (fd->extension == NULL)
334 fd->extension = fd->name + strlen(fd->name);
337 fd->sidecar_priority = sidecar_file_priority(fd->extension);
338 file_data_set_collate_keys(fd);
342 *-----------------------------------------------------------------------------
343 * create or reuse Filedata
344 *-----------------------------------------------------------------------------
347 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
351 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
353 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
356 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
358 fd = g_hash_table_lookup(file_data_pool, path_utf8);
364 if (!fd && file_data_planned_change_hash)
366 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
369 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
371 file_data_apply_ci(fd);
379 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
382 changed = file_data_check_changed_single_file(fd, st);
384 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
389 fd = g_new0(FileData, 1);
391 fd->size = st->st_size;
392 fd->date = st->st_mtime;
393 fd->mode = st->st_mode;
395 fd->magick = 0x12345678;
397 if (disable_sidecars) fd->disable_grouping = TRUE;
399 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
404 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
406 gchar *path_utf8 = path_to_utf8(path);
407 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
413 void init_exif_time_data(GList *files) {
415 DEBUG_1("%s init_exif_time_data: ...", get_exec_time());
427 void set_exif_time_data(GList *files) {
429 uint year, month, day, hour, min, sec;
432 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
437 if (file->exifdate > 0)
440 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
444 DEBUG_1("%s set_exif_time_data: Getting exiftime for %s", get_exec_time(), file->path);
446 file->exif = exif_read_fd(file);
450 tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
453 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
454 time_str.tm_year = year - 1900;
455 time_str.tm_mon = month - 1;
456 time_str.tm_mday = day;
457 time_str.tm_hour = hour;
458 time_str.tm_min = min;
459 time_str.tm_sec = sec;
460 time_str.tm_isdst = 0;
462 file->exifdate = mktime(&time_str);
470 FileData *file_data_new_no_grouping(const gchar *path_utf8)
474 if (!stat_utf8(path_utf8, &st))
480 return file_data_new(path_utf8, &st, TRUE);
483 FileData *file_data_new_dir(const gchar *path_utf8)
487 if (!stat_utf8(path_utf8, &st))
493 /* dir or non-existing yet */
494 g_assert(S_ISDIR(st.st_mode));
496 return file_data_new(path_utf8, &st, TRUE);
500 *-----------------------------------------------------------------------------
502 *-----------------------------------------------------------------------------
505 #ifdef DEBUG_FILEDATA
506 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
508 FileData *file_data_ref(FileData *fd)
511 if (fd == NULL) return NULL;
512 #ifdef DEBUG_FILEDATA
513 if (fd->magick != 0x12345678)
514 DEBUG_0("fd magick mismatch at %s:%d", file, line);
516 g_assert(fd->magick == 0x12345678);
519 #ifdef DEBUG_FILEDATA
520 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
522 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
527 static void file_data_free(FileData *fd)
529 g_assert(fd->magick == 0x12345678);
530 g_assert(fd->ref == 0);
532 metadata_cache_free(fd);
533 g_hash_table_remove(file_data_pool, fd->original_path);
536 g_free(fd->original_path);
537 g_free(fd->collate_key_name);
538 g_free(fd->collate_key_name_nocase);
539 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
540 histmap_free(fd->histmap);
542 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
544 file_data_change_info_free(NULL, fd);
548 #ifdef DEBUG_FILEDATA
549 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
551 void file_data_unref(FileData *fd)
554 if (fd == NULL) return;
555 #ifdef DEBUG_FILEDATA
556 if (fd->magick != 0x12345678)
557 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
559 g_assert(fd->magick == 0x12345678);
562 #ifdef DEBUG_FILEDATA
563 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
565 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
570 FileData *parent = fd->parent ? fd->parent : fd;
572 if (parent->ref > 0) return;
574 work = parent->sidecar_files;
577 FileData *sfd = work->data;
578 if (sfd->ref > 0) return;
582 /* none of parent/children is referenced, we can free everything */
584 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
586 work = parent->sidecar_files;
589 FileData *sfd = work->data;
594 g_list_free(parent->sidecar_files);
595 parent->sidecar_files = NULL;
597 file_data_free(parent);
604 *-----------------------------------------------------------------------------
605 * sidecar file info struct
606 *-----------------------------------------------------------------------------
609 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
611 const FileData *fda = a;
612 const FileData *fdb = b;
614 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
615 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
617 return strcmp(fdb->extension, fda->extension);
621 static gint sidecar_file_priority(const gchar *extension)
626 if (extension == NULL)
629 work = sidecar_ext_get_list();
632 gchar *ext = work->data;
635 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
641 static void file_data_check_sidecars(const GList *basename_list)
643 /* basename_list contains the new group - first is the parent, then sorted sidecars */
644 /* all files in the list have ref count > 0 */
647 GList *s_work, *new_sidecars;
650 if (!basename_list) return;
653 DEBUG_2("basename start");
654 work = basename_list;
657 FileData *fd = work->data;
659 g_assert(fd->magick == 0x12345678);
660 DEBUG_2("basename: %p %s", fd, fd->name);
663 g_assert(fd->parent->magick == 0x12345678);
664 DEBUG_2(" parent: %p", fd->parent);
666 s_work = fd->sidecar_files;
669 FileData *sfd = s_work->data;
670 s_work = s_work->next;
671 g_assert(sfd->magick == 0x12345678);
672 DEBUG_2(" sidecar: %p %s", sfd, sfd->name);
675 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
678 parent_fd = basename_list->data;
680 /* check if the second and next entries of basename_list are already connected
681 as sidecars of the first entry (parent_fd) */
682 work = basename_list->next;
683 s_work = parent_fd->sidecar_files;
685 while (work && s_work)
687 if (work->data != s_work->data) break;
689 s_work = s_work->next;
692 if (!work && !s_work)
694 DEBUG_2("basename no change");
695 return; /* no change in grouping */
698 /* we have to regroup it */
700 /* first, disconnect everything and send notification*/
702 work = basename_list;
705 FileData *fd = work->data;
707 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
711 FileData *old_parent = fd->parent;
712 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
713 file_data_ref(old_parent);
714 file_data_disconnect_sidecar_file(old_parent, fd);
715 file_data_send_notification(old_parent, NOTIFY_REREAD);
716 file_data_unref(old_parent);
719 while (fd->sidecar_files)
721 FileData *sfd = fd->sidecar_files->data;
722 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
724 file_data_disconnect_sidecar_file(fd, sfd);
725 file_data_send_notification(sfd, NOTIFY_REREAD);
726 file_data_unref(sfd);
728 file_data_send_notification(fd, NOTIFY_GROUPING);
730 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
733 /* now we can form the new group */
734 work = basename_list->next;
738 FileData *sfd = work->data;
739 g_assert(sfd->magick == 0x12345678);
740 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
741 sfd->parent = parent_fd;
742 new_sidecars = g_list_prepend(new_sidecars, sfd);
745 g_assert(parent_fd->sidecar_files == NULL);
746 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
747 DEBUG_1("basename group changed for %s", parent_fd->path);
751 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
753 g_assert(target->magick == 0x12345678);
754 g_assert(sfd->magick == 0x12345678);
755 g_assert(g_list_find(target->sidecar_files, sfd));
757 file_data_ref(target);
760 g_assert(sfd->parent == target);
762 file_data_increment_version(sfd); /* increments both sfd and target */
764 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
767 file_data_unref(target);
768 file_data_unref(sfd);
771 /* disables / enables grouping for particular file, sends UPDATE notification */
772 void file_data_disable_grouping(FileData *fd, gboolean disable)
774 if (!fd->disable_grouping == !disable) return;
776 fd->disable_grouping = !!disable;
782 FileData *parent = file_data_ref(fd->parent);
783 file_data_disconnect_sidecar_file(parent, fd);
784 file_data_send_notification(parent, NOTIFY_GROUPING);
785 file_data_unref(parent);
787 else if (fd->sidecar_files)
789 GList *sidecar_files = filelist_copy(fd->sidecar_files);
790 GList *work = sidecar_files;
793 FileData *sfd = work->data;
795 file_data_disconnect_sidecar_file(fd, sfd);
796 file_data_send_notification(sfd, NOTIFY_GROUPING);
798 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
799 filelist_free(sidecar_files);
803 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
808 file_data_increment_version(fd);
809 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
811 file_data_send_notification(fd, NOTIFY_GROUPING);
814 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
821 FileData *fd = work->data;
823 file_data_disable_grouping(fd, disable);
831 *-----------------------------------------------------------------------------
833 *-----------------------------------------------------------------------------
837 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
840 if (!filelist_sort_ascend)
847 switch (filelist_sort_method)
852 if (fa->size < fb->size) return -1;
853 if (fa->size > fb->size) return 1;
854 /* fall back to name */
857 if (fa->date < fb->date) return -1;
858 if (fa->date > fb->date) return 1;
859 /* fall back to name */
862 if (fa->exifdate < fb->exifdate) return -1;
863 if (fa->exifdate > fb->exifdate) return 1;
864 /* fall back to name */
866 #ifdef HAVE_STRVERSCMP
868 ret = strverscmp(fa->name, fb->name);
869 if (ret != 0) return ret;
876 if (options->file_sort.case_sensitive)
877 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
879 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
881 if (ret != 0) return ret;
883 /* do not return 0 unless the files are really the same
884 file_data_pool ensures that original_path is unique
886 return strcmp(fa->original_path, fb->original_path);
889 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
891 filelist_sort_method = method;
892 filelist_sort_ascend = ascend;
893 return filelist_sort_compare_filedata(fa, fb);
896 static gint filelist_sort_file_cb(gpointer a, gpointer b)
898 return filelist_sort_compare_filedata(a, b);
901 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
903 filelist_sort_method = method;
904 filelist_sort_ascend = ascend;
905 return g_list_sort(list, cb);
908 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
910 filelist_sort_method = method;
911 filelist_sort_ascend = ascend;
912 return g_list_insert_sorted(list, data, cb);
915 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
917 if (method == SORT_EXIFTIME)
919 set_exif_time_data(list);
921 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
924 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
926 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
930 *-----------------------------------------------------------------------------
931 * basename hash - grouping of sidecars in filelist
932 *-----------------------------------------------------------------------------
936 static GHashTable *file_data_basename_hash_new(void)
938 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
941 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
944 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
946 list = g_hash_table_lookup(basename_hash, basename);
948 if (!g_list_find(list, fd))
950 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
951 g_hash_table_insert(basename_hash, basename, list);
961 static void file_data_basename_hash_remove(GHashTable *basename_hash, FileData *fd)
964 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
966 list = g_hash_table_lookup(basename_hash, basename);
968 if (!g_list_find(list, fd)) return;
970 list = g_list_remove(list, fd);
975 g_hash_table_insert(basename_hash, basename, list);
979 g_hash_table_remove(basename_hash, basename);
985 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
987 filelist_free((GList *)value);
990 static void file_data_basename_hash_free(GHashTable *basename_hash)
992 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
993 g_hash_table_destroy(basename_hash);
997 *-----------------------------------------------------------------------------
998 * handling sidecars in filelist
999 *-----------------------------------------------------------------------------
1002 static GList *filelist_filter_out_sidecars(GList *flist)
1004 GList *work = flist;
1005 GList *flist_filtered = NULL;
1009 FileData *fd = work->data;
1012 if (fd->parent) /* remove fd's that are children */
1013 file_data_unref(fd);
1015 flist_filtered = g_list_prepend(flist_filtered, fd);
1019 return flist_filtered;
1022 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
1024 GList *basename_list = (GList *)value;
1025 file_data_check_sidecars(basename_list);
1029 static gboolean is_hidden_file(const gchar *name)
1031 if (name[0] != '.') return FALSE;
1032 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1037 *-----------------------------------------------------------------------------
1038 * the main filelist function
1039 *-----------------------------------------------------------------------------
1042 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1047 GList *dlist = NULL;
1048 GList *flist = NULL;
1049 gint (*stat_func)(const gchar *path, struct stat *buf);
1050 GHashTable *basename_hash = NULL;
1052 g_assert(files || dirs);
1054 if (files) *files = NULL;
1055 if (dirs) *dirs = NULL;
1057 pathl = path_from_utf8(dir_path);
1058 if (!pathl) return FALSE;
1060 dp = opendir(pathl);
1067 if (files) basename_hash = file_data_basename_hash_new();
1069 if (follow_symlinks)
1074 while ((dir = readdir(dp)) != NULL)
1076 struct stat ent_sbuf;
1077 const gchar *name = dir->d_name;
1080 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1083 filepath = g_build_filename(pathl, name, NULL);
1084 if (stat_func(filepath, &ent_sbuf) >= 0)
1086 if (S_ISDIR(ent_sbuf.st_mode))
1088 /* we ignore the .thumbnails dir for cleanliness */
1090 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1091 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1092 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1093 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1095 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1100 if (files && filter_name_exists(name))
1102 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1103 flist = g_list_prepend(flist, fd);
1104 if (fd->sidecar_priority && !fd->disable_grouping)
1106 file_data_basename_hash_insert(basename_hash, fd);
1113 if (errno == EOVERFLOW)
1115 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1125 if (dirs) *dirs = dlist;
1128 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1130 *files = filelist_filter_out_sidecars(flist);
1132 if (basename_hash) file_data_basename_hash_free(basename_hash);
1134 // Call a separate function to initialize the exif datestamps for the found files..
1135 if (files) init_exif_time_data(*files);
1140 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1142 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1145 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1147 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1150 FileData *file_data_new_group(const gchar *path_utf8)
1157 if (!stat_utf8(path_utf8, &st))
1163 if (S_ISDIR(st.st_mode))
1164 return file_data_new(path_utf8, &st, TRUE);
1166 dir = remove_level_from_path(path_utf8);
1168 filelist_read_real(dir, &files, NULL, TRUE);
1170 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1174 filelist_free(files);
1180 void filelist_free(GList *list)
1187 file_data_unref((FileData *)work->data);
1195 GList *filelist_copy(GList *list)
1197 GList *new_list = NULL;
1208 new_list = g_list_prepend(new_list, file_data_ref(fd));
1211 return g_list_reverse(new_list);
1214 GList *filelist_from_path_list(GList *list)
1216 GList *new_list = NULL;
1227 new_list = g_list_prepend(new_list, file_data_new_group(path));
1230 return g_list_reverse(new_list);
1233 GList *filelist_to_path_list(GList *list)
1235 GList *new_list = NULL;
1246 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1249 return g_list_reverse(new_list);
1252 GList *filelist_filter(GList *list, gboolean is_dir_list)
1256 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1261 FileData *fd = (FileData *)(work->data);
1262 const gchar *name = fd->name;
1264 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1265 (!is_dir_list && !filter_name_exists(name)) ||
1266 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1267 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1271 list = g_list_remove_link(list, link);
1272 file_data_unref(fd);
1283 *-----------------------------------------------------------------------------
1284 * filelist recursive
1285 *-----------------------------------------------------------------------------
1288 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1290 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1293 GList *filelist_sort_path(GList *list)
1295 return g_list_sort(list, filelist_sort_path_cb);
1298 static void filelist_recursive_append(GList **list, GList *dirs)
1305 FileData *fd = (FileData *)(work->data);
1309 if (filelist_read(fd, &f, &d))
1311 f = filelist_filter(f, FALSE);
1312 f = filelist_sort_path(f);
1313 *list = g_list_concat(*list, f);
1315 d = filelist_filter(d, TRUE);
1316 d = filelist_sort_path(d);
1317 filelist_recursive_append(list, d);
1325 GList *filelist_recursive(FileData *dir_fd)
1330 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1331 list = filelist_filter(list, FALSE);
1332 list = filelist_sort_path(list);
1334 d = filelist_filter(d, TRUE);
1335 d = filelist_sort_path(d);
1336 filelist_recursive_append(&list, d);
1343 *-----------------------------------------------------------------------------
1344 * file modification support
1345 *-----------------------------------------------------------------------------
1349 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1351 if (!fdci && fd) fdci = fd->change;
1355 g_free(fdci->source);
1360 if (fd) fd->change = NULL;
1363 static gboolean file_data_can_write_directly(FileData *fd)
1365 return filter_name_is_writable(fd->extension);
1368 static gboolean file_data_can_write_sidecar(FileData *fd)
1370 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1373 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1375 gchar *sidecar_path = NULL;
1378 if (!file_data_can_write_sidecar(fd)) return NULL;
1380 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1383 FileData *sfd = work->data;
1385 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
1387 sidecar_path = g_strdup(sfd->path);
1392 if (!existing_only && !sidecar_path)
1394 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1395 sidecar_path = g_strconcat(base, ".xmp", NULL);
1399 return sidecar_path;
1403 * marks and orientation
1406 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1407 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1408 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1409 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1411 gboolean file_data_get_mark(FileData *fd, gint n)
1413 gboolean valid = (fd->valid_marks & (1 << n));
1415 if (file_data_get_mark_func[n] && !valid)
1417 guint old = fd->marks;
1418 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1420 if (!value != !(fd->marks & (1 << n)))
1422 fd->marks = fd->marks ^ (1 << n);
1425 fd->valid_marks |= (1 << n);
1426 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1428 file_data_unref(fd);
1430 else if (!old && fd->marks)
1436 return !!(fd->marks & (1 << n));
1439 guint file_data_get_marks(FileData *fd)
1442 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1446 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1449 if (!value == !file_data_get_mark(fd, n)) return;
1451 if (file_data_set_mark_func[n])
1453 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1458 fd->marks = fd->marks ^ (1 << n);
1460 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1462 file_data_unref(fd);
1464 else if (!old && fd->marks)
1469 file_data_increment_version(fd);
1470 file_data_send_notification(fd, NOTIFY_MARKS);
1473 gboolean file_data_filter_marks(FileData *fd, guint filter)
1476 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1477 return ((fd->marks & filter) == filter);
1480 GList *file_data_filter_marks_list(GList *list, guint filter)
1487 FileData *fd = work->data;
1491 if (!file_data_filter_marks(fd, filter))
1493 list = g_list_remove_link(list, link);
1494 file_data_unref(fd);
1502 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1504 FileData *fd = value;
1505 file_data_increment_version(fd);
1506 file_data_send_notification(fd, NOTIFY_MARKS);
1509 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1511 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1513 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1515 file_data_get_mark_func[n] = get_mark_func;
1516 file_data_set_mark_func[n] = set_mark_func;
1517 file_data_mark_func_data[n] = data;
1518 file_data_destroy_mark_func[n] = notify;
1522 /* this effectively changes all known files */
1523 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1529 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1531 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1532 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1533 if (data) *data = file_data_mark_func_data[n];
1536 gint file_data_get_user_orientation(FileData *fd)
1538 return fd->user_orientation;
1541 void file_data_set_user_orientation(FileData *fd, gint value)
1543 if (fd->user_orientation == value) return;
1545 fd->user_orientation = value;
1546 file_data_increment_version(fd);
1547 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1552 * file_data - operates on the given fd
1553 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1557 /* return list of sidecar file extensions in a string */
1558 gchar *file_data_sc_list_to_string(FileData *fd)
1561 GString *result = g_string_new("");
1563 work = fd->sidecar_files;
1566 FileData *sfd = work->data;
1568 result = g_string_append(result, "+ ");
1569 result = g_string_append(result, sfd->extension);
1571 if (work) result = g_string_append_c(result, ' ');
1574 return g_string_free(result, FALSE);
1580 * add FileDataChangeInfo (see typedefs.h) for the given operation
1581 * uses file_data_add_change_info
1583 * fails if the fd->change already exists - change operations can't run in parallel
1584 * fd->change_info works as a lock
1586 * dest can be NULL - in this case the current name is used for now, it will
1591 FileDataChangeInfo types:
1593 MOVE - path is changed, name may be changed too
1594 RENAME - path remains unchanged, name is changed
1595 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1596 sidecar names are changed too, extensions are not changed
1598 UPDATE - file size, date or grouping has been changed
1601 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1603 FileDataChangeInfo *fdci;
1605 if (fd->change) return FALSE;
1607 fdci = g_new0(FileDataChangeInfo, 1);
1612 fdci->source = g_strdup(src);
1614 fdci->source = g_strdup(fd->path);
1617 fdci->dest = g_strdup(dest);
1624 static void file_data_planned_change_remove(FileData *fd)
1626 if (file_data_planned_change_hash &&
1627 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1629 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1631 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1632 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1633 file_data_unref(fd);
1634 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1636 g_hash_table_destroy(file_data_planned_change_hash);
1637 file_data_planned_change_hash = NULL;
1638 DEBUG_1("planned change: empty");
1645 void file_data_free_ci(FileData *fd)
1647 FileDataChangeInfo *fdci = fd->change;
1651 file_data_planned_change_remove(fd);
1653 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1655 g_free(fdci->source);
1663 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1665 FileDataChangeInfo *fdci = fd->change;
1667 fdci->regroup_when_finished = enable;
1670 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1674 if (fd->parent) fd = fd->parent;
1676 if (fd->change) return FALSE;
1678 work = fd->sidecar_files;
1681 FileData *sfd = work->data;
1683 if (sfd->change) return FALSE;
1687 file_data_add_ci(fd, type, NULL, NULL);
1689 work = fd->sidecar_files;
1692 FileData *sfd = work->data;
1694 file_data_add_ci(sfd, type, NULL, NULL);
1701 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1705 if (fd->parent) fd = fd->parent;
1707 if (!fd->change || fd->change->type != type) return FALSE;
1709 work = fd->sidecar_files;
1712 FileData *sfd = work->data;
1714 if (!sfd->change || sfd->change->type != type) return FALSE;
1722 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1724 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1725 file_data_sc_update_ci_copy(fd, dest_path);
1729 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1731 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1732 file_data_sc_update_ci_move(fd, dest_path);
1736 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1738 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1739 file_data_sc_update_ci_rename(fd, dest_path);
1743 gboolean file_data_sc_add_ci_delete(FileData *fd)
1745 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1748 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1750 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1751 file_data_sc_update_ci_unspecified(fd, dest_path);
1755 gboolean file_data_add_ci_write_metadata(FileData *fd)
1757 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1760 void file_data_sc_free_ci(FileData *fd)
1764 if (fd->parent) fd = fd->parent;
1766 file_data_free_ci(fd);
1768 work = fd->sidecar_files;
1771 FileData *sfd = work->data;
1773 file_data_free_ci(sfd);
1778 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1781 gboolean ret = TRUE;
1786 FileData *fd = work->data;
1788 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1795 static void file_data_sc_revert_ci_list(GList *fd_list)
1802 FileData *fd = work->data;
1804 file_data_sc_free_ci(fd);
1809 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1816 FileData *fd = work->data;
1818 if (!func(fd, dest))
1820 file_data_sc_revert_ci_list(work->prev);
1829 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1831 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1834 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1836 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1839 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1841 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1844 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1846 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1849 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1852 gboolean ret = TRUE;
1857 FileData *fd = work->data;
1859 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1866 void file_data_free_ci_list(GList *fd_list)
1873 FileData *fd = work->data;
1875 file_data_free_ci(fd);
1880 void file_data_sc_free_ci_list(GList *fd_list)
1887 FileData *fd = work->data;
1889 file_data_sc_free_ci(fd);
1895 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1896 * fails if fd->change does not exist or the change type does not match
1899 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1901 FileDataChangeType type = fd->change->type;
1903 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1907 if (!file_data_planned_change_hash)
1908 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1910 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1912 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1913 g_hash_table_remove(file_data_planned_change_hash, old_path);
1914 file_data_unref(fd);
1917 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1922 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1923 g_hash_table_remove(file_data_planned_change_hash, new_path);
1924 file_data_unref(ofd);
1927 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1929 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1934 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1936 gchar *old_path = fd->change->dest;
1938 fd->change->dest = g_strdup(dest_path);
1939 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1943 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1945 const gchar *extension = extension_from_path(fd->change->source);
1946 gchar *base = remove_extension_from_path(dest_path);
1947 gchar *old_path = fd->change->dest;
1949 fd->change->dest = g_strconcat(base, extension, NULL);
1950 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1956 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1959 gchar *dest_path_full = NULL;
1961 if (fd->parent) fd = fd->parent;
1965 dest_path = fd->path;
1967 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1969 gchar *dir = remove_level_from_path(fd->path);
1971 dest_path_full = g_build_filename(dir, dest_path, NULL);
1973 dest_path = dest_path_full;
1975 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1977 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1978 dest_path = dest_path_full;
1981 file_data_update_ci_dest(fd, dest_path);
1983 work = fd->sidecar_files;
1986 FileData *sfd = work->data;
1988 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1992 g_free(dest_path_full);
1995 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1997 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1998 file_data_sc_update_ci(fd, dest_path);
2002 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2004 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2007 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2009 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2012 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2014 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2017 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2019 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2022 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2024 gboolean (*func)(FileData *, const gchar *))
2027 gboolean ret = TRUE;
2032 FileData *fd = work->data;
2034 if (!func(fd, dest)) ret = FALSE;
2041 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2043 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2046 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2048 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2051 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2053 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2058 * verify source and dest paths - dest image exists, etc.
2059 * it should detect all possible problems with the planned operation
2062 gint file_data_verify_ci(FileData *fd)
2064 gint ret = CHANGE_OK;
2069 DEBUG_1("Change checked: no change info: %s", fd->path);
2073 if (!isname(fd->path))
2075 /* this probably should not happen */
2076 ret |= CHANGE_NO_SRC;
2077 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2081 dir = remove_level_from_path(fd->path);
2083 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2084 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2085 fd->change->type != FILEDATA_CHANGE_RENAME &&
2086 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2089 ret |= CHANGE_WARN_UNSAVED_META;
2090 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2093 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2094 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2095 !access_file(fd->path, R_OK))
2097 ret |= CHANGE_NO_READ_PERM;
2098 DEBUG_1("Change checked: no read permission: %s", fd->path);
2100 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2101 !access_file(dir, W_OK))
2103 ret |= CHANGE_NO_WRITE_PERM_DIR;
2104 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2106 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2107 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2108 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2109 !access_file(fd->path, W_OK))
2111 ret |= CHANGE_WARN_NO_WRITE_PERM;
2112 DEBUG_1("Change checked: no write permission: %s", fd->path);
2114 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2115 - that means that there are no hard errors and warnings can be disabled
2116 - the destination is determined during the check
2118 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2120 /* determine destination file */
2121 gboolean have_dest = FALSE;
2122 gchar *dest_dir = NULL;
2124 if (options->metadata.save_in_image_file)
2126 if (file_data_can_write_directly(fd))
2128 /* we can write the file directly */
2129 if (access_file(fd->path, W_OK))
2135 if (options->metadata.warn_on_write_problems)
2137 ret |= CHANGE_WARN_NO_WRITE_PERM;
2138 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2142 else if (file_data_can_write_sidecar(fd))
2144 /* we can write sidecar */
2145 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2146 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2148 file_data_update_ci_dest(fd, sidecar);
2153 if (options->metadata.warn_on_write_problems)
2155 ret |= CHANGE_WARN_NO_WRITE_PERM;
2156 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2165 /* write private metadata file under ~/.geeqie */
2167 /* If an existing metadata file exists, we will try writing to
2168 * it's location regardless of the user's preference.
2170 gchar *metadata_path = NULL;
2172 /* but ignore XMP if we are not able to write it */
2173 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2175 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2177 if (metadata_path && !access_file(metadata_path, W_OK))
2179 g_free(metadata_path);
2180 metadata_path = NULL;
2187 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2188 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2190 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2192 metadata_path = g_build_filename(dest_dir, filename, NULL);
2196 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2198 file_data_update_ci_dest(fd, metadata_path);
2203 ret |= CHANGE_NO_WRITE_PERM_DEST;
2204 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2206 g_free(metadata_path);
2211 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2216 same = (strcmp(fd->path, fd->change->dest) == 0);
2220 const gchar *dest_ext = extension_from_path(fd->change->dest);
2221 if (!dest_ext) dest_ext = "";
2223 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2225 ret |= CHANGE_WARN_CHANGED_EXT;
2226 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2231 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2233 ret |= CHANGE_WARN_SAME;
2234 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2238 dest_dir = remove_level_from_path(fd->change->dest);
2240 if (!isdir(dest_dir))
2242 ret |= CHANGE_NO_DEST_DIR;
2243 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2245 else if (!access_file(dest_dir, W_OK))
2247 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2248 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2252 if (isfile(fd->change->dest))
2254 if (!access_file(fd->change->dest, W_OK))
2256 ret |= CHANGE_NO_WRITE_PERM_DEST;
2257 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2261 ret |= CHANGE_WARN_DEST_EXISTS;
2262 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2265 else if (isdir(fd->change->dest))
2267 ret |= CHANGE_DEST_EXISTS;
2268 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2275 fd->change->error = ret;
2276 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2283 gint file_data_sc_verify_ci(FileData *fd)
2288 ret = file_data_verify_ci(fd);
2290 work = fd->sidecar_files;
2293 FileData *sfd = work->data;
2295 ret |= file_data_verify_ci(sfd);
2302 gchar *file_data_get_error_string(gint error)
2304 GString *result = g_string_new("");
2306 if (error & CHANGE_NO_SRC)
2308 if (result->len > 0) g_string_append(result, ", ");
2309 g_string_append(result, _("file or directory does not exist"));
2312 if (error & CHANGE_DEST_EXISTS)
2314 if (result->len > 0) g_string_append(result, ", ");
2315 g_string_append(result, _("destination already exists"));
2318 if (error & CHANGE_NO_WRITE_PERM_DEST)
2320 if (result->len > 0) g_string_append(result, ", ");
2321 g_string_append(result, _("destination can't be overwritten"));
2324 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2326 if (result->len > 0) g_string_append(result, ", ");
2327 g_string_append(result, _("destination directory is not writable"));
2330 if (error & CHANGE_NO_DEST_DIR)
2332 if (result->len > 0) g_string_append(result, ", ");
2333 g_string_append(result, _("destination directory does not exist"));
2336 if (error & CHANGE_NO_WRITE_PERM_DIR)
2338 if (result->len > 0) g_string_append(result, ", ");
2339 g_string_append(result, _("source directory is not writable"));
2342 if (error & CHANGE_NO_READ_PERM)
2344 if (result->len > 0) g_string_append(result, ", ");
2345 g_string_append(result, _("no read permission"));
2348 if (error & CHANGE_WARN_NO_WRITE_PERM)
2350 if (result->len > 0) g_string_append(result, ", ");
2351 g_string_append(result, _("file is readonly"));
2354 if (error & CHANGE_WARN_DEST_EXISTS)
2356 if (result->len > 0) g_string_append(result, ", ");
2357 g_string_append(result, _("destination already exists and will be overwritten"));
2360 if (error & CHANGE_WARN_SAME)
2362 if (result->len > 0) g_string_append(result, ", ");
2363 g_string_append(result, _("source and destination are the same"));
2366 if (error & CHANGE_WARN_CHANGED_EXT)
2368 if (result->len > 0) g_string_append(result, ", ");
2369 g_string_append(result, _("source and destination have different extension"));
2372 if (error & CHANGE_WARN_UNSAVED_META)
2374 if (result->len > 0) g_string_append(result, ", ");
2375 g_string_append(result, _("there are unsaved metadata changes for the file"));
2378 return g_string_free(result, FALSE);
2381 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2384 gint all_errors = 0;
2385 gint common_errors = ~0;
2390 if (!list) return 0;
2392 num = g_list_length(list);
2393 errors = g_new(int, num);
2404 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2405 all_errors |= error;
2406 common_errors &= error;
2413 if (desc && all_errors)
2416 GString *result = g_string_new("");
2420 gchar *str = file_data_get_error_string(common_errors);
2421 g_string_append(result, str);
2422 g_string_append(result, "\n");
2436 error = errors[i] & ~common_errors;
2440 gchar *str = file_data_get_error_string(error);
2441 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2446 *desc = g_string_free(result, FALSE);
2455 * perform the change described by FileFataChangeInfo
2456 * it is used for internal operations,
2457 * this function actually operates with files on the filesystem
2458 * it should implement safe delete
2461 static gboolean file_data_perform_move(FileData *fd)
2463 g_assert(!strcmp(fd->change->source, fd->path));
2464 return move_file(fd->change->source, fd->change->dest);
2467 static gboolean file_data_perform_copy(FileData *fd)
2469 g_assert(!strcmp(fd->change->source, fd->path));
2470 return copy_file(fd->change->source, fd->change->dest);
2473 static gboolean file_data_perform_delete(FileData *fd)
2475 if (isdir(fd->path) && !islink(fd->path))
2476 return rmdir_utf8(fd->path);
2478 if (options->file_ops.safe_delete_enable)
2479 return file_util_safe_unlink(fd->path);
2481 return unlink_file(fd->path);
2484 gboolean file_data_perform_ci(FileData *fd)
2486 FileDataChangeType type = fd->change->type;
2490 case FILEDATA_CHANGE_MOVE:
2491 return file_data_perform_move(fd);
2492 case FILEDATA_CHANGE_COPY:
2493 return file_data_perform_copy(fd);
2494 case FILEDATA_CHANGE_RENAME:
2495 return file_data_perform_move(fd); /* the same as move */
2496 case FILEDATA_CHANGE_DELETE:
2497 return file_data_perform_delete(fd);
2498 case FILEDATA_CHANGE_WRITE_METADATA:
2499 return metadata_write_perform(fd);
2500 case FILEDATA_CHANGE_UNSPECIFIED:
2501 /* nothing to do here */
2509 gboolean file_data_sc_perform_ci(FileData *fd)
2512 gboolean ret = TRUE;
2513 FileDataChangeType type = fd->change->type;
2515 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2517 work = fd->sidecar_files;
2520 FileData *sfd = work->data;
2522 if (!file_data_perform_ci(sfd)) ret = FALSE;
2526 if (!file_data_perform_ci(fd)) ret = FALSE;
2532 * updates FileData structure according to FileDataChangeInfo
2535 gboolean file_data_apply_ci(FileData *fd)
2537 FileDataChangeType type = fd->change->type;
2540 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2542 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2543 file_data_planned_change_remove(fd);
2545 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2547 /* this change overwrites another file which is already known to other modules
2548 renaming fd would create duplicate FileData structure
2549 the best thing we can do is nothing
2550 FIXME: maybe we could copy stuff like marks
2552 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2556 file_data_set_path(fd, fd->change->dest);
2559 file_data_increment_version(fd);
2560 file_data_send_notification(fd, NOTIFY_CHANGE);
2565 gboolean file_data_sc_apply_ci(FileData *fd)
2568 FileDataChangeType type = fd->change->type;
2570 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2572 work = fd->sidecar_files;
2575 FileData *sfd = work->data;
2577 file_data_apply_ci(sfd);
2581 file_data_apply_ci(fd);
2586 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2589 if (fd->parent) fd = fd->parent;
2590 if (!g_list_find(list, fd)) return FALSE;
2592 work = fd->sidecar_files;
2595 if (!g_list_find(list, work->data)) return FALSE;
2602 static gboolean file_data_list_dump(GList *list)
2604 GList *work, *work2;
2609 FileData *fd = work->data;
2610 printf("%s\n", fd->name);
2611 work2 = fd->sidecar_files;
2614 FileData *fd = work2->data;
2615 printf(" %s\n", fd->name);
2616 work2 = work2->next;
2624 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2629 /* change partial groups to independent files */
2634 FileData *fd = work->data;
2637 if (!file_data_list_contains_whole_group(list, fd))
2639 file_data_disable_grouping(fd, TRUE);
2642 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2648 /* remove sidecars from the list,
2649 they can be still acessed via main_fd->sidecar_files */
2653 FileData *fd = work->data;
2657 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2659 out = g_list_prepend(out, file_data_ref(fd));
2663 filelist_free(list);
2664 out = g_list_reverse(out);
2674 * notify other modules about the change described by FileDataChangeInfo
2677 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2678 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2679 implementation in view_file_list.c */
2682 typedef struct _NotifyIdleData NotifyIdleData;
2684 struct _NotifyIdleData {
2690 typedef struct _NotifyData NotifyData;
2692 struct _NotifyData {
2693 FileDataNotifyFunc func;
2695 NotifyPriority priority;
2698 static GList *notify_func_list = NULL;
2700 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2702 NotifyData *nda = (NotifyData *)a;
2703 NotifyData *ndb = (NotifyData *)b;
2705 if (nda->priority < ndb->priority) return -1;
2706 if (nda->priority > ndb->priority) return 1;
2710 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2713 GList *work = notify_func_list;
2717 NotifyData *nd = (NotifyData *)work->data;
2719 if (nd->func == func && nd->data == data)
2721 g_warning("Notify func already registered");
2727 nd = g_new(NotifyData, 1);
2730 nd->priority = priority;
2732 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2733 DEBUG_2("Notify func registered: %p", nd);
2738 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2740 GList *work = notify_func_list;
2744 NotifyData *nd = (NotifyData *)work->data;
2746 if (nd->func == func && nd->data == data)
2748 notify_func_list = g_list_delete_link(notify_func_list, work);
2750 DEBUG_2("Notify func unregistered: %p", nd);
2756 g_warning("Notify func not found");
2761 gboolean file_data_send_notification_idle_cb(gpointer data)
2763 NotifyIdleData *nid = (NotifyIdleData *)data;
2764 GList *work = notify_func_list;
2768 NotifyData *nd = (NotifyData *)work->data;
2770 nd->func(nid->fd, nid->type, nd->data);
2773 file_data_unref(nid->fd);
2778 void file_data_send_notification(FileData *fd, NotifyType type)
2780 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
2781 nid->fd = file_data_ref(fd);
2783 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
2786 static GHashTable *file_data_monitor_pool = NULL;
2787 static guint realtime_monitor_id = 0; /* event source id */
2789 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2793 file_data_check_changed_files(fd);
2795 DEBUG_1("monitor %s", fd->path);
2798 static gboolean realtime_monitor_cb(gpointer data)
2800 if (!options->update_on_time_change) return TRUE;
2801 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2805 gboolean file_data_register_real_time_monitor(FileData *fd)
2811 if (!file_data_monitor_pool)
2812 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2814 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2816 DEBUG_1("Register realtime %d %s", count, fd->path);
2819 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2821 if (!realtime_monitor_id)
2823 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2829 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2833 g_assert(file_data_monitor_pool);
2835 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2837 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2839 g_assert(count > 0);
2844 g_hash_table_remove(file_data_monitor_pool, fd);
2846 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2848 file_data_unref(fd);
2850 if (g_hash_table_size(file_data_monitor_pool) == 0)
2852 g_source_remove(realtime_monitor_id);
2853 realtime_monitor_id = 0;
2859 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */