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)
416 DEBUG_1("%s init_exif_time_data: ...", get_exec_time());
428 void read_exif_time_data(FileData *file)
430 if (file->exifdate > 0)
432 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
436 file->exif = exif_read_fd(file);
440 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
441 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), file, file->path);
446 uint year, month, day, hour, min, sec;
448 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
449 time_str.tm_year = year - 1900;
450 time_str.tm_mon = month - 1;
451 time_str.tm_mday = day;
452 time_str.tm_hour = hour;
453 time_str.tm_min = min;
454 time_str.tm_sec = sec;
455 time_str.tm_isdst = 0;
457 file->exifdate = mktime(&time_str);
463 void set_exif_time_data(GList *files)
465 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
469 FileData *file = files->data;
471 read_exif_time_data(file);
476 FileData *file_data_new_no_grouping(const gchar *path_utf8)
480 if (!stat_utf8(path_utf8, &st))
486 return file_data_new(path_utf8, &st, TRUE);
489 FileData *file_data_new_dir(const gchar *path_utf8)
493 if (!stat_utf8(path_utf8, &st))
499 /* dir or non-existing yet */
500 g_assert(S_ISDIR(st.st_mode));
502 return file_data_new(path_utf8, &st, TRUE);
506 *-----------------------------------------------------------------------------
508 *-----------------------------------------------------------------------------
511 #ifdef DEBUG_FILEDATA
512 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
514 FileData *file_data_ref(FileData *fd)
517 if (fd == NULL) return NULL;
518 #ifdef DEBUG_FILEDATA
519 if (fd->magick != 0x12345678)
520 DEBUG_0("fd magick mismatch at %s:%d", file, line);
522 g_assert(fd->magick == 0x12345678);
525 #ifdef DEBUG_FILEDATA
526 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
528 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
533 static void file_data_free(FileData *fd)
535 g_assert(fd->magick == 0x12345678);
536 g_assert(fd->ref == 0);
538 metadata_cache_free(fd);
539 g_hash_table_remove(file_data_pool, fd->original_path);
542 g_free(fd->original_path);
543 g_free(fd->collate_key_name);
544 g_free(fd->collate_key_name_nocase);
545 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
546 histmap_free(fd->histmap);
548 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
550 file_data_change_info_free(NULL, fd);
554 #ifdef DEBUG_FILEDATA
555 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
557 void file_data_unref(FileData *fd)
560 if (fd == NULL) return;
561 #ifdef DEBUG_FILEDATA
562 if (fd->magick != 0x12345678)
563 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
565 g_assert(fd->magick == 0x12345678);
568 #ifdef DEBUG_FILEDATA
569 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
571 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
576 FileData *parent = fd->parent ? fd->parent : fd;
578 if (parent->ref > 0) return;
580 work = parent->sidecar_files;
583 FileData *sfd = work->data;
584 if (sfd->ref > 0) return;
588 /* none of parent/children is referenced, we can free everything */
590 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
592 work = parent->sidecar_files;
595 FileData *sfd = work->data;
600 g_list_free(parent->sidecar_files);
601 parent->sidecar_files = NULL;
603 file_data_free(parent);
610 *-----------------------------------------------------------------------------
611 * sidecar file info struct
612 *-----------------------------------------------------------------------------
615 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
617 const FileData *fda = a;
618 const FileData *fdb = b;
620 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
621 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
623 return strcmp(fdb->extension, fda->extension);
627 static gint sidecar_file_priority(const gchar *extension)
632 if (extension == NULL)
635 work = sidecar_ext_get_list();
638 gchar *ext = work->data;
641 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
647 static void file_data_check_sidecars(const GList *basename_list)
649 /* basename_list contains the new group - first is the parent, then sorted sidecars */
650 /* all files in the list have ref count > 0 */
653 GList *s_work, *new_sidecars;
656 if (!basename_list) return;
659 DEBUG_2("basename start");
660 work = basename_list;
663 FileData *fd = work->data;
665 g_assert(fd->magick == 0x12345678);
666 DEBUG_2("basename: %p %s", fd, fd->name);
669 g_assert(fd->parent->magick == 0x12345678);
670 DEBUG_2(" parent: %p", fd->parent);
672 s_work = fd->sidecar_files;
675 FileData *sfd = s_work->data;
676 s_work = s_work->next;
677 g_assert(sfd->magick == 0x12345678);
678 DEBUG_2(" sidecar: %p %s", sfd, sfd->name);
681 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
684 parent_fd = basename_list->data;
686 /* check if the second and next entries of basename_list are already connected
687 as sidecars of the first entry (parent_fd) */
688 work = basename_list->next;
689 s_work = parent_fd->sidecar_files;
691 while (work && s_work)
693 if (work->data != s_work->data) break;
695 s_work = s_work->next;
698 if (!work && !s_work)
700 DEBUG_2("basename no change");
701 return; /* no change in grouping */
704 /* we have to regroup it */
706 /* first, disconnect everything and send notification*/
708 work = basename_list;
711 FileData *fd = work->data;
713 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
717 FileData *old_parent = fd->parent;
718 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
719 file_data_ref(old_parent);
720 file_data_disconnect_sidecar_file(old_parent, fd);
721 file_data_send_notification(old_parent, NOTIFY_REREAD);
722 file_data_unref(old_parent);
725 while (fd->sidecar_files)
727 FileData *sfd = fd->sidecar_files->data;
728 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
730 file_data_disconnect_sidecar_file(fd, sfd);
731 file_data_send_notification(sfd, NOTIFY_REREAD);
732 file_data_unref(sfd);
734 file_data_send_notification(fd, NOTIFY_GROUPING);
736 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
739 /* now we can form the new group */
740 work = basename_list->next;
744 FileData *sfd = work->data;
745 g_assert(sfd->magick == 0x12345678);
746 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
747 sfd->parent = parent_fd;
748 new_sidecars = g_list_prepend(new_sidecars, sfd);
751 g_assert(parent_fd->sidecar_files == NULL);
752 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
753 DEBUG_1("basename group changed for %s", parent_fd->path);
757 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
759 g_assert(target->magick == 0x12345678);
760 g_assert(sfd->magick == 0x12345678);
761 g_assert(g_list_find(target->sidecar_files, sfd));
763 file_data_ref(target);
766 g_assert(sfd->parent == target);
768 file_data_increment_version(sfd); /* increments both sfd and target */
770 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
773 file_data_unref(target);
774 file_data_unref(sfd);
777 /* disables / enables grouping for particular file, sends UPDATE notification */
778 void file_data_disable_grouping(FileData *fd, gboolean disable)
780 if (!fd->disable_grouping == !disable) return;
782 fd->disable_grouping = !!disable;
788 FileData *parent = file_data_ref(fd->parent);
789 file_data_disconnect_sidecar_file(parent, fd);
790 file_data_send_notification(parent, NOTIFY_GROUPING);
791 file_data_unref(parent);
793 else if (fd->sidecar_files)
795 GList *sidecar_files = filelist_copy(fd->sidecar_files);
796 GList *work = sidecar_files;
799 FileData *sfd = work->data;
801 file_data_disconnect_sidecar_file(fd, sfd);
802 file_data_send_notification(sfd, NOTIFY_GROUPING);
804 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
805 filelist_free(sidecar_files);
809 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
814 file_data_increment_version(fd);
815 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
817 file_data_send_notification(fd, NOTIFY_GROUPING);
820 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
827 FileData *fd = work->data;
829 file_data_disable_grouping(fd, disable);
837 *-----------------------------------------------------------------------------
839 *-----------------------------------------------------------------------------
843 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
846 if (!filelist_sort_ascend)
853 switch (filelist_sort_method)
858 if (fa->size < fb->size) return -1;
859 if (fa->size > fb->size) return 1;
860 /* fall back to name */
863 if (fa->date < fb->date) return -1;
864 if (fa->date > fb->date) return 1;
865 /* fall back to name */
868 if (fa->exifdate < fb->exifdate) return -1;
869 if (fa->exifdate > fb->exifdate) return 1;
870 /* fall back to name */
872 #ifdef HAVE_STRVERSCMP
874 ret = strverscmp(fa->name, fb->name);
875 if (ret != 0) return ret;
882 if (options->file_sort.case_sensitive)
883 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
885 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
887 if (ret != 0) return ret;
889 /* do not return 0 unless the files are really the same
890 file_data_pool ensures that original_path is unique
892 return strcmp(fa->original_path, fb->original_path);
895 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
897 filelist_sort_method = method;
898 filelist_sort_ascend = ascend;
899 return filelist_sort_compare_filedata(fa, fb);
902 static gint filelist_sort_file_cb(gpointer a, gpointer b)
904 return filelist_sort_compare_filedata(a, b);
907 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
909 filelist_sort_method = method;
910 filelist_sort_ascend = ascend;
911 return g_list_sort(list, cb);
914 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
916 filelist_sort_method = method;
917 filelist_sort_ascend = ascend;
918 return g_list_insert_sorted(list, data, cb);
921 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
923 if (method == SORT_EXIFTIME)
925 set_exif_time_data(list);
927 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
930 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
932 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
936 *-----------------------------------------------------------------------------
937 * basename hash - grouping of sidecars in filelist
938 *-----------------------------------------------------------------------------
942 static GHashTable *file_data_basename_hash_new(void)
944 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
947 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
950 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
952 list = g_hash_table_lookup(basename_hash, basename);
954 if (!g_list_find(list, fd))
956 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
957 g_hash_table_insert(basename_hash, basename, list);
967 static void file_data_basename_hash_remove(GHashTable *basename_hash, FileData *fd)
970 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
972 list = g_hash_table_lookup(basename_hash, basename);
974 if (!g_list_find(list, fd)) return;
976 list = g_list_remove(list, fd);
981 g_hash_table_insert(basename_hash, basename, list);
985 g_hash_table_remove(basename_hash, basename);
991 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
993 filelist_free((GList *)value);
996 static void file_data_basename_hash_free(GHashTable *basename_hash)
998 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
999 g_hash_table_destroy(basename_hash);
1003 *-----------------------------------------------------------------------------
1004 * handling sidecars in filelist
1005 *-----------------------------------------------------------------------------
1008 static GList *filelist_filter_out_sidecars(GList *flist)
1010 GList *work = flist;
1011 GList *flist_filtered = NULL;
1015 FileData *fd = work->data;
1018 if (fd->parent) /* remove fd's that are children */
1019 file_data_unref(fd);
1021 flist_filtered = g_list_prepend(flist_filtered, fd);
1025 return flist_filtered;
1028 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
1030 GList *basename_list = (GList *)value;
1031 file_data_check_sidecars(basename_list);
1035 static gboolean is_hidden_file(const gchar *name)
1037 if (name[0] != '.') return FALSE;
1038 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1043 *-----------------------------------------------------------------------------
1044 * the main filelist function
1045 *-----------------------------------------------------------------------------
1048 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1053 GList *dlist = NULL;
1054 GList *flist = NULL;
1055 gint (*stat_func)(const gchar *path, struct stat *buf);
1056 GHashTable *basename_hash = NULL;
1058 g_assert(files || dirs);
1060 if (files) *files = NULL;
1061 if (dirs) *dirs = NULL;
1063 pathl = path_from_utf8(dir_path);
1064 if (!pathl) return FALSE;
1066 dp = opendir(pathl);
1073 if (files) basename_hash = file_data_basename_hash_new();
1075 if (follow_symlinks)
1080 while ((dir = readdir(dp)) != NULL)
1082 struct stat ent_sbuf;
1083 const gchar *name = dir->d_name;
1086 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1089 filepath = g_build_filename(pathl, name, NULL);
1090 if (stat_func(filepath, &ent_sbuf) >= 0)
1092 if (S_ISDIR(ent_sbuf.st_mode))
1094 /* we ignore the .thumbnails dir for cleanliness */
1096 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1097 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1098 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1099 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1101 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1106 if (files && filter_name_exists(name))
1108 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1109 flist = g_list_prepend(flist, fd);
1110 if (fd->sidecar_priority && !fd->disable_grouping)
1112 file_data_basename_hash_insert(basename_hash, fd);
1119 if (errno == EOVERFLOW)
1121 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1131 if (dirs) *dirs = dlist;
1135 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1137 *files = filelist_filter_out_sidecars(flist);
1139 if (basename_hash) file_data_basename_hash_free(basename_hash);
1141 // Call a separate function to initialize the exif datestamps for the found files..
1142 if (files) init_exif_time_data(*files);
1147 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1149 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1152 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1154 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1157 FileData *file_data_new_group(const gchar *path_utf8)
1164 if (!stat_utf8(path_utf8, &st))
1170 if (S_ISDIR(st.st_mode))
1171 return file_data_new(path_utf8, &st, TRUE);
1173 dir = remove_level_from_path(path_utf8);
1175 filelist_read_real(dir, &files, NULL, TRUE);
1177 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1181 filelist_free(files);
1187 void filelist_free(GList *list)
1194 file_data_unref((FileData *)work->data);
1202 GList *filelist_copy(GList *list)
1204 GList *new_list = NULL;
1215 new_list = g_list_prepend(new_list, file_data_ref(fd));
1218 return g_list_reverse(new_list);
1221 GList *filelist_from_path_list(GList *list)
1223 GList *new_list = NULL;
1234 new_list = g_list_prepend(new_list, file_data_new_group(path));
1237 return g_list_reverse(new_list);
1240 GList *filelist_to_path_list(GList *list)
1242 GList *new_list = NULL;
1253 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1256 return g_list_reverse(new_list);
1259 GList *filelist_filter(GList *list, gboolean is_dir_list)
1263 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1268 FileData *fd = (FileData *)(work->data);
1269 const gchar *name = fd->name;
1271 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1272 (!is_dir_list && !filter_name_exists(name)) ||
1273 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1274 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1278 list = g_list_remove_link(list, link);
1279 file_data_unref(fd);
1290 *-----------------------------------------------------------------------------
1291 * filelist recursive
1292 *-----------------------------------------------------------------------------
1295 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1297 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1300 GList *filelist_sort_path(GList *list)
1302 return g_list_sort(list, filelist_sort_path_cb);
1305 static void filelist_recursive_append(GList **list, GList *dirs)
1312 FileData *fd = (FileData *)(work->data);
1316 if (filelist_read(fd, &f, &d))
1318 f = filelist_filter(f, FALSE);
1319 f = filelist_sort_path(f);
1320 *list = g_list_concat(*list, f);
1322 d = filelist_filter(d, TRUE);
1323 d = filelist_sort_path(d);
1324 filelist_recursive_append(list, d);
1332 GList *filelist_recursive(FileData *dir_fd)
1337 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1338 list = filelist_filter(list, FALSE);
1339 list = filelist_sort_path(list);
1341 d = filelist_filter(d, TRUE);
1342 d = filelist_sort_path(d);
1343 filelist_recursive_append(&list, d);
1350 *-----------------------------------------------------------------------------
1351 * file modification support
1352 *-----------------------------------------------------------------------------
1356 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1358 if (!fdci && fd) fdci = fd->change;
1362 g_free(fdci->source);
1367 if (fd) fd->change = NULL;
1370 static gboolean file_data_can_write_directly(FileData *fd)
1372 return filter_name_is_writable(fd->extension);
1375 static gboolean file_data_can_write_sidecar(FileData *fd)
1377 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1380 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1382 gchar *sidecar_path = NULL;
1385 if (!file_data_can_write_sidecar(fd)) return NULL;
1387 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1390 FileData *sfd = work->data;
1392 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
1394 sidecar_path = g_strdup(sfd->path);
1399 if (!existing_only && !sidecar_path)
1401 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1402 sidecar_path = g_strconcat(base, ".xmp", NULL);
1406 return sidecar_path;
1410 * marks and orientation
1413 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1414 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1415 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1416 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1418 gboolean file_data_get_mark(FileData *fd, gint n)
1420 gboolean valid = (fd->valid_marks & (1 << n));
1422 if (file_data_get_mark_func[n] && !valid)
1424 guint old = fd->marks;
1425 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1427 if (!value != !(fd->marks & (1 << n)))
1429 fd->marks = fd->marks ^ (1 << n);
1432 fd->valid_marks |= (1 << n);
1433 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1435 file_data_unref(fd);
1437 else if (!old && fd->marks)
1443 return !!(fd->marks & (1 << n));
1446 guint file_data_get_marks(FileData *fd)
1449 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1453 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1456 if (!value == !file_data_get_mark(fd, n)) return;
1458 if (file_data_set_mark_func[n])
1460 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1465 fd->marks = fd->marks ^ (1 << n);
1467 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1469 file_data_unref(fd);
1471 else if (!old && fd->marks)
1476 file_data_increment_version(fd);
1477 file_data_send_notification(fd, NOTIFY_MARKS);
1480 gboolean file_data_filter_marks(FileData *fd, guint filter)
1483 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1484 return ((fd->marks & filter) == filter);
1487 GList *file_data_filter_marks_list(GList *list, guint filter)
1494 FileData *fd = work->data;
1498 if (!file_data_filter_marks(fd, filter))
1500 list = g_list_remove_link(list, link);
1501 file_data_unref(fd);
1509 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1511 FileData *fd = value;
1512 file_data_increment_version(fd);
1513 file_data_send_notification(fd, NOTIFY_MARKS);
1516 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1518 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1520 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1522 file_data_get_mark_func[n] = get_mark_func;
1523 file_data_set_mark_func[n] = set_mark_func;
1524 file_data_mark_func_data[n] = data;
1525 file_data_destroy_mark_func[n] = notify;
1529 /* this effectively changes all known files */
1530 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1536 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1538 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1539 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1540 if (data) *data = file_data_mark_func_data[n];
1543 gint file_data_get_user_orientation(FileData *fd)
1545 return fd->user_orientation;
1548 void file_data_set_user_orientation(FileData *fd, gint value)
1550 if (fd->user_orientation == value) return;
1552 fd->user_orientation = value;
1553 file_data_increment_version(fd);
1554 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1559 * file_data - operates on the given fd
1560 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1564 /* return list of sidecar file extensions in a string */
1565 gchar *file_data_sc_list_to_string(FileData *fd)
1568 GString *result = g_string_new("");
1570 work = fd->sidecar_files;
1573 FileData *sfd = work->data;
1575 result = g_string_append(result, "+ ");
1576 result = g_string_append(result, sfd->extension);
1578 if (work) result = g_string_append_c(result, ' ');
1581 return g_string_free(result, FALSE);
1587 * add FileDataChangeInfo (see typedefs.h) for the given operation
1588 * uses file_data_add_change_info
1590 * fails if the fd->change already exists - change operations can't run in parallel
1591 * fd->change_info works as a lock
1593 * dest can be NULL - in this case the current name is used for now, it will
1598 FileDataChangeInfo types:
1600 MOVE - path is changed, name may be changed too
1601 RENAME - path remains unchanged, name is changed
1602 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1603 sidecar names are changed too, extensions are not changed
1605 UPDATE - file size, date or grouping has been changed
1608 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1610 FileDataChangeInfo *fdci;
1612 if (fd->change) return FALSE;
1614 fdci = g_new0(FileDataChangeInfo, 1);
1619 fdci->source = g_strdup(src);
1621 fdci->source = g_strdup(fd->path);
1624 fdci->dest = g_strdup(dest);
1631 static void file_data_planned_change_remove(FileData *fd)
1633 if (file_data_planned_change_hash &&
1634 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1636 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1638 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1639 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1640 file_data_unref(fd);
1641 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1643 g_hash_table_destroy(file_data_planned_change_hash);
1644 file_data_planned_change_hash = NULL;
1645 DEBUG_1("planned change: empty");
1652 void file_data_free_ci(FileData *fd)
1654 FileDataChangeInfo *fdci = fd->change;
1658 file_data_planned_change_remove(fd);
1660 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1662 g_free(fdci->source);
1670 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1672 FileDataChangeInfo *fdci = fd->change;
1674 fdci->regroup_when_finished = enable;
1677 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1681 if (fd->parent) fd = fd->parent;
1683 if (fd->change) return FALSE;
1685 work = fd->sidecar_files;
1688 FileData *sfd = work->data;
1690 if (sfd->change) return FALSE;
1694 file_data_add_ci(fd, type, NULL, NULL);
1696 work = fd->sidecar_files;
1699 FileData *sfd = work->data;
1701 file_data_add_ci(sfd, type, NULL, NULL);
1708 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1712 if (fd->parent) fd = fd->parent;
1714 if (!fd->change || fd->change->type != type) return FALSE;
1716 work = fd->sidecar_files;
1719 FileData *sfd = work->data;
1721 if (!sfd->change || sfd->change->type != type) return FALSE;
1729 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1731 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1732 file_data_sc_update_ci_copy(fd, dest_path);
1736 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1738 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1739 file_data_sc_update_ci_move(fd, dest_path);
1743 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1745 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1746 file_data_sc_update_ci_rename(fd, dest_path);
1750 gboolean file_data_sc_add_ci_delete(FileData *fd)
1752 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1755 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1757 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1758 file_data_sc_update_ci_unspecified(fd, dest_path);
1762 gboolean file_data_add_ci_write_metadata(FileData *fd)
1764 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1767 void file_data_sc_free_ci(FileData *fd)
1771 if (fd->parent) fd = fd->parent;
1773 file_data_free_ci(fd);
1775 work = fd->sidecar_files;
1778 FileData *sfd = work->data;
1780 file_data_free_ci(sfd);
1785 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1788 gboolean ret = TRUE;
1793 FileData *fd = work->data;
1795 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1802 static void file_data_sc_revert_ci_list(GList *fd_list)
1809 FileData *fd = work->data;
1811 file_data_sc_free_ci(fd);
1816 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1823 FileData *fd = work->data;
1825 if (!func(fd, dest))
1827 file_data_sc_revert_ci_list(work->prev);
1836 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1838 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1841 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1843 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1846 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1848 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1851 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1853 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1856 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1859 gboolean ret = TRUE;
1864 FileData *fd = work->data;
1866 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1873 void file_data_free_ci_list(GList *fd_list)
1880 FileData *fd = work->data;
1882 file_data_free_ci(fd);
1887 void file_data_sc_free_ci_list(GList *fd_list)
1894 FileData *fd = work->data;
1896 file_data_sc_free_ci(fd);
1902 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1903 * fails if fd->change does not exist or the change type does not match
1906 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1908 FileDataChangeType type = fd->change->type;
1910 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1914 if (!file_data_planned_change_hash)
1915 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1917 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1919 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1920 g_hash_table_remove(file_data_planned_change_hash, old_path);
1921 file_data_unref(fd);
1924 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1929 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1930 g_hash_table_remove(file_data_planned_change_hash, new_path);
1931 file_data_unref(ofd);
1934 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1936 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1941 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1943 gchar *old_path = fd->change->dest;
1945 fd->change->dest = g_strdup(dest_path);
1946 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1950 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1952 const gchar *extension = extension_from_path(fd->change->source);
1953 gchar *base = remove_extension_from_path(dest_path);
1954 gchar *old_path = fd->change->dest;
1956 fd->change->dest = g_strconcat(base, extension, NULL);
1957 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1963 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1966 gchar *dest_path_full = NULL;
1968 if (fd->parent) fd = fd->parent;
1972 dest_path = fd->path;
1974 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1976 gchar *dir = remove_level_from_path(fd->path);
1978 dest_path_full = g_build_filename(dir, dest_path, NULL);
1980 dest_path = dest_path_full;
1982 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1984 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1985 dest_path = dest_path_full;
1988 file_data_update_ci_dest(fd, dest_path);
1990 work = fd->sidecar_files;
1993 FileData *sfd = work->data;
1995 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1999 g_free(dest_path_full);
2002 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2004 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2005 file_data_sc_update_ci(fd, dest_path);
2009 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2011 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2014 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2016 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2019 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2021 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2024 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2026 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2029 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2031 gboolean (*func)(FileData *, const gchar *))
2034 gboolean ret = TRUE;
2039 FileData *fd = work->data;
2041 if (!func(fd, dest)) ret = FALSE;
2048 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2050 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2053 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2055 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2058 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2060 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2065 * verify source and dest paths - dest image exists, etc.
2066 * it should detect all possible problems with the planned operation
2069 gint file_data_verify_ci(FileData *fd)
2071 gint ret = CHANGE_OK;
2076 DEBUG_1("Change checked: no change info: %s", fd->path);
2080 if (!isname(fd->path))
2082 /* this probably should not happen */
2083 ret |= CHANGE_NO_SRC;
2084 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2088 dir = remove_level_from_path(fd->path);
2090 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2091 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2092 fd->change->type != FILEDATA_CHANGE_RENAME &&
2093 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2096 ret |= CHANGE_WARN_UNSAVED_META;
2097 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2100 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2101 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2102 !access_file(fd->path, R_OK))
2104 ret |= CHANGE_NO_READ_PERM;
2105 DEBUG_1("Change checked: no read permission: %s", fd->path);
2107 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2108 !access_file(dir, W_OK))
2110 ret |= CHANGE_NO_WRITE_PERM_DIR;
2111 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2113 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2114 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2115 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2116 !access_file(fd->path, W_OK))
2118 ret |= CHANGE_WARN_NO_WRITE_PERM;
2119 DEBUG_1("Change checked: no write permission: %s", fd->path);
2121 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2122 - that means that there are no hard errors and warnings can be disabled
2123 - the destination is determined during the check
2125 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2127 /* determine destination file */
2128 gboolean have_dest = FALSE;
2129 gchar *dest_dir = NULL;
2131 if (options->metadata.save_in_image_file)
2133 if (file_data_can_write_directly(fd))
2135 /* we can write the file directly */
2136 if (access_file(fd->path, W_OK))
2142 if (options->metadata.warn_on_write_problems)
2144 ret |= CHANGE_WARN_NO_WRITE_PERM;
2145 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2149 else if (file_data_can_write_sidecar(fd))
2151 /* we can write sidecar */
2152 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2153 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2155 file_data_update_ci_dest(fd, sidecar);
2160 if (options->metadata.warn_on_write_problems)
2162 ret |= CHANGE_WARN_NO_WRITE_PERM;
2163 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2172 /* write private metadata file under ~/.geeqie */
2174 /* If an existing metadata file exists, we will try writing to
2175 * it's location regardless of the user's preference.
2177 gchar *metadata_path = NULL;
2179 /* but ignore XMP if we are not able to write it */
2180 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2182 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2184 if (metadata_path && !access_file(metadata_path, W_OK))
2186 g_free(metadata_path);
2187 metadata_path = NULL;
2194 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2195 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2197 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2199 metadata_path = g_build_filename(dest_dir, filename, NULL);
2203 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2205 file_data_update_ci_dest(fd, metadata_path);
2210 ret |= CHANGE_NO_WRITE_PERM_DEST;
2211 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2213 g_free(metadata_path);
2218 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2223 same = (strcmp(fd->path, fd->change->dest) == 0);
2227 const gchar *dest_ext = extension_from_path(fd->change->dest);
2228 if (!dest_ext) dest_ext = "";
2230 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2232 ret |= CHANGE_WARN_CHANGED_EXT;
2233 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2238 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2240 ret |= CHANGE_WARN_SAME;
2241 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2245 dest_dir = remove_level_from_path(fd->change->dest);
2247 if (!isdir(dest_dir))
2249 ret |= CHANGE_NO_DEST_DIR;
2250 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2252 else if (!access_file(dest_dir, W_OK))
2254 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2255 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2259 if (isfile(fd->change->dest))
2261 if (!access_file(fd->change->dest, W_OK))
2263 ret |= CHANGE_NO_WRITE_PERM_DEST;
2264 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2268 ret |= CHANGE_WARN_DEST_EXISTS;
2269 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2272 else if (isdir(fd->change->dest))
2274 ret |= CHANGE_DEST_EXISTS;
2275 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2282 fd->change->error = ret;
2283 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2290 gint file_data_sc_verify_ci(FileData *fd)
2295 ret = file_data_verify_ci(fd);
2297 work = fd->sidecar_files;
2300 FileData *sfd = work->data;
2302 ret |= file_data_verify_ci(sfd);
2309 gchar *file_data_get_error_string(gint error)
2311 GString *result = g_string_new("");
2313 if (error & CHANGE_NO_SRC)
2315 if (result->len > 0) g_string_append(result, ", ");
2316 g_string_append(result, _("file or directory does not exist"));
2319 if (error & CHANGE_DEST_EXISTS)
2321 if (result->len > 0) g_string_append(result, ", ");
2322 g_string_append(result, _("destination already exists"));
2325 if (error & CHANGE_NO_WRITE_PERM_DEST)
2327 if (result->len > 0) g_string_append(result, ", ");
2328 g_string_append(result, _("destination can't be overwritten"));
2331 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2333 if (result->len > 0) g_string_append(result, ", ");
2334 g_string_append(result, _("destination directory is not writable"));
2337 if (error & CHANGE_NO_DEST_DIR)
2339 if (result->len > 0) g_string_append(result, ", ");
2340 g_string_append(result, _("destination directory does not exist"));
2343 if (error & CHANGE_NO_WRITE_PERM_DIR)
2345 if (result->len > 0) g_string_append(result, ", ");
2346 g_string_append(result, _("source directory is not writable"));
2349 if (error & CHANGE_NO_READ_PERM)
2351 if (result->len > 0) g_string_append(result, ", ");
2352 g_string_append(result, _("no read permission"));
2355 if (error & CHANGE_WARN_NO_WRITE_PERM)
2357 if (result->len > 0) g_string_append(result, ", ");
2358 g_string_append(result, _("file is readonly"));
2361 if (error & CHANGE_WARN_DEST_EXISTS)
2363 if (result->len > 0) g_string_append(result, ", ");
2364 g_string_append(result, _("destination already exists and will be overwritten"));
2367 if (error & CHANGE_WARN_SAME)
2369 if (result->len > 0) g_string_append(result, ", ");
2370 g_string_append(result, _("source and destination are the same"));
2373 if (error & CHANGE_WARN_CHANGED_EXT)
2375 if (result->len > 0) g_string_append(result, ", ");
2376 g_string_append(result, _("source and destination have different extension"));
2379 if (error & CHANGE_WARN_UNSAVED_META)
2381 if (result->len > 0) g_string_append(result, ", ");
2382 g_string_append(result, _("there are unsaved metadata changes for the file"));
2385 return g_string_free(result, FALSE);
2388 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2391 gint all_errors = 0;
2392 gint common_errors = ~0;
2397 if (!list) return 0;
2399 num = g_list_length(list);
2400 errors = g_new(int, num);
2411 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2412 all_errors |= error;
2413 common_errors &= error;
2420 if (desc && all_errors)
2423 GString *result = g_string_new("");
2427 gchar *str = file_data_get_error_string(common_errors);
2428 g_string_append(result, str);
2429 g_string_append(result, "\n");
2443 error = errors[i] & ~common_errors;
2447 gchar *str = file_data_get_error_string(error);
2448 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2453 *desc = g_string_free(result, FALSE);
2462 * perform the change described by FileFataChangeInfo
2463 * it is used for internal operations,
2464 * this function actually operates with files on the filesystem
2465 * it should implement safe delete
2468 static gboolean file_data_perform_move(FileData *fd)
2470 g_assert(!strcmp(fd->change->source, fd->path));
2471 return move_file(fd->change->source, fd->change->dest);
2474 static gboolean file_data_perform_copy(FileData *fd)
2476 g_assert(!strcmp(fd->change->source, fd->path));
2477 return copy_file(fd->change->source, fd->change->dest);
2480 static gboolean file_data_perform_delete(FileData *fd)
2482 if (isdir(fd->path) && !islink(fd->path))
2483 return rmdir_utf8(fd->path);
2485 if (options->file_ops.safe_delete_enable)
2486 return file_util_safe_unlink(fd->path);
2488 return unlink_file(fd->path);
2491 gboolean file_data_perform_ci(FileData *fd)
2493 FileDataChangeType type = fd->change->type;
2497 case FILEDATA_CHANGE_MOVE:
2498 return file_data_perform_move(fd);
2499 case FILEDATA_CHANGE_COPY:
2500 return file_data_perform_copy(fd);
2501 case FILEDATA_CHANGE_RENAME:
2502 return file_data_perform_move(fd); /* the same as move */
2503 case FILEDATA_CHANGE_DELETE:
2504 return file_data_perform_delete(fd);
2505 case FILEDATA_CHANGE_WRITE_METADATA:
2506 return metadata_write_perform(fd);
2507 case FILEDATA_CHANGE_UNSPECIFIED:
2508 /* nothing to do here */
2516 gboolean file_data_sc_perform_ci(FileData *fd)
2519 gboolean ret = TRUE;
2520 FileDataChangeType type = fd->change->type;
2522 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2524 work = fd->sidecar_files;
2527 FileData *sfd = work->data;
2529 if (!file_data_perform_ci(sfd)) ret = FALSE;
2533 if (!file_data_perform_ci(fd)) ret = FALSE;
2539 * updates FileData structure according to FileDataChangeInfo
2542 gboolean file_data_apply_ci(FileData *fd)
2544 FileDataChangeType type = fd->change->type;
2547 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2549 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2550 file_data_planned_change_remove(fd);
2552 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2554 /* this change overwrites another file which is already known to other modules
2555 renaming fd would create duplicate FileData structure
2556 the best thing we can do is nothing
2557 FIXME: maybe we could copy stuff like marks
2559 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2563 file_data_set_path(fd, fd->change->dest);
2566 file_data_increment_version(fd);
2567 file_data_send_notification(fd, NOTIFY_CHANGE);
2572 gboolean file_data_sc_apply_ci(FileData *fd)
2575 FileDataChangeType type = fd->change->type;
2577 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2579 work = fd->sidecar_files;
2582 FileData *sfd = work->data;
2584 file_data_apply_ci(sfd);
2588 file_data_apply_ci(fd);
2593 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2596 if (fd->parent) fd = fd->parent;
2597 if (!g_list_find(list, fd)) return FALSE;
2599 work = fd->sidecar_files;
2602 if (!g_list_find(list, work->data)) return FALSE;
2609 static gboolean file_data_list_dump(GList *list)
2611 GList *work, *work2;
2616 FileData *fd = work->data;
2617 printf("%s\n", fd->name);
2618 work2 = fd->sidecar_files;
2621 FileData *fd = work2->data;
2622 printf(" %s\n", fd->name);
2623 work2 = work2->next;
2631 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2636 /* change partial groups to independent files */
2641 FileData *fd = work->data;
2644 if (!file_data_list_contains_whole_group(list, fd))
2646 file_data_disable_grouping(fd, TRUE);
2649 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2655 /* remove sidecars from the list,
2656 they can be still acessed via main_fd->sidecar_files */
2660 FileData *fd = work->data;
2664 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2666 out = g_list_prepend(out, file_data_ref(fd));
2670 filelist_free(list);
2671 out = g_list_reverse(out);
2681 * notify other modules about the change described by FileDataChangeInfo
2684 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2685 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2686 implementation in view_file_list.c */
2689 typedef struct _NotifyIdleData NotifyIdleData;
2691 struct _NotifyIdleData {
2697 typedef struct _NotifyData NotifyData;
2699 struct _NotifyData {
2700 FileDataNotifyFunc func;
2702 NotifyPriority priority;
2705 static GList *notify_func_list = NULL;
2707 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2709 NotifyData *nda = (NotifyData *)a;
2710 NotifyData *ndb = (NotifyData *)b;
2712 if (nda->priority < ndb->priority) return -1;
2713 if (nda->priority > ndb->priority) return 1;
2717 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2720 GList *work = notify_func_list;
2724 NotifyData *nd = (NotifyData *)work->data;
2726 if (nd->func == func && nd->data == data)
2728 g_warning("Notify func already registered");
2734 nd = g_new(NotifyData, 1);
2737 nd->priority = priority;
2739 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2740 DEBUG_2("Notify func registered: %p", nd);
2745 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2747 GList *work = notify_func_list;
2751 NotifyData *nd = (NotifyData *)work->data;
2753 if (nd->func == func && nd->data == data)
2755 notify_func_list = g_list_delete_link(notify_func_list, work);
2757 DEBUG_2("Notify func unregistered: %p", nd);
2763 g_warning("Notify func not found");
2768 gboolean file_data_send_notification_idle_cb(gpointer data)
2770 NotifyIdleData *nid = (NotifyIdleData *)data;
2771 GList *work = notify_func_list;
2775 NotifyData *nd = (NotifyData *)work->data;
2777 nd->func(nid->fd, nid->type, nd->data);
2780 file_data_unref(nid->fd);
2785 void file_data_send_notification(FileData *fd, NotifyType type)
2787 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
2788 nid->fd = file_data_ref(fd);
2790 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
2793 static GHashTable *file_data_monitor_pool = NULL;
2794 static guint realtime_monitor_id = 0; /* event source id */
2796 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2800 file_data_check_changed_files(fd);
2802 DEBUG_1("monitor %s", fd->path);
2805 static gboolean realtime_monitor_cb(gpointer data)
2807 if (!options->update_on_time_change) return TRUE;
2808 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2812 gboolean file_data_register_real_time_monitor(FileData *fd)
2818 if (!file_data_monitor_pool)
2819 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2821 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2823 DEBUG_1("Register realtime %d %s", count, fd->path);
2826 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2828 if (!realtime_monitor_id)
2830 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2836 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2840 g_assert(file_data_monitor_pool);
2842 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2844 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2846 g_assert(count > 0);
2851 g_hash_table_remove(file_data_monitor_pool, fd);
2853 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2855 file_data_unref(fd);
2857 if (g_hash_table_size(file_data_monitor_pool) == 0)
2859 g_source_remove(realtime_monitor_id);
2860 realtime_monitor_id = 0;
2866 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */