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 = FD_MAGICK;
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 if (fd->magick != FD_MAGICK)
519 #ifdef DEBUG_FILEDATA
520 DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
522 DEBUG_0("fd magick mismatch fd=%p", fd);
524 g_assert(fd->magick == FD_MAGICK);
527 #ifdef DEBUG_FILEDATA
528 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", fd, fd->ref, fd->path, file, line);
530 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
535 static void file_data_free(FileData *fd)
537 g_assert(fd->magick == FD_MAGICK);
538 g_assert(fd->ref == 0);
540 metadata_cache_free(fd);
541 g_hash_table_remove(file_data_pool, fd->original_path);
544 g_free(fd->original_path);
545 g_free(fd->collate_key_name);
546 g_free(fd->collate_key_name_nocase);
547 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
548 histmap_free(fd->histmap);
550 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
552 file_data_change_info_free(NULL, fd);
556 #ifdef DEBUG_FILEDATA
557 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
559 void file_data_unref(FileData *fd)
562 if (fd == NULL) return;
563 if (fd->magick != FD_MAGICK)
564 #ifdef DEBUG_FILEDATA
565 DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
567 DEBUG_0("fd magick mismatch fd=%p", fd);
569 g_assert(fd->magick == FD_MAGICK);
572 #ifdef DEBUG_FILEDATA
573 DEBUG_2("file_data_unref fd=%p (%d): '%s' @ %s:%d", fd, fd->ref, fd->path, file, line);
575 DEBUG_2("file_data_unref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
580 FileData *parent = fd->parent ? fd->parent : fd;
582 if (parent->ref > 0) return;
584 work = parent->sidecar_files;
587 FileData *sfd = work->data;
588 if (sfd->ref > 0) return;
592 /* none of parent/children is referenced, we can free everything */
594 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
596 work = parent->sidecar_files;
599 FileData *sfd = work->data;
604 g_list_free(parent->sidecar_files);
605 parent->sidecar_files = NULL;
607 file_data_free(parent);
614 *-----------------------------------------------------------------------------
615 * sidecar file info struct
616 *-----------------------------------------------------------------------------
619 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
621 const FileData *fda = a;
622 const FileData *fdb = b;
624 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
625 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
627 return strcmp(fdb->extension, fda->extension);
631 static gint sidecar_file_priority(const gchar *extension)
636 if (extension == NULL)
639 work = sidecar_ext_get_list();
642 gchar *ext = work->data;
645 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
651 static void file_data_check_sidecars(const GList *basename_list)
653 /* basename_list contains the new group - first is the parent, then sorted sidecars */
654 /* all files in the list have ref count > 0 */
657 GList *s_work, *new_sidecars;
660 if (!basename_list) return;
663 DEBUG_2("basename start");
664 work = basename_list;
667 FileData *fd = work->data;
669 g_assert(fd->magick == FD_MAGICK);
670 DEBUG_2("basename: %p %s", fd, fd->name);
673 g_assert(fd->parent->magick == FD_MAGICK);
674 DEBUG_2(" parent: %p", fd->parent);
676 s_work = fd->sidecar_files;
679 FileData *sfd = s_work->data;
680 s_work = s_work->next;
681 g_assert(sfd->magick == FD_MAGICK);
682 DEBUG_2(" sidecar: %p %s", sfd, sfd->name);
685 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
688 parent_fd = basename_list->data;
690 /* check if the second and next entries of basename_list are already connected
691 as sidecars of the first entry (parent_fd) */
692 work = basename_list->next;
693 s_work = parent_fd->sidecar_files;
695 while (work && s_work)
697 if (work->data != s_work->data) break;
699 s_work = s_work->next;
702 if (!work && !s_work)
704 DEBUG_2("basename no change");
705 return; /* no change in grouping */
708 /* we have to regroup it */
710 /* first, disconnect everything and send notification*/
712 work = basename_list;
715 FileData *fd = work->data;
717 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
721 FileData *old_parent = fd->parent;
722 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
723 file_data_ref(old_parent);
724 file_data_disconnect_sidecar_file(old_parent, fd);
725 file_data_send_notification(old_parent, NOTIFY_REREAD);
726 file_data_unref(old_parent);
729 while (fd->sidecar_files)
731 FileData *sfd = fd->sidecar_files->data;
732 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
734 file_data_disconnect_sidecar_file(fd, sfd);
735 file_data_send_notification(sfd, NOTIFY_REREAD);
736 file_data_unref(sfd);
738 file_data_send_notification(fd, NOTIFY_GROUPING);
740 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
743 /* now we can form the new group */
744 work = basename_list->next;
748 FileData *sfd = work->data;
749 g_assert(sfd->magick == FD_MAGICK);
750 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
751 sfd->parent = parent_fd;
752 new_sidecars = g_list_prepend(new_sidecars, sfd);
755 g_assert(parent_fd->sidecar_files == NULL);
756 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
757 DEBUG_1("basename group changed for %s", parent_fd->path);
761 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
763 g_assert(target->magick == FD_MAGICK);
764 g_assert(sfd->magick == FD_MAGICK);
765 g_assert(g_list_find(target->sidecar_files, sfd));
767 file_data_ref(target);
770 g_assert(sfd->parent == target);
772 file_data_increment_version(sfd); /* increments both sfd and target */
774 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
777 file_data_unref(target);
778 file_data_unref(sfd);
781 /* disables / enables grouping for particular file, sends UPDATE notification */
782 void file_data_disable_grouping(FileData *fd, gboolean disable)
784 if (!fd->disable_grouping == !disable) return;
786 fd->disable_grouping = !!disable;
792 FileData *parent = file_data_ref(fd->parent);
793 file_data_disconnect_sidecar_file(parent, fd);
794 file_data_send_notification(parent, NOTIFY_GROUPING);
795 file_data_unref(parent);
797 else if (fd->sidecar_files)
799 GList *sidecar_files = filelist_copy(fd->sidecar_files);
800 GList *work = sidecar_files;
803 FileData *sfd = work->data;
805 file_data_disconnect_sidecar_file(fd, sfd);
806 file_data_send_notification(sfd, NOTIFY_GROUPING);
808 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
809 filelist_free(sidecar_files);
813 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
818 file_data_increment_version(fd);
819 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
821 file_data_send_notification(fd, NOTIFY_GROUPING);
824 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
831 FileData *fd = work->data;
833 file_data_disable_grouping(fd, disable);
841 *-----------------------------------------------------------------------------
843 *-----------------------------------------------------------------------------
847 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
850 if (!filelist_sort_ascend)
857 switch (filelist_sort_method)
862 if (fa->size < fb->size) return -1;
863 if (fa->size > fb->size) return 1;
864 /* fall back to name */
867 if (fa->date < fb->date) return -1;
868 if (fa->date > fb->date) return 1;
869 /* fall back to name */
872 if (fa->exifdate < fb->exifdate) return -1;
873 if (fa->exifdate > fb->exifdate) return 1;
874 /* fall back to name */
876 #ifdef HAVE_STRVERSCMP
878 ret = strverscmp(fa->name, fb->name);
879 if (ret != 0) return ret;
886 if (options->file_sort.case_sensitive)
887 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
889 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
891 if (ret != 0) return ret;
893 /* do not return 0 unless the files are really the same
894 file_data_pool ensures that original_path is unique
896 return strcmp(fa->original_path, fb->original_path);
899 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
901 filelist_sort_method = method;
902 filelist_sort_ascend = ascend;
903 return filelist_sort_compare_filedata(fa, fb);
906 static gint filelist_sort_file_cb(gpointer a, gpointer b)
908 return filelist_sort_compare_filedata(a, b);
911 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
913 filelist_sort_method = method;
914 filelist_sort_ascend = ascend;
915 return g_list_sort(list, cb);
918 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
920 filelist_sort_method = method;
921 filelist_sort_ascend = ascend;
922 return g_list_insert_sorted(list, data, cb);
925 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
927 if (method == SORT_EXIFTIME)
929 set_exif_time_data(list);
931 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
934 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
936 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
940 *-----------------------------------------------------------------------------
941 * basename hash - grouping of sidecars in filelist
942 *-----------------------------------------------------------------------------
946 static GHashTable *file_data_basename_hash_new(void)
948 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
951 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
954 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
956 list = g_hash_table_lookup(basename_hash, basename);
958 if (!g_list_find(list, fd))
960 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
961 g_hash_table_insert(basename_hash, basename, list);
971 static void file_data_basename_hash_remove(GHashTable *basename_hash, FileData *fd)
974 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
976 list = g_hash_table_lookup(basename_hash, basename);
978 if (!g_list_find(list, fd)) return;
980 list = g_list_remove(list, fd);
985 g_hash_table_insert(basename_hash, basename, list);
989 g_hash_table_remove(basename_hash, basename);
995 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
997 filelist_free((GList *)value);
1000 static void file_data_basename_hash_free(GHashTable *basename_hash)
1002 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1003 g_hash_table_destroy(basename_hash);
1007 *-----------------------------------------------------------------------------
1008 * handling sidecars in filelist
1009 *-----------------------------------------------------------------------------
1012 static GList *filelist_filter_out_sidecars(GList *flist)
1014 GList *work = flist;
1015 GList *flist_filtered = NULL;
1019 FileData *fd = work->data;
1022 if (fd->parent) /* remove fd's that are children */
1023 file_data_unref(fd);
1025 flist_filtered = g_list_prepend(flist_filtered, fd);
1029 return flist_filtered;
1032 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
1034 GList *basename_list = (GList *)value;
1035 file_data_check_sidecars(basename_list);
1039 static gboolean is_hidden_file(const gchar *name)
1041 if (name[0] != '.') return FALSE;
1042 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1047 *-----------------------------------------------------------------------------
1048 * the main filelist function
1049 *-----------------------------------------------------------------------------
1052 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1057 GList *dlist = NULL;
1058 GList *flist = NULL;
1059 gint (*stat_func)(const gchar *path, struct stat *buf);
1060 GHashTable *basename_hash = NULL;
1062 g_assert(files || dirs);
1064 if (files) *files = NULL;
1065 if (dirs) *dirs = NULL;
1067 pathl = path_from_utf8(dir_path);
1068 if (!pathl) return FALSE;
1070 dp = opendir(pathl);
1077 if (files) basename_hash = file_data_basename_hash_new();
1079 if (follow_symlinks)
1084 while ((dir = readdir(dp)) != NULL)
1086 struct stat ent_sbuf;
1087 const gchar *name = dir->d_name;
1090 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1093 filepath = g_build_filename(pathl, name, NULL);
1094 if (stat_func(filepath, &ent_sbuf) >= 0)
1096 if (S_ISDIR(ent_sbuf.st_mode))
1098 /* we ignore the .thumbnails dir for cleanliness */
1100 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1101 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1102 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1103 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1105 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1110 if (files && filter_name_exists(name))
1112 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1113 flist = g_list_prepend(flist, fd);
1114 if (fd->sidecar_priority && !fd->disable_grouping)
1116 file_data_basename_hash_insert(basename_hash, fd);
1123 if (errno == EOVERFLOW)
1125 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1135 if (dirs) *dirs = dlist;
1139 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1141 *files = filelist_filter_out_sidecars(flist);
1143 if (basename_hash) file_data_basename_hash_free(basename_hash);
1145 // Call a separate function to initialize the exif datestamps for the found files..
1146 if (files) init_exif_time_data(*files);
1151 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1153 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1156 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1158 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1161 FileData *file_data_new_group(const gchar *path_utf8)
1168 if (!stat_utf8(path_utf8, &st))
1174 if (S_ISDIR(st.st_mode))
1175 return file_data_new(path_utf8, &st, TRUE);
1177 dir = remove_level_from_path(path_utf8);
1179 filelist_read_real(dir, &files, NULL, TRUE);
1181 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1185 filelist_free(files);
1191 void filelist_free(GList *list)
1198 file_data_unref((FileData *)work->data);
1206 GList *filelist_copy(GList *list)
1208 GList *new_list = NULL;
1219 new_list = g_list_prepend(new_list, file_data_ref(fd));
1222 return g_list_reverse(new_list);
1225 GList *filelist_from_path_list(GList *list)
1227 GList *new_list = NULL;
1238 new_list = g_list_prepend(new_list, file_data_new_group(path));
1241 return g_list_reverse(new_list);
1244 GList *filelist_to_path_list(GList *list)
1246 GList *new_list = NULL;
1257 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1260 return g_list_reverse(new_list);
1263 GList *filelist_filter(GList *list, gboolean is_dir_list)
1267 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1272 FileData *fd = (FileData *)(work->data);
1273 const gchar *name = fd->name;
1275 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1276 (!is_dir_list && !filter_name_exists(name)) ||
1277 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1278 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1282 list = g_list_remove_link(list, link);
1283 file_data_unref(fd);
1294 *-----------------------------------------------------------------------------
1295 * filelist recursive
1296 *-----------------------------------------------------------------------------
1299 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1301 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1304 GList *filelist_sort_path(GList *list)
1306 return g_list_sort(list, filelist_sort_path_cb);
1309 static void filelist_recursive_append(GList **list, GList *dirs)
1316 FileData *fd = (FileData *)(work->data);
1320 if (filelist_read(fd, &f, &d))
1322 f = filelist_filter(f, FALSE);
1323 f = filelist_sort_path(f);
1324 *list = g_list_concat(*list, f);
1326 d = filelist_filter(d, TRUE);
1327 d = filelist_sort_path(d);
1328 filelist_recursive_append(list, d);
1336 GList *filelist_recursive(FileData *dir_fd)
1341 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1342 list = filelist_filter(list, FALSE);
1343 list = filelist_sort_path(list);
1345 d = filelist_filter(d, TRUE);
1346 d = filelist_sort_path(d);
1347 filelist_recursive_append(&list, d);
1354 *-----------------------------------------------------------------------------
1355 * file modification support
1356 *-----------------------------------------------------------------------------
1360 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1362 if (!fdci && fd) fdci = fd->change;
1366 g_free(fdci->source);
1371 if (fd) fd->change = NULL;
1374 static gboolean file_data_can_write_directly(FileData *fd)
1376 return filter_name_is_writable(fd->extension);
1379 static gboolean file_data_can_write_sidecar(FileData *fd)
1381 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1384 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1386 gchar *sidecar_path = NULL;
1389 if (!file_data_can_write_sidecar(fd)) return NULL;
1391 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1394 FileData *sfd = work->data;
1396 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
1398 sidecar_path = g_strdup(sfd->path);
1403 if (!existing_only && !sidecar_path)
1405 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1406 sidecar_path = g_strconcat(base, ".xmp", NULL);
1410 return sidecar_path;
1414 * marks and orientation
1417 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1418 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1419 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1420 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1422 gboolean file_data_get_mark(FileData *fd, gint n)
1424 gboolean valid = (fd->valid_marks & (1 << n));
1426 if (file_data_get_mark_func[n] && !valid)
1428 guint old = fd->marks;
1429 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1431 if (!value != !(fd->marks & (1 << n)))
1433 fd->marks = fd->marks ^ (1 << n);
1436 fd->valid_marks |= (1 << n);
1437 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1439 file_data_unref(fd);
1441 else if (!old && fd->marks)
1447 return !!(fd->marks & (1 << n));
1450 guint file_data_get_marks(FileData *fd)
1453 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1457 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1460 if (!value == !file_data_get_mark(fd, n)) return;
1462 if (file_data_set_mark_func[n])
1464 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1469 fd->marks = fd->marks ^ (1 << n);
1471 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1473 file_data_unref(fd);
1475 else if (!old && fd->marks)
1480 file_data_increment_version(fd);
1481 file_data_send_notification(fd, NOTIFY_MARKS);
1484 gboolean file_data_filter_marks(FileData *fd, guint filter)
1487 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1488 return ((fd->marks & filter) == filter);
1491 GList *file_data_filter_marks_list(GList *list, guint filter)
1498 FileData *fd = work->data;
1502 if (!file_data_filter_marks(fd, filter))
1504 list = g_list_remove_link(list, link);
1505 file_data_unref(fd);
1513 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1515 FileData *fd = value;
1516 file_data_increment_version(fd);
1517 file_data_send_notification(fd, NOTIFY_MARKS);
1520 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1522 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1524 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1526 file_data_get_mark_func[n] = get_mark_func;
1527 file_data_set_mark_func[n] = set_mark_func;
1528 file_data_mark_func_data[n] = data;
1529 file_data_destroy_mark_func[n] = notify;
1533 /* this effectively changes all known files */
1534 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1540 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1542 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1543 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1544 if (data) *data = file_data_mark_func_data[n];
1547 gint file_data_get_user_orientation(FileData *fd)
1549 return fd->user_orientation;
1552 void file_data_set_user_orientation(FileData *fd, gint value)
1554 if (fd->user_orientation == value) return;
1556 fd->user_orientation = value;
1557 file_data_increment_version(fd);
1558 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1563 * file_data - operates on the given fd
1564 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1568 /* return list of sidecar file extensions in a string */
1569 gchar *file_data_sc_list_to_string(FileData *fd)
1572 GString *result = g_string_new("");
1574 work = fd->sidecar_files;
1577 FileData *sfd = work->data;
1579 result = g_string_append(result, "+ ");
1580 result = g_string_append(result, sfd->extension);
1582 if (work) result = g_string_append_c(result, ' ');
1585 return g_string_free(result, FALSE);
1591 * add FileDataChangeInfo (see typedefs.h) for the given operation
1592 * uses file_data_add_change_info
1594 * fails if the fd->change already exists - change operations can't run in parallel
1595 * fd->change_info works as a lock
1597 * dest can be NULL - in this case the current name is used for now, it will
1602 FileDataChangeInfo types:
1604 MOVE - path is changed, name may be changed too
1605 RENAME - path remains unchanged, name is changed
1606 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1607 sidecar names are changed too, extensions are not changed
1609 UPDATE - file size, date or grouping has been changed
1612 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1614 FileDataChangeInfo *fdci;
1616 if (fd->change) return FALSE;
1618 fdci = g_new0(FileDataChangeInfo, 1);
1623 fdci->source = g_strdup(src);
1625 fdci->source = g_strdup(fd->path);
1628 fdci->dest = g_strdup(dest);
1635 static void file_data_planned_change_remove(FileData *fd)
1637 if (file_data_planned_change_hash &&
1638 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1640 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1642 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1643 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1644 file_data_unref(fd);
1645 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1647 g_hash_table_destroy(file_data_planned_change_hash);
1648 file_data_planned_change_hash = NULL;
1649 DEBUG_1("planned change: empty");
1656 void file_data_free_ci(FileData *fd)
1658 FileDataChangeInfo *fdci = fd->change;
1662 file_data_planned_change_remove(fd);
1664 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1666 g_free(fdci->source);
1674 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1676 FileDataChangeInfo *fdci = fd->change;
1678 fdci->regroup_when_finished = enable;
1681 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1685 if (fd->parent) fd = fd->parent;
1687 if (fd->change) return FALSE;
1689 work = fd->sidecar_files;
1692 FileData *sfd = work->data;
1694 if (sfd->change) return FALSE;
1698 file_data_add_ci(fd, type, NULL, NULL);
1700 work = fd->sidecar_files;
1703 FileData *sfd = work->data;
1705 file_data_add_ci(sfd, type, NULL, NULL);
1712 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1716 if (fd->parent) fd = fd->parent;
1718 if (!fd->change || fd->change->type != type) return FALSE;
1720 work = fd->sidecar_files;
1723 FileData *sfd = work->data;
1725 if (!sfd->change || sfd->change->type != type) return FALSE;
1733 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1735 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1736 file_data_sc_update_ci_copy(fd, dest_path);
1740 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1742 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1743 file_data_sc_update_ci_move(fd, dest_path);
1747 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1749 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1750 file_data_sc_update_ci_rename(fd, dest_path);
1754 gboolean file_data_sc_add_ci_delete(FileData *fd)
1756 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1759 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1761 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1762 file_data_sc_update_ci_unspecified(fd, dest_path);
1766 gboolean file_data_add_ci_write_metadata(FileData *fd)
1768 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1771 void file_data_sc_free_ci(FileData *fd)
1775 if (fd->parent) fd = fd->parent;
1777 file_data_free_ci(fd);
1779 work = fd->sidecar_files;
1782 FileData *sfd = work->data;
1784 file_data_free_ci(sfd);
1789 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1792 gboolean ret = TRUE;
1797 FileData *fd = work->data;
1799 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1806 static void file_data_sc_revert_ci_list(GList *fd_list)
1813 FileData *fd = work->data;
1815 file_data_sc_free_ci(fd);
1820 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1827 FileData *fd = work->data;
1829 if (!func(fd, dest))
1831 file_data_sc_revert_ci_list(work->prev);
1840 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1842 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1845 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1847 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1850 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1852 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1855 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1857 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1860 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1863 gboolean ret = TRUE;
1868 FileData *fd = work->data;
1870 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1877 void file_data_free_ci_list(GList *fd_list)
1884 FileData *fd = work->data;
1886 file_data_free_ci(fd);
1891 void file_data_sc_free_ci_list(GList *fd_list)
1898 FileData *fd = work->data;
1900 file_data_sc_free_ci(fd);
1906 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1907 * fails if fd->change does not exist or the change type does not match
1910 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1912 FileDataChangeType type = fd->change->type;
1914 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1918 if (!file_data_planned_change_hash)
1919 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1921 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1923 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1924 g_hash_table_remove(file_data_planned_change_hash, old_path);
1925 file_data_unref(fd);
1928 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1933 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1934 g_hash_table_remove(file_data_planned_change_hash, new_path);
1935 file_data_unref(ofd);
1938 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1940 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1945 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1947 gchar *old_path = fd->change->dest;
1949 fd->change->dest = g_strdup(dest_path);
1950 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1954 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1956 const gchar *extension = extension_from_path(fd->change->source);
1957 gchar *base = remove_extension_from_path(dest_path);
1958 gchar *old_path = fd->change->dest;
1960 fd->change->dest = g_strconcat(base, extension, NULL);
1961 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1967 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1970 gchar *dest_path_full = NULL;
1972 if (fd->parent) fd = fd->parent;
1976 dest_path = fd->path;
1978 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1980 gchar *dir = remove_level_from_path(fd->path);
1982 dest_path_full = g_build_filename(dir, dest_path, NULL);
1984 dest_path = dest_path_full;
1986 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1988 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1989 dest_path = dest_path_full;
1992 file_data_update_ci_dest(fd, dest_path);
1994 work = fd->sidecar_files;
1997 FileData *sfd = work->data;
1999 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2003 g_free(dest_path_full);
2006 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2008 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2009 file_data_sc_update_ci(fd, dest_path);
2013 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2015 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2018 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2020 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2023 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2025 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2028 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2030 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2033 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2035 gboolean (*func)(FileData *, const gchar *))
2038 gboolean ret = TRUE;
2043 FileData *fd = work->data;
2045 if (!func(fd, dest)) ret = FALSE;
2052 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2054 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2057 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2059 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2062 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2064 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2069 * verify source and dest paths - dest image exists, etc.
2070 * it should detect all possible problems with the planned operation
2073 gint file_data_verify_ci(FileData *fd)
2075 gint ret = CHANGE_OK;
2080 DEBUG_1("Change checked: no change info: %s", fd->path);
2084 if (!isname(fd->path))
2086 /* this probably should not happen */
2087 ret |= CHANGE_NO_SRC;
2088 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2092 dir = remove_level_from_path(fd->path);
2094 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2095 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2096 fd->change->type != FILEDATA_CHANGE_RENAME &&
2097 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2100 ret |= CHANGE_WARN_UNSAVED_META;
2101 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2104 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2105 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2106 !access_file(fd->path, R_OK))
2108 ret |= CHANGE_NO_READ_PERM;
2109 DEBUG_1("Change checked: no read permission: %s", fd->path);
2111 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2112 !access_file(dir, W_OK))
2114 ret |= CHANGE_NO_WRITE_PERM_DIR;
2115 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2117 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2118 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2119 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2120 !access_file(fd->path, W_OK))
2122 ret |= CHANGE_WARN_NO_WRITE_PERM;
2123 DEBUG_1("Change checked: no write permission: %s", fd->path);
2125 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2126 - that means that there are no hard errors and warnings can be disabled
2127 - the destination is determined during the check
2129 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2131 /* determine destination file */
2132 gboolean have_dest = FALSE;
2133 gchar *dest_dir = NULL;
2135 if (options->metadata.save_in_image_file)
2137 if (file_data_can_write_directly(fd))
2139 /* we can write the file directly */
2140 if (access_file(fd->path, W_OK))
2146 if (options->metadata.warn_on_write_problems)
2148 ret |= CHANGE_WARN_NO_WRITE_PERM;
2149 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2153 else if (file_data_can_write_sidecar(fd))
2155 /* we can write sidecar */
2156 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2157 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2159 file_data_update_ci_dest(fd, sidecar);
2164 if (options->metadata.warn_on_write_problems)
2166 ret |= CHANGE_WARN_NO_WRITE_PERM;
2167 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2176 /* write private metadata file under ~/.geeqie */
2178 /* If an existing metadata file exists, we will try writing to
2179 * it's location regardless of the user's preference.
2181 gchar *metadata_path = NULL;
2183 /* but ignore XMP if we are not able to write it */
2184 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2186 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2188 if (metadata_path && !access_file(metadata_path, W_OK))
2190 g_free(metadata_path);
2191 metadata_path = NULL;
2198 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2199 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2201 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2203 metadata_path = g_build_filename(dest_dir, filename, NULL);
2207 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2209 file_data_update_ci_dest(fd, metadata_path);
2214 ret |= CHANGE_NO_WRITE_PERM_DEST;
2215 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2217 g_free(metadata_path);
2222 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2227 same = (strcmp(fd->path, fd->change->dest) == 0);
2231 const gchar *dest_ext = extension_from_path(fd->change->dest);
2232 if (!dest_ext) dest_ext = "";
2234 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2236 ret |= CHANGE_WARN_CHANGED_EXT;
2237 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2242 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2244 ret |= CHANGE_WARN_SAME;
2245 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2249 dest_dir = remove_level_from_path(fd->change->dest);
2251 if (!isdir(dest_dir))
2253 ret |= CHANGE_NO_DEST_DIR;
2254 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2256 else if (!access_file(dest_dir, W_OK))
2258 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2259 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2263 if (isfile(fd->change->dest))
2265 if (!access_file(fd->change->dest, W_OK))
2267 ret |= CHANGE_NO_WRITE_PERM_DEST;
2268 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2272 ret |= CHANGE_WARN_DEST_EXISTS;
2273 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2276 else if (isdir(fd->change->dest))
2278 ret |= CHANGE_DEST_EXISTS;
2279 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2286 fd->change->error = ret;
2287 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2294 gint file_data_sc_verify_ci(FileData *fd)
2299 ret = file_data_verify_ci(fd);
2301 work = fd->sidecar_files;
2304 FileData *sfd = work->data;
2306 ret |= file_data_verify_ci(sfd);
2313 gchar *file_data_get_error_string(gint error)
2315 GString *result = g_string_new("");
2317 if (error & CHANGE_NO_SRC)
2319 if (result->len > 0) g_string_append(result, ", ");
2320 g_string_append(result, _("file or directory does not exist"));
2323 if (error & CHANGE_DEST_EXISTS)
2325 if (result->len > 0) g_string_append(result, ", ");
2326 g_string_append(result, _("destination already exists"));
2329 if (error & CHANGE_NO_WRITE_PERM_DEST)
2331 if (result->len > 0) g_string_append(result, ", ");
2332 g_string_append(result, _("destination can't be overwritten"));
2335 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2337 if (result->len > 0) g_string_append(result, ", ");
2338 g_string_append(result, _("destination directory is not writable"));
2341 if (error & CHANGE_NO_DEST_DIR)
2343 if (result->len > 0) g_string_append(result, ", ");
2344 g_string_append(result, _("destination directory does not exist"));
2347 if (error & CHANGE_NO_WRITE_PERM_DIR)
2349 if (result->len > 0) g_string_append(result, ", ");
2350 g_string_append(result, _("source directory is not writable"));
2353 if (error & CHANGE_NO_READ_PERM)
2355 if (result->len > 0) g_string_append(result, ", ");
2356 g_string_append(result, _("no read permission"));
2359 if (error & CHANGE_WARN_NO_WRITE_PERM)
2361 if (result->len > 0) g_string_append(result, ", ");
2362 g_string_append(result, _("file is readonly"));
2365 if (error & CHANGE_WARN_DEST_EXISTS)
2367 if (result->len > 0) g_string_append(result, ", ");
2368 g_string_append(result, _("destination already exists and will be overwritten"));
2371 if (error & CHANGE_WARN_SAME)
2373 if (result->len > 0) g_string_append(result, ", ");
2374 g_string_append(result, _("source and destination are the same"));
2377 if (error & CHANGE_WARN_CHANGED_EXT)
2379 if (result->len > 0) g_string_append(result, ", ");
2380 g_string_append(result, _("source and destination have different extension"));
2383 if (error & CHANGE_WARN_UNSAVED_META)
2385 if (result->len > 0) g_string_append(result, ", ");
2386 g_string_append(result, _("there are unsaved metadata changes for the file"));
2389 return g_string_free(result, FALSE);
2392 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2395 gint all_errors = 0;
2396 gint common_errors = ~0;
2401 if (!list) return 0;
2403 num = g_list_length(list);
2404 errors = g_new(int, num);
2415 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2416 all_errors |= error;
2417 common_errors &= error;
2424 if (desc && all_errors)
2427 GString *result = g_string_new("");
2431 gchar *str = file_data_get_error_string(common_errors);
2432 g_string_append(result, str);
2433 g_string_append(result, "\n");
2447 error = errors[i] & ~common_errors;
2451 gchar *str = file_data_get_error_string(error);
2452 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2457 *desc = g_string_free(result, FALSE);
2466 * perform the change described by FileFataChangeInfo
2467 * it is used for internal operations,
2468 * this function actually operates with files on the filesystem
2469 * it should implement safe delete
2472 static gboolean file_data_perform_move(FileData *fd)
2474 g_assert(!strcmp(fd->change->source, fd->path));
2475 return move_file(fd->change->source, fd->change->dest);
2478 static gboolean file_data_perform_copy(FileData *fd)
2480 g_assert(!strcmp(fd->change->source, fd->path));
2481 return copy_file(fd->change->source, fd->change->dest);
2484 static gboolean file_data_perform_delete(FileData *fd)
2486 if (isdir(fd->path) && !islink(fd->path))
2487 return rmdir_utf8(fd->path);
2489 if (options->file_ops.safe_delete_enable)
2490 return file_util_safe_unlink(fd->path);
2492 return unlink_file(fd->path);
2495 gboolean file_data_perform_ci(FileData *fd)
2497 FileDataChangeType type = fd->change->type;
2501 case FILEDATA_CHANGE_MOVE:
2502 return file_data_perform_move(fd);
2503 case FILEDATA_CHANGE_COPY:
2504 return file_data_perform_copy(fd);
2505 case FILEDATA_CHANGE_RENAME:
2506 return file_data_perform_move(fd); /* the same as move */
2507 case FILEDATA_CHANGE_DELETE:
2508 return file_data_perform_delete(fd);
2509 case FILEDATA_CHANGE_WRITE_METADATA:
2510 return metadata_write_perform(fd);
2511 case FILEDATA_CHANGE_UNSPECIFIED:
2512 /* nothing to do here */
2520 gboolean file_data_sc_perform_ci(FileData *fd)
2523 gboolean ret = TRUE;
2524 FileDataChangeType type = fd->change->type;
2526 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2528 work = fd->sidecar_files;
2531 FileData *sfd = work->data;
2533 if (!file_data_perform_ci(sfd)) ret = FALSE;
2537 if (!file_data_perform_ci(fd)) ret = FALSE;
2543 * updates FileData structure according to FileDataChangeInfo
2546 gboolean file_data_apply_ci(FileData *fd)
2548 FileDataChangeType type = fd->change->type;
2551 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2553 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2554 file_data_planned_change_remove(fd);
2556 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2558 /* this change overwrites another file which is already known to other modules
2559 renaming fd would create duplicate FileData structure
2560 the best thing we can do is nothing
2561 FIXME: maybe we could copy stuff like marks
2563 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2567 file_data_set_path(fd, fd->change->dest);
2570 file_data_increment_version(fd);
2571 file_data_send_notification(fd, NOTIFY_CHANGE);
2576 gboolean file_data_sc_apply_ci(FileData *fd)
2579 FileDataChangeType type = fd->change->type;
2581 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2583 work = fd->sidecar_files;
2586 FileData *sfd = work->data;
2588 file_data_apply_ci(sfd);
2592 file_data_apply_ci(fd);
2597 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2600 if (fd->parent) fd = fd->parent;
2601 if (!g_list_find(list, fd)) return FALSE;
2603 work = fd->sidecar_files;
2606 if (!g_list_find(list, work->data)) return FALSE;
2613 static gboolean file_data_list_dump(GList *list)
2615 GList *work, *work2;
2620 FileData *fd = work->data;
2621 printf("%s\n", fd->name);
2622 work2 = fd->sidecar_files;
2625 FileData *fd = work2->data;
2626 printf(" %s\n", fd->name);
2627 work2 = work2->next;
2635 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2640 /* change partial groups to independent files */
2645 FileData *fd = work->data;
2648 if (!file_data_list_contains_whole_group(list, fd))
2650 file_data_disable_grouping(fd, TRUE);
2653 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2659 /* remove sidecars from the list,
2660 they can be still acessed via main_fd->sidecar_files */
2664 FileData *fd = work->data;
2668 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2670 out = g_list_prepend(out, file_data_ref(fd));
2674 filelist_free(list);
2675 out = g_list_reverse(out);
2685 * notify other modules about the change described by FileDataChangeInfo
2688 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2689 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2690 implementation in view_file_list.c */
2693 typedef struct _NotifyIdleData NotifyIdleData;
2695 struct _NotifyIdleData {
2701 typedef struct _NotifyData NotifyData;
2703 struct _NotifyData {
2704 FileDataNotifyFunc func;
2706 NotifyPriority priority;
2709 static GList *notify_func_list = NULL;
2711 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2713 NotifyData *nda = (NotifyData *)a;
2714 NotifyData *ndb = (NotifyData *)b;
2716 if (nda->priority < ndb->priority) return -1;
2717 if (nda->priority > ndb->priority) return 1;
2721 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2724 GList *work = notify_func_list;
2728 NotifyData *nd = (NotifyData *)work->data;
2730 if (nd->func == func && nd->data == data)
2732 g_warning("Notify func already registered");
2738 nd = g_new(NotifyData, 1);
2741 nd->priority = priority;
2743 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2744 DEBUG_2("Notify func registered: %p", nd);
2749 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2751 GList *work = notify_func_list;
2755 NotifyData *nd = (NotifyData *)work->data;
2757 if (nd->func == func && nd->data == data)
2759 notify_func_list = g_list_delete_link(notify_func_list, work);
2761 DEBUG_2("Notify func unregistered: %p", nd);
2767 g_warning("Notify func not found");
2772 gboolean file_data_send_notification_idle_cb(gpointer data)
2774 NotifyIdleData *nid = (NotifyIdleData *)data;
2775 GList *work = notify_func_list;
2779 NotifyData *nd = (NotifyData *)work->data;
2781 nd->func(nid->fd, nid->type, nd->data);
2784 file_data_unref(nid->fd);
2789 void file_data_send_notification(FileData *fd, NotifyType type)
2791 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
2792 nid->fd = file_data_ref(fd);
2794 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
2797 static GHashTable *file_data_monitor_pool = NULL;
2798 static guint realtime_monitor_id = 0; /* event source id */
2800 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2804 file_data_check_changed_files(fd);
2806 DEBUG_1("monitor %s", fd->path);
2809 static gboolean realtime_monitor_cb(gpointer data)
2811 if (!options->update_on_time_change) return TRUE;
2812 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2816 gboolean file_data_register_real_time_monitor(FileData *fd)
2822 if (!file_data_monitor_pool)
2823 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2825 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2827 DEBUG_1("Register realtime %d %s", count, fd->path);
2830 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2832 if (!realtime_monitor_id)
2834 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2840 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2844 g_assert(file_data_monitor_pool);
2846 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2848 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2850 g_assert(count > 0);
2855 g_hash_table_remove(file_data_monitor_pool, fd);
2857 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2859 file_data_unref(fd);
2861 if (g_hash_table_size(file_data_monitor_pool) == 0)
2863 g_source_remove(realtime_monitor_id);
2864 realtime_monitor_id = 0;
2870 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */