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 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
270 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
273 g_free(caseless_name);
276 static void file_data_set_path(FileData *fd, const gchar *path)
278 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
279 g_assert(file_data_pool);
283 if (fd->original_path)
285 g_hash_table_remove(file_data_pool, fd->original_path);
286 g_free(fd->original_path);
289 g_assert(!g_hash_table_lookup(file_data_pool, path));
291 fd->original_path = g_strdup(path);
292 g_hash_table_insert(file_data_pool, fd->original_path, fd);
294 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
296 fd->path = g_strdup(path);
298 fd->extension = fd->name + 1;
299 file_data_set_collate_keys(fd);
303 fd->path = g_strdup(path);
304 fd->name = filename_from_path(fd->path);
306 if (strcmp(fd->name, "..") == 0)
308 gchar *dir = remove_level_from_path(path);
310 fd->path = remove_level_from_path(dir);
313 fd->extension = fd->name + 2;
314 file_data_set_collate_keys(fd);
317 else if (strcmp(fd->name, ".") == 0)
320 fd->path = remove_level_from_path(path);
322 fd->extension = fd->name + 1;
323 file_data_set_collate_keys(fd);
327 fd->extension = registered_extension_from_path(fd->path);
328 if (fd->extension == NULL)
330 fd->extension = fd->name + strlen(fd->name);
333 fd->sidecar_priority = sidecar_file_priority(fd->extension);
334 file_data_set_collate_keys(fd);
338 *-----------------------------------------------------------------------------
339 * create or reuse Filedata
340 *-----------------------------------------------------------------------------
343 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
347 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
349 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
352 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
354 fd = g_hash_table_lookup(file_data_pool, path_utf8);
360 if (!fd && file_data_planned_change_hash)
362 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
365 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
367 file_data_apply_ci(fd);
375 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
378 changed = file_data_check_changed_single_file(fd, st);
380 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
385 fd = g_new0(FileData, 1);
387 fd->size = st->st_size;
388 fd->date = st->st_mtime;
389 fd->mode = st->st_mode;
391 fd->magick = FD_MAGICK;
393 if (disable_sidecars) fd->disable_grouping = TRUE;
395 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
400 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
402 gchar *path_utf8 = path_to_utf8(path);
403 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
409 void init_exif_time_data(GList *files)
412 DEBUG_1("%s init_exif_time_data: ...", get_exec_time());
424 void read_exif_time_data(FileData *file)
426 if (file->exifdate > 0)
428 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
432 file->exif = exif_read_fd(file);
436 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
437 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), file, file->path);
442 uint year, month, day, hour, min, sec;
444 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
445 time_str.tm_year = year - 1900;
446 time_str.tm_mon = month - 1;
447 time_str.tm_mday = day;
448 time_str.tm_hour = hour;
449 time_str.tm_min = min;
450 time_str.tm_sec = sec;
451 time_str.tm_isdst = 0;
453 file->exifdate = mktime(&time_str);
459 void set_exif_time_data(GList *files)
461 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
465 FileData *file = files->data;
467 read_exif_time_data(file);
472 FileData *file_data_new_no_grouping(const gchar *path_utf8)
476 if (!stat_utf8(path_utf8, &st))
482 return file_data_new(path_utf8, &st, TRUE);
485 FileData *file_data_new_dir(const gchar *path_utf8)
489 if (!stat_utf8(path_utf8, &st))
495 /* dir or non-existing yet */
496 g_assert(S_ISDIR(st.st_mode));
498 return file_data_new(path_utf8, &st, TRUE);
502 *-----------------------------------------------------------------------------
504 *-----------------------------------------------------------------------------
507 #ifdef DEBUG_FILEDATA
508 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
510 FileData *file_data_ref(FileData *fd)
513 if (fd == NULL) return NULL;
514 if (fd->magick != FD_MAGICK)
515 #ifdef DEBUG_FILEDATA
516 DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
518 DEBUG_0("fd magick mismatch fd=%p", fd);
520 g_assert(fd->magick == FD_MAGICK);
523 #ifdef DEBUG_FILEDATA
524 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", fd, fd->ref, fd->path, file, line);
526 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
531 static void file_data_free(FileData *fd)
533 g_assert(fd->magick == FD_MAGICK);
534 g_assert(fd->ref == 0);
536 metadata_cache_free(fd);
537 g_hash_table_remove(file_data_pool, fd->original_path);
540 g_free(fd->original_path);
541 g_free(fd->collate_key_name);
542 g_free(fd->collate_key_name_nocase);
543 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
544 histmap_free(fd->histmap);
546 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
548 file_data_change_info_free(NULL, fd);
552 #ifdef DEBUG_FILEDATA
553 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
555 void file_data_unref(FileData *fd)
558 if (fd == NULL) return;
559 if (fd->magick != FD_MAGICK)
560 #ifdef DEBUG_FILEDATA
561 DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
563 DEBUG_0("fd magick mismatch fd=%p", fd);
565 g_assert(fd->magick == FD_MAGICK);
568 #ifdef DEBUG_FILEDATA
569 DEBUG_2("file_data_unref fd=%p (%d): '%s' @ %s:%d", fd, fd->ref, fd->path, file, line);
571 DEBUG_2("file_data_unref fd=%p (%d): '%s'", fd, 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 == FD_MAGICK);
666 DEBUG_2("basename: %p %s", fd, fd->name);
669 g_assert(fd->parent->magick == FD_MAGICK);
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 == FD_MAGICK);
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 == FD_MAGICK);
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 == FD_MAGICK);
760 g_assert(sfd->magick == FD_MAGICK);
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);
966 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
968 filelist_free((GList *)value);
971 static void file_data_basename_hash_free(GHashTable *basename_hash)
973 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
974 g_hash_table_destroy(basename_hash);
978 *-----------------------------------------------------------------------------
979 * handling sidecars in filelist
980 *-----------------------------------------------------------------------------
983 static GList *filelist_filter_out_sidecars(GList *flist)
986 GList *flist_filtered = NULL;
990 FileData *fd = work->data;
993 if (fd->parent) /* remove fd's that are children */
996 flist_filtered = g_list_prepend(flist_filtered, fd);
1000 return flist_filtered;
1003 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
1005 GList *basename_list = (GList *)value;
1006 file_data_check_sidecars(basename_list);
1010 static gboolean is_hidden_file(const gchar *name)
1012 if (name[0] != '.') return FALSE;
1013 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1018 *-----------------------------------------------------------------------------
1019 * the main filelist function
1020 *-----------------------------------------------------------------------------
1023 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1028 GList *dlist = NULL;
1029 GList *flist = NULL;
1030 gint (*stat_func)(const gchar *path, struct stat *buf);
1031 GHashTable *basename_hash = NULL;
1033 g_assert(files || dirs);
1035 if (files) *files = NULL;
1036 if (dirs) *dirs = NULL;
1038 pathl = path_from_utf8(dir_path);
1039 if (!pathl) return FALSE;
1041 dp = opendir(pathl);
1048 if (files) basename_hash = file_data_basename_hash_new();
1050 if (follow_symlinks)
1055 while ((dir = readdir(dp)) != NULL)
1057 struct stat ent_sbuf;
1058 const gchar *name = dir->d_name;
1061 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1064 filepath = g_build_filename(pathl, name, NULL);
1065 if (stat_func(filepath, &ent_sbuf) >= 0)
1067 if (S_ISDIR(ent_sbuf.st_mode))
1069 /* we ignore the .thumbnails dir for cleanliness */
1071 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1072 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1073 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1074 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1076 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1081 if (files && filter_name_exists(name))
1083 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1084 flist = g_list_prepend(flist, fd);
1085 if (fd->sidecar_priority && !fd->disable_grouping)
1087 file_data_basename_hash_insert(basename_hash, fd);
1094 if (errno == EOVERFLOW)
1096 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1106 if (dirs) *dirs = dlist;
1110 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1112 *files = filelist_filter_out_sidecars(flist);
1114 if (basename_hash) file_data_basename_hash_free(basename_hash);
1116 // Call a separate function to initialize the exif datestamps for the found files..
1117 if (files) init_exif_time_data(*files);
1122 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1124 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1127 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1129 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1132 FileData *file_data_new_group(const gchar *path_utf8)
1139 if (!stat_utf8(path_utf8, &st))
1145 if (S_ISDIR(st.st_mode))
1146 return file_data_new(path_utf8, &st, TRUE);
1148 dir = remove_level_from_path(path_utf8);
1150 filelist_read_real(dir, &files, NULL, TRUE);
1152 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1153 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1159 filelist_free(files);
1165 void filelist_free(GList *list)
1172 file_data_unref((FileData *)work->data);
1180 GList *filelist_copy(GList *list)
1182 GList *new_list = NULL;
1193 new_list = g_list_prepend(new_list, file_data_ref(fd));
1196 return g_list_reverse(new_list);
1199 GList *filelist_from_path_list(GList *list)
1201 GList *new_list = NULL;
1212 new_list = g_list_prepend(new_list, file_data_new_group(path));
1215 return g_list_reverse(new_list);
1218 GList *filelist_to_path_list(GList *list)
1220 GList *new_list = NULL;
1231 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1234 return g_list_reverse(new_list);
1237 GList *filelist_filter(GList *list, gboolean is_dir_list)
1241 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1246 FileData *fd = (FileData *)(work->data);
1247 const gchar *name = fd->name;
1249 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1250 (!is_dir_list && !filter_name_exists(name)) ||
1251 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1252 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1256 list = g_list_remove_link(list, link);
1257 file_data_unref(fd);
1268 *-----------------------------------------------------------------------------
1269 * filelist recursive
1270 *-----------------------------------------------------------------------------
1273 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1275 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1278 GList *filelist_sort_path(GList *list)
1280 return g_list_sort(list, filelist_sort_path_cb);
1283 static void filelist_recursive_append(GList **list, GList *dirs)
1290 FileData *fd = (FileData *)(work->data);
1294 if (filelist_read(fd, &f, &d))
1296 f = filelist_filter(f, FALSE);
1297 f = filelist_sort_path(f);
1298 *list = g_list_concat(*list, f);
1300 d = filelist_filter(d, TRUE);
1301 d = filelist_sort_path(d);
1302 filelist_recursive_append(list, d);
1310 GList *filelist_recursive(FileData *dir_fd)
1315 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1316 list = filelist_filter(list, FALSE);
1317 list = filelist_sort_path(list);
1319 d = filelist_filter(d, TRUE);
1320 d = filelist_sort_path(d);
1321 filelist_recursive_append(&list, d);
1328 *-----------------------------------------------------------------------------
1329 * file modification support
1330 *-----------------------------------------------------------------------------
1334 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1336 if (!fdci && fd) fdci = fd->change;
1340 g_free(fdci->source);
1345 if (fd) fd->change = NULL;
1348 static gboolean file_data_can_write_directly(FileData *fd)
1350 return filter_name_is_writable(fd->extension);
1353 static gboolean file_data_can_write_sidecar(FileData *fd)
1355 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1358 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1360 gchar *sidecar_path = NULL;
1363 if (!file_data_can_write_sidecar(fd)) return NULL;
1365 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1368 FileData *sfd = work->data;
1370 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
1372 sidecar_path = g_strdup(sfd->path);
1377 if (!existing_only && !sidecar_path)
1379 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1380 sidecar_path = g_strconcat(base, ".xmp", NULL);
1384 return sidecar_path;
1388 * marks and orientation
1391 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1392 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1393 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1394 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1396 gboolean file_data_get_mark(FileData *fd, gint n)
1398 gboolean valid = (fd->valid_marks & (1 << n));
1400 if (file_data_get_mark_func[n] && !valid)
1402 guint old = fd->marks;
1403 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1405 if (!value != !(fd->marks & (1 << n)))
1407 fd->marks = fd->marks ^ (1 << n);
1410 fd->valid_marks |= (1 << n);
1411 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1413 file_data_unref(fd);
1415 else if (!old && fd->marks)
1421 return !!(fd->marks & (1 << n));
1424 guint file_data_get_marks(FileData *fd)
1427 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1431 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1434 if (!value == !file_data_get_mark(fd, n)) return;
1436 if (file_data_set_mark_func[n])
1438 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1443 fd->marks = fd->marks ^ (1 << n);
1445 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1447 file_data_unref(fd);
1449 else if (!old && fd->marks)
1454 file_data_increment_version(fd);
1455 file_data_send_notification(fd, NOTIFY_MARKS);
1458 gboolean file_data_filter_marks(FileData *fd, guint filter)
1461 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1462 return ((fd->marks & filter) == filter);
1465 GList *file_data_filter_marks_list(GList *list, guint filter)
1472 FileData *fd = work->data;
1476 if (!file_data_filter_marks(fd, filter))
1478 list = g_list_remove_link(list, link);
1479 file_data_unref(fd);
1487 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1489 FileData *fd = value;
1490 file_data_increment_version(fd);
1491 file_data_send_notification(fd, NOTIFY_MARKS);
1494 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1496 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1498 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1500 file_data_get_mark_func[n] = get_mark_func;
1501 file_data_set_mark_func[n] = set_mark_func;
1502 file_data_mark_func_data[n] = data;
1503 file_data_destroy_mark_func[n] = notify;
1507 /* this effectively changes all known files */
1508 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1514 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1516 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1517 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1518 if (data) *data = file_data_mark_func_data[n];
1521 gint file_data_get_user_orientation(FileData *fd)
1523 return fd->user_orientation;
1526 void file_data_set_user_orientation(FileData *fd, gint value)
1528 if (fd->user_orientation == value) return;
1530 fd->user_orientation = value;
1531 file_data_increment_version(fd);
1532 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1537 * file_data - operates on the given fd
1538 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1542 /* return list of sidecar file extensions in a string */
1543 gchar *file_data_sc_list_to_string(FileData *fd)
1546 GString *result = g_string_new("");
1548 work = fd->sidecar_files;
1551 FileData *sfd = work->data;
1553 result = g_string_append(result, "+ ");
1554 result = g_string_append(result, sfd->extension);
1556 if (work) result = g_string_append_c(result, ' ');
1559 return g_string_free(result, FALSE);
1565 * add FileDataChangeInfo (see typedefs.h) for the given operation
1566 * uses file_data_add_change_info
1568 * fails if the fd->change already exists - change operations can't run in parallel
1569 * fd->change_info works as a lock
1571 * dest can be NULL - in this case the current name is used for now, it will
1576 FileDataChangeInfo types:
1578 MOVE - path is changed, name may be changed too
1579 RENAME - path remains unchanged, name is changed
1580 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1581 sidecar names are changed too, extensions are not changed
1583 UPDATE - file size, date or grouping has been changed
1586 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1588 FileDataChangeInfo *fdci;
1590 if (fd->change) return FALSE;
1592 fdci = g_new0(FileDataChangeInfo, 1);
1597 fdci->source = g_strdup(src);
1599 fdci->source = g_strdup(fd->path);
1602 fdci->dest = g_strdup(dest);
1609 static void file_data_planned_change_remove(FileData *fd)
1611 if (file_data_planned_change_hash &&
1612 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1614 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1616 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1617 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1618 file_data_unref(fd);
1619 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1621 g_hash_table_destroy(file_data_planned_change_hash);
1622 file_data_planned_change_hash = NULL;
1623 DEBUG_1("planned change: empty");
1630 void file_data_free_ci(FileData *fd)
1632 FileDataChangeInfo *fdci = fd->change;
1636 file_data_planned_change_remove(fd);
1638 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1640 g_free(fdci->source);
1648 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1650 FileDataChangeInfo *fdci = fd->change;
1652 fdci->regroup_when_finished = enable;
1655 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1659 if (fd->parent) fd = fd->parent;
1661 if (fd->change) return FALSE;
1663 work = fd->sidecar_files;
1666 FileData *sfd = work->data;
1668 if (sfd->change) return FALSE;
1672 file_data_add_ci(fd, type, NULL, NULL);
1674 work = fd->sidecar_files;
1677 FileData *sfd = work->data;
1679 file_data_add_ci(sfd, type, NULL, NULL);
1686 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1690 if (fd->parent) fd = fd->parent;
1692 if (!fd->change || fd->change->type != type) return FALSE;
1694 work = fd->sidecar_files;
1697 FileData *sfd = work->data;
1699 if (!sfd->change || sfd->change->type != type) return FALSE;
1707 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1709 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1710 file_data_sc_update_ci_copy(fd, dest_path);
1714 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1716 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1717 file_data_sc_update_ci_move(fd, dest_path);
1721 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1723 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1724 file_data_sc_update_ci_rename(fd, dest_path);
1728 gboolean file_data_sc_add_ci_delete(FileData *fd)
1730 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1733 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1735 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1736 file_data_sc_update_ci_unspecified(fd, dest_path);
1740 gboolean file_data_add_ci_write_metadata(FileData *fd)
1742 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1745 void file_data_sc_free_ci(FileData *fd)
1749 if (fd->parent) fd = fd->parent;
1751 file_data_free_ci(fd);
1753 work = fd->sidecar_files;
1756 FileData *sfd = work->data;
1758 file_data_free_ci(sfd);
1763 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1766 gboolean ret = TRUE;
1771 FileData *fd = work->data;
1773 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1780 static void file_data_sc_revert_ci_list(GList *fd_list)
1787 FileData *fd = work->data;
1789 file_data_sc_free_ci(fd);
1794 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1801 FileData *fd = work->data;
1803 if (!func(fd, dest))
1805 file_data_sc_revert_ci_list(work->prev);
1814 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1816 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1819 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1821 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1824 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1826 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1829 gboolean file_data_sc_add_ci_unspecified_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_unspecified);
1834 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1837 gboolean ret = TRUE;
1842 FileData *fd = work->data;
1844 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1851 void file_data_free_ci_list(GList *fd_list)
1858 FileData *fd = work->data;
1860 file_data_free_ci(fd);
1865 void file_data_sc_free_ci_list(GList *fd_list)
1872 FileData *fd = work->data;
1874 file_data_sc_free_ci(fd);
1880 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1881 * fails if fd->change does not exist or the change type does not match
1884 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1886 FileDataChangeType type = fd->change->type;
1888 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1892 if (!file_data_planned_change_hash)
1893 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1895 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1897 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1898 g_hash_table_remove(file_data_planned_change_hash, old_path);
1899 file_data_unref(fd);
1902 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1907 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1908 g_hash_table_remove(file_data_planned_change_hash, new_path);
1909 file_data_unref(ofd);
1912 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1914 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1919 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1921 gchar *old_path = fd->change->dest;
1923 fd->change->dest = g_strdup(dest_path);
1924 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1928 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1930 const gchar *extension = extension_from_path(fd->change->source);
1931 gchar *base = remove_extension_from_path(dest_path);
1932 gchar *old_path = fd->change->dest;
1934 fd->change->dest = g_strconcat(base, extension, NULL);
1935 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1941 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1944 gchar *dest_path_full = NULL;
1946 if (fd->parent) fd = fd->parent;
1950 dest_path = fd->path;
1952 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1954 gchar *dir = remove_level_from_path(fd->path);
1956 dest_path_full = g_build_filename(dir, dest_path, NULL);
1958 dest_path = dest_path_full;
1960 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1962 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1963 dest_path = dest_path_full;
1966 file_data_update_ci_dest(fd, dest_path);
1968 work = fd->sidecar_files;
1971 FileData *sfd = work->data;
1973 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1977 g_free(dest_path_full);
1980 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1982 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1983 file_data_sc_update_ci(fd, dest_path);
1987 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1989 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1992 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1994 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1997 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1999 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2002 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2004 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2007 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2009 gboolean (*func)(FileData *, const gchar *))
2012 gboolean ret = TRUE;
2017 FileData *fd = work->data;
2019 if (!func(fd, dest)) ret = FALSE;
2026 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2028 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2031 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2033 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2036 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2038 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2043 * verify source and dest paths - dest image exists, etc.
2044 * it should detect all possible problems with the planned operation
2047 gint file_data_verify_ci(FileData *fd)
2049 gint ret = CHANGE_OK;
2054 DEBUG_1("Change checked: no change info: %s", fd->path);
2058 if (!isname(fd->path))
2060 /* this probably should not happen */
2061 ret |= CHANGE_NO_SRC;
2062 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2066 dir = remove_level_from_path(fd->path);
2068 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2069 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2070 fd->change->type != FILEDATA_CHANGE_RENAME &&
2071 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2074 ret |= CHANGE_WARN_UNSAVED_META;
2075 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2078 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2079 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2080 !access_file(fd->path, R_OK))
2082 ret |= CHANGE_NO_READ_PERM;
2083 DEBUG_1("Change checked: no read permission: %s", fd->path);
2085 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2086 !access_file(dir, W_OK))
2088 ret |= CHANGE_NO_WRITE_PERM_DIR;
2089 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2091 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2092 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2093 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2094 !access_file(fd->path, W_OK))
2096 ret |= CHANGE_WARN_NO_WRITE_PERM;
2097 DEBUG_1("Change checked: no write permission: %s", fd->path);
2099 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2100 - that means that there are no hard errors and warnings can be disabled
2101 - the destination is determined during the check
2103 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2105 /* determine destination file */
2106 gboolean have_dest = FALSE;
2107 gchar *dest_dir = NULL;
2109 if (options->metadata.save_in_image_file)
2111 if (file_data_can_write_directly(fd))
2113 /* we can write the file directly */
2114 if (access_file(fd->path, W_OK))
2120 if (options->metadata.warn_on_write_problems)
2122 ret |= CHANGE_WARN_NO_WRITE_PERM;
2123 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2127 else if (file_data_can_write_sidecar(fd))
2129 /* we can write sidecar */
2130 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2131 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2133 file_data_update_ci_dest(fd, sidecar);
2138 if (options->metadata.warn_on_write_problems)
2140 ret |= CHANGE_WARN_NO_WRITE_PERM;
2141 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2150 /* write private metadata file under ~/.geeqie */
2152 /* If an existing metadata file exists, we will try writing to
2153 * it's location regardless of the user's preference.
2155 gchar *metadata_path = NULL;
2157 /* but ignore XMP if we are not able to write it */
2158 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2160 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2162 if (metadata_path && !access_file(metadata_path, W_OK))
2164 g_free(metadata_path);
2165 metadata_path = NULL;
2172 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2173 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2175 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2177 metadata_path = g_build_filename(dest_dir, filename, NULL);
2181 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2183 file_data_update_ci_dest(fd, metadata_path);
2188 ret |= CHANGE_NO_WRITE_PERM_DEST;
2189 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2191 g_free(metadata_path);
2196 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2201 same = (strcmp(fd->path, fd->change->dest) == 0);
2205 const gchar *dest_ext = extension_from_path(fd->change->dest);
2206 if (!dest_ext) dest_ext = "";
2208 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2210 ret |= CHANGE_WARN_CHANGED_EXT;
2211 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2216 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2218 ret |= CHANGE_WARN_SAME;
2219 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2223 dest_dir = remove_level_from_path(fd->change->dest);
2225 if (!isdir(dest_dir))
2227 ret |= CHANGE_NO_DEST_DIR;
2228 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2230 else if (!access_file(dest_dir, W_OK))
2232 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2233 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2237 if (isfile(fd->change->dest))
2239 if (!access_file(fd->change->dest, W_OK))
2241 ret |= CHANGE_NO_WRITE_PERM_DEST;
2242 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2246 ret |= CHANGE_WARN_DEST_EXISTS;
2247 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2250 else if (isdir(fd->change->dest))
2252 ret |= CHANGE_DEST_EXISTS;
2253 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2260 fd->change->error = ret;
2261 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2268 gint file_data_sc_verify_ci(FileData *fd)
2273 ret = file_data_verify_ci(fd);
2275 work = fd->sidecar_files;
2278 FileData *sfd = work->data;
2280 ret |= file_data_verify_ci(sfd);
2287 gchar *file_data_get_error_string(gint error)
2289 GString *result = g_string_new("");
2291 if (error & CHANGE_NO_SRC)
2293 if (result->len > 0) g_string_append(result, ", ");
2294 g_string_append(result, _("file or directory does not exist"));
2297 if (error & CHANGE_DEST_EXISTS)
2299 if (result->len > 0) g_string_append(result, ", ");
2300 g_string_append(result, _("destination already exists"));
2303 if (error & CHANGE_NO_WRITE_PERM_DEST)
2305 if (result->len > 0) g_string_append(result, ", ");
2306 g_string_append(result, _("destination can't be overwritten"));
2309 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2311 if (result->len > 0) g_string_append(result, ", ");
2312 g_string_append(result, _("destination directory is not writable"));
2315 if (error & CHANGE_NO_DEST_DIR)
2317 if (result->len > 0) g_string_append(result, ", ");
2318 g_string_append(result, _("destination directory does not exist"));
2321 if (error & CHANGE_NO_WRITE_PERM_DIR)
2323 if (result->len > 0) g_string_append(result, ", ");
2324 g_string_append(result, _("source directory is not writable"));
2327 if (error & CHANGE_NO_READ_PERM)
2329 if (result->len > 0) g_string_append(result, ", ");
2330 g_string_append(result, _("no read permission"));
2333 if (error & CHANGE_WARN_NO_WRITE_PERM)
2335 if (result->len > 0) g_string_append(result, ", ");
2336 g_string_append(result, _("file is readonly"));
2339 if (error & CHANGE_WARN_DEST_EXISTS)
2341 if (result->len > 0) g_string_append(result, ", ");
2342 g_string_append(result, _("destination already exists and will be overwritten"));
2345 if (error & CHANGE_WARN_SAME)
2347 if (result->len > 0) g_string_append(result, ", ");
2348 g_string_append(result, _("source and destination are the same"));
2351 if (error & CHANGE_WARN_CHANGED_EXT)
2353 if (result->len > 0) g_string_append(result, ", ");
2354 g_string_append(result, _("source and destination have different extension"));
2357 if (error & CHANGE_WARN_UNSAVED_META)
2359 if (result->len > 0) g_string_append(result, ", ");
2360 g_string_append(result, _("there are unsaved metadata changes for the file"));
2363 return g_string_free(result, FALSE);
2366 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2369 gint all_errors = 0;
2370 gint common_errors = ~0;
2375 if (!list) return 0;
2377 num = g_list_length(list);
2378 errors = g_new(int, num);
2389 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2390 all_errors |= error;
2391 common_errors &= error;
2398 if (desc && all_errors)
2401 GString *result = g_string_new("");
2405 gchar *str = file_data_get_error_string(common_errors);
2406 g_string_append(result, str);
2407 g_string_append(result, "\n");
2421 error = errors[i] & ~common_errors;
2425 gchar *str = file_data_get_error_string(error);
2426 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2431 *desc = g_string_free(result, FALSE);
2440 * perform the change described by FileFataChangeInfo
2441 * it is used for internal operations,
2442 * this function actually operates with files on the filesystem
2443 * it should implement safe delete
2446 static gboolean file_data_perform_move(FileData *fd)
2448 g_assert(!strcmp(fd->change->source, fd->path));
2449 return move_file(fd->change->source, fd->change->dest);
2452 static gboolean file_data_perform_copy(FileData *fd)
2454 g_assert(!strcmp(fd->change->source, fd->path));
2455 return copy_file(fd->change->source, fd->change->dest);
2458 static gboolean file_data_perform_delete(FileData *fd)
2460 if (isdir(fd->path) && !islink(fd->path))
2461 return rmdir_utf8(fd->path);
2463 if (options->file_ops.safe_delete_enable)
2464 return file_util_safe_unlink(fd->path);
2466 return unlink_file(fd->path);
2469 gboolean file_data_perform_ci(FileData *fd)
2471 FileDataChangeType type = fd->change->type;
2475 case FILEDATA_CHANGE_MOVE:
2476 return file_data_perform_move(fd);
2477 case FILEDATA_CHANGE_COPY:
2478 return file_data_perform_copy(fd);
2479 case FILEDATA_CHANGE_RENAME:
2480 return file_data_perform_move(fd); /* the same as move */
2481 case FILEDATA_CHANGE_DELETE:
2482 return file_data_perform_delete(fd);
2483 case FILEDATA_CHANGE_WRITE_METADATA:
2484 return metadata_write_perform(fd);
2485 case FILEDATA_CHANGE_UNSPECIFIED:
2486 /* nothing to do here */
2494 gboolean file_data_sc_perform_ci(FileData *fd)
2497 gboolean ret = TRUE;
2498 FileDataChangeType type = fd->change->type;
2500 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2502 work = fd->sidecar_files;
2505 FileData *sfd = work->data;
2507 if (!file_data_perform_ci(sfd)) ret = FALSE;
2511 if (!file_data_perform_ci(fd)) ret = FALSE;
2517 * updates FileData structure according to FileDataChangeInfo
2520 gboolean file_data_apply_ci(FileData *fd)
2522 FileDataChangeType type = fd->change->type;
2525 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2527 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2528 file_data_planned_change_remove(fd);
2530 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2532 /* this change overwrites another file which is already known to other modules
2533 renaming fd would create duplicate FileData structure
2534 the best thing we can do is nothing
2535 FIXME: maybe we could copy stuff like marks
2537 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2541 file_data_set_path(fd, fd->change->dest);
2544 file_data_increment_version(fd);
2545 file_data_send_notification(fd, NOTIFY_CHANGE);
2550 gboolean file_data_sc_apply_ci(FileData *fd)
2553 FileDataChangeType type = fd->change->type;
2555 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2557 work = fd->sidecar_files;
2560 FileData *sfd = work->data;
2562 file_data_apply_ci(sfd);
2566 file_data_apply_ci(fd);
2571 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2574 if (fd->parent) fd = fd->parent;
2575 if (!g_list_find(list, fd)) return FALSE;
2577 work = fd->sidecar_files;
2580 if (!g_list_find(list, work->data)) return FALSE;
2586 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2591 /* change partial groups to independent files */
2596 FileData *fd = work->data;
2599 if (!file_data_list_contains_whole_group(list, fd))
2601 file_data_disable_grouping(fd, TRUE);
2604 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2610 /* remove sidecars from the list,
2611 they can be still acessed via main_fd->sidecar_files */
2615 FileData *fd = work->data;
2619 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2621 out = g_list_prepend(out, file_data_ref(fd));
2625 filelist_free(list);
2626 out = g_list_reverse(out);
2636 * notify other modules about the change described by FileDataChangeInfo
2639 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2640 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2641 implementation in view_file_list.c */
2644 typedef struct _NotifyIdleData NotifyIdleData;
2646 struct _NotifyIdleData {
2652 typedef struct _NotifyData NotifyData;
2654 struct _NotifyData {
2655 FileDataNotifyFunc func;
2657 NotifyPriority priority;
2660 static GList *notify_func_list = NULL;
2662 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2664 NotifyData *nda = (NotifyData *)a;
2665 NotifyData *ndb = (NotifyData *)b;
2667 if (nda->priority < ndb->priority) return -1;
2668 if (nda->priority > ndb->priority) return 1;
2672 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2675 GList *work = notify_func_list;
2679 NotifyData *nd = (NotifyData *)work->data;
2681 if (nd->func == func && nd->data == data)
2683 g_warning("Notify func already registered");
2689 nd = g_new(NotifyData, 1);
2692 nd->priority = priority;
2694 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2695 DEBUG_2("Notify func registered: %p", nd);
2700 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2702 GList *work = notify_func_list;
2706 NotifyData *nd = (NotifyData *)work->data;
2708 if (nd->func == func && nd->data == data)
2710 notify_func_list = g_list_delete_link(notify_func_list, work);
2712 DEBUG_2("Notify func unregistered: %p", nd);
2718 g_warning("Notify func not found");
2723 gboolean file_data_send_notification_idle_cb(gpointer data)
2725 NotifyIdleData *nid = (NotifyIdleData *)data;
2726 GList *work = notify_func_list;
2730 NotifyData *nd = (NotifyData *)work->data;
2732 nd->func(nid->fd, nid->type, nd->data);
2735 file_data_unref(nid->fd);
2740 void file_data_send_notification(FileData *fd, NotifyType type)
2742 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
2743 nid->fd = file_data_ref(fd);
2745 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
2748 static GHashTable *file_data_monitor_pool = NULL;
2749 static guint realtime_monitor_id = 0; /* event source id */
2751 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2755 file_data_check_changed_files(fd);
2757 DEBUG_1("monitor %s", fd->path);
2760 static gboolean realtime_monitor_cb(gpointer data)
2762 if (!options->update_on_time_change) return TRUE;
2763 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2767 gboolean file_data_register_real_time_monitor(FileData *fd)
2773 if (!file_data_monitor_pool)
2774 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2776 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2778 DEBUG_1("Register realtime %d %s", count, fd->path);
2781 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2783 if (!realtime_monitor_id)
2785 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2791 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2795 g_assert(file_data_monitor_pool);
2797 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2799 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2801 g_assert(count > 0);
2806 g_hash_table_remove(file_data_monitor_pool, fd);
2808 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2810 file_data_unref(fd);
2812 if (g_hash_table_size(file_data_monitor_pool) == 0)
2814 g_source_remove(realtime_monitor_id);
2815 realtime_monitor_id = 0;
2821 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */