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);
1158 filelist_free(files);
1164 void filelist_free(GList *list)
1171 file_data_unref((FileData *)work->data);
1179 GList *filelist_copy(GList *list)
1181 GList *new_list = NULL;
1192 new_list = g_list_prepend(new_list, file_data_ref(fd));
1195 return g_list_reverse(new_list);
1198 GList *filelist_from_path_list(GList *list)
1200 GList *new_list = NULL;
1211 new_list = g_list_prepend(new_list, file_data_new_group(path));
1214 return g_list_reverse(new_list);
1217 GList *filelist_to_path_list(GList *list)
1219 GList *new_list = NULL;
1230 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1233 return g_list_reverse(new_list);
1236 GList *filelist_filter(GList *list, gboolean is_dir_list)
1240 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1245 FileData *fd = (FileData *)(work->data);
1246 const gchar *name = fd->name;
1248 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1249 (!is_dir_list && !filter_name_exists(name)) ||
1250 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1251 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1255 list = g_list_remove_link(list, link);
1256 file_data_unref(fd);
1267 *-----------------------------------------------------------------------------
1268 * filelist recursive
1269 *-----------------------------------------------------------------------------
1272 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1274 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1277 GList *filelist_sort_path(GList *list)
1279 return g_list_sort(list, filelist_sort_path_cb);
1282 static void filelist_recursive_append(GList **list, GList *dirs)
1289 FileData *fd = (FileData *)(work->data);
1293 if (filelist_read(fd, &f, &d))
1295 f = filelist_filter(f, FALSE);
1296 f = filelist_sort_path(f);
1297 *list = g_list_concat(*list, f);
1299 d = filelist_filter(d, TRUE);
1300 d = filelist_sort_path(d);
1301 filelist_recursive_append(list, d);
1309 GList *filelist_recursive(FileData *dir_fd)
1314 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1315 list = filelist_filter(list, FALSE);
1316 list = filelist_sort_path(list);
1318 d = filelist_filter(d, TRUE);
1319 d = filelist_sort_path(d);
1320 filelist_recursive_append(&list, d);
1327 *-----------------------------------------------------------------------------
1328 * file modification support
1329 *-----------------------------------------------------------------------------
1333 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1335 if (!fdci && fd) fdci = fd->change;
1339 g_free(fdci->source);
1344 if (fd) fd->change = NULL;
1347 static gboolean file_data_can_write_directly(FileData *fd)
1349 return filter_name_is_writable(fd->extension);
1352 static gboolean file_data_can_write_sidecar(FileData *fd)
1354 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1357 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1359 gchar *sidecar_path = NULL;
1362 if (!file_data_can_write_sidecar(fd)) return NULL;
1364 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1367 FileData *sfd = work->data;
1369 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
1371 sidecar_path = g_strdup(sfd->path);
1376 if (!existing_only && !sidecar_path)
1378 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1379 sidecar_path = g_strconcat(base, ".xmp", NULL);
1383 return sidecar_path;
1387 * marks and orientation
1390 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1391 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1392 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1393 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1395 gboolean file_data_get_mark(FileData *fd, gint n)
1397 gboolean valid = (fd->valid_marks & (1 << n));
1399 if (file_data_get_mark_func[n] && !valid)
1401 guint old = fd->marks;
1402 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1404 if (!value != !(fd->marks & (1 << n)))
1406 fd->marks = fd->marks ^ (1 << n);
1409 fd->valid_marks |= (1 << n);
1410 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1412 file_data_unref(fd);
1414 else if (!old && fd->marks)
1420 return !!(fd->marks & (1 << n));
1423 guint file_data_get_marks(FileData *fd)
1426 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1430 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1433 if (!value == !file_data_get_mark(fd, n)) return;
1435 if (file_data_set_mark_func[n])
1437 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1442 fd->marks = fd->marks ^ (1 << n);
1444 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1446 file_data_unref(fd);
1448 else if (!old && fd->marks)
1453 file_data_increment_version(fd);
1454 file_data_send_notification(fd, NOTIFY_MARKS);
1457 gboolean file_data_filter_marks(FileData *fd, guint filter)
1460 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1461 return ((fd->marks & filter) == filter);
1464 GList *file_data_filter_marks_list(GList *list, guint filter)
1471 FileData *fd = work->data;
1475 if (!file_data_filter_marks(fd, filter))
1477 list = g_list_remove_link(list, link);
1478 file_data_unref(fd);
1486 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1488 FileData *fd = value;
1489 file_data_increment_version(fd);
1490 file_data_send_notification(fd, NOTIFY_MARKS);
1493 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1495 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1497 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1499 file_data_get_mark_func[n] = get_mark_func;
1500 file_data_set_mark_func[n] = set_mark_func;
1501 file_data_mark_func_data[n] = data;
1502 file_data_destroy_mark_func[n] = notify;
1506 /* this effectively changes all known files */
1507 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1513 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1515 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1516 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1517 if (data) *data = file_data_mark_func_data[n];
1520 gint file_data_get_user_orientation(FileData *fd)
1522 return fd->user_orientation;
1525 void file_data_set_user_orientation(FileData *fd, gint value)
1527 if (fd->user_orientation == value) return;
1529 fd->user_orientation = value;
1530 file_data_increment_version(fd);
1531 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1536 * file_data - operates on the given fd
1537 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1541 /* return list of sidecar file extensions in a string */
1542 gchar *file_data_sc_list_to_string(FileData *fd)
1545 GString *result = g_string_new("");
1547 work = fd->sidecar_files;
1550 FileData *sfd = work->data;
1552 result = g_string_append(result, "+ ");
1553 result = g_string_append(result, sfd->extension);
1555 if (work) result = g_string_append_c(result, ' ');
1558 return g_string_free(result, FALSE);
1564 * add FileDataChangeInfo (see typedefs.h) for the given operation
1565 * uses file_data_add_change_info
1567 * fails if the fd->change already exists - change operations can't run in parallel
1568 * fd->change_info works as a lock
1570 * dest can be NULL - in this case the current name is used for now, it will
1575 FileDataChangeInfo types:
1577 MOVE - path is changed, name may be changed too
1578 RENAME - path remains unchanged, name is changed
1579 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1580 sidecar names are changed too, extensions are not changed
1582 UPDATE - file size, date or grouping has been changed
1585 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1587 FileDataChangeInfo *fdci;
1589 if (fd->change) return FALSE;
1591 fdci = g_new0(FileDataChangeInfo, 1);
1596 fdci->source = g_strdup(src);
1598 fdci->source = g_strdup(fd->path);
1601 fdci->dest = g_strdup(dest);
1608 static void file_data_planned_change_remove(FileData *fd)
1610 if (file_data_planned_change_hash &&
1611 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1613 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1615 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1616 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1617 file_data_unref(fd);
1618 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1620 g_hash_table_destroy(file_data_planned_change_hash);
1621 file_data_planned_change_hash = NULL;
1622 DEBUG_1("planned change: empty");
1629 void file_data_free_ci(FileData *fd)
1631 FileDataChangeInfo *fdci = fd->change;
1635 file_data_planned_change_remove(fd);
1637 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1639 g_free(fdci->source);
1647 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1649 FileDataChangeInfo *fdci = fd->change;
1651 fdci->regroup_when_finished = enable;
1654 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1658 if (fd->parent) fd = fd->parent;
1660 if (fd->change) return FALSE;
1662 work = fd->sidecar_files;
1665 FileData *sfd = work->data;
1667 if (sfd->change) return FALSE;
1671 file_data_add_ci(fd, type, NULL, NULL);
1673 work = fd->sidecar_files;
1676 FileData *sfd = work->data;
1678 file_data_add_ci(sfd, type, NULL, NULL);
1685 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1689 if (fd->parent) fd = fd->parent;
1691 if (!fd->change || fd->change->type != type) return FALSE;
1693 work = fd->sidecar_files;
1696 FileData *sfd = work->data;
1698 if (!sfd->change || sfd->change->type != type) return FALSE;
1706 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1708 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1709 file_data_sc_update_ci_copy(fd, dest_path);
1713 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1715 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1716 file_data_sc_update_ci_move(fd, dest_path);
1720 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1722 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1723 file_data_sc_update_ci_rename(fd, dest_path);
1727 gboolean file_data_sc_add_ci_delete(FileData *fd)
1729 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1732 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1734 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1735 file_data_sc_update_ci_unspecified(fd, dest_path);
1739 gboolean file_data_add_ci_write_metadata(FileData *fd)
1741 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1744 void file_data_sc_free_ci(FileData *fd)
1748 if (fd->parent) fd = fd->parent;
1750 file_data_free_ci(fd);
1752 work = fd->sidecar_files;
1755 FileData *sfd = work->data;
1757 file_data_free_ci(sfd);
1762 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1765 gboolean ret = TRUE;
1770 FileData *fd = work->data;
1772 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1779 static void file_data_sc_revert_ci_list(GList *fd_list)
1786 FileData *fd = work->data;
1788 file_data_sc_free_ci(fd);
1793 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1800 FileData *fd = work->data;
1802 if (!func(fd, dest))
1804 file_data_sc_revert_ci_list(work->prev);
1813 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1815 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1818 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1820 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1823 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1825 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1828 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1830 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1833 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1836 gboolean ret = TRUE;
1841 FileData *fd = work->data;
1843 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1850 void file_data_free_ci_list(GList *fd_list)
1857 FileData *fd = work->data;
1859 file_data_free_ci(fd);
1864 void file_data_sc_free_ci_list(GList *fd_list)
1871 FileData *fd = work->data;
1873 file_data_sc_free_ci(fd);
1879 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1880 * fails if fd->change does not exist or the change type does not match
1883 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1885 FileDataChangeType type = fd->change->type;
1887 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1891 if (!file_data_planned_change_hash)
1892 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1894 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1896 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1897 g_hash_table_remove(file_data_planned_change_hash, old_path);
1898 file_data_unref(fd);
1901 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1906 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1907 g_hash_table_remove(file_data_planned_change_hash, new_path);
1908 file_data_unref(ofd);
1911 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1913 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1918 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1920 gchar *old_path = fd->change->dest;
1922 fd->change->dest = g_strdup(dest_path);
1923 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1927 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1929 const gchar *extension = extension_from_path(fd->change->source);
1930 gchar *base = remove_extension_from_path(dest_path);
1931 gchar *old_path = fd->change->dest;
1933 fd->change->dest = g_strconcat(base, extension, NULL);
1934 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1940 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1943 gchar *dest_path_full = NULL;
1945 if (fd->parent) fd = fd->parent;
1949 dest_path = fd->path;
1951 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1953 gchar *dir = remove_level_from_path(fd->path);
1955 dest_path_full = g_build_filename(dir, dest_path, NULL);
1957 dest_path = dest_path_full;
1959 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1961 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1962 dest_path = dest_path_full;
1965 file_data_update_ci_dest(fd, dest_path);
1967 work = fd->sidecar_files;
1970 FileData *sfd = work->data;
1972 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1976 g_free(dest_path_full);
1979 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1981 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1982 file_data_sc_update_ci(fd, dest_path);
1986 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1988 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1991 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1993 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1996 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1998 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2001 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2003 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2006 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2008 gboolean (*func)(FileData *, const gchar *))
2011 gboolean ret = TRUE;
2016 FileData *fd = work->data;
2018 if (!func(fd, dest)) ret = FALSE;
2025 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2027 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2030 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2032 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2035 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2037 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2042 * verify source and dest paths - dest image exists, etc.
2043 * it should detect all possible problems with the planned operation
2046 gint file_data_verify_ci(FileData *fd)
2048 gint ret = CHANGE_OK;
2053 DEBUG_1("Change checked: no change info: %s", fd->path);
2057 if (!isname(fd->path))
2059 /* this probably should not happen */
2060 ret |= CHANGE_NO_SRC;
2061 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2065 dir = remove_level_from_path(fd->path);
2067 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2068 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2069 fd->change->type != FILEDATA_CHANGE_RENAME &&
2070 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2073 ret |= CHANGE_WARN_UNSAVED_META;
2074 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2077 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2078 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2079 !access_file(fd->path, R_OK))
2081 ret |= CHANGE_NO_READ_PERM;
2082 DEBUG_1("Change checked: no read permission: %s", fd->path);
2084 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2085 !access_file(dir, W_OK))
2087 ret |= CHANGE_NO_WRITE_PERM_DIR;
2088 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2090 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2091 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2092 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2093 !access_file(fd->path, W_OK))
2095 ret |= CHANGE_WARN_NO_WRITE_PERM;
2096 DEBUG_1("Change checked: no write permission: %s", fd->path);
2098 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2099 - that means that there are no hard errors and warnings can be disabled
2100 - the destination is determined during the check
2102 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2104 /* determine destination file */
2105 gboolean have_dest = FALSE;
2106 gchar *dest_dir = NULL;
2108 if (options->metadata.save_in_image_file)
2110 if (file_data_can_write_directly(fd))
2112 /* we can write the file directly */
2113 if (access_file(fd->path, W_OK))
2119 if (options->metadata.warn_on_write_problems)
2121 ret |= CHANGE_WARN_NO_WRITE_PERM;
2122 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2126 else if (file_data_can_write_sidecar(fd))
2128 /* we can write sidecar */
2129 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2130 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2132 file_data_update_ci_dest(fd, sidecar);
2137 if (options->metadata.warn_on_write_problems)
2139 ret |= CHANGE_WARN_NO_WRITE_PERM;
2140 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2149 /* write private metadata file under ~/.geeqie */
2151 /* If an existing metadata file exists, we will try writing to
2152 * it's location regardless of the user's preference.
2154 gchar *metadata_path = NULL;
2156 /* but ignore XMP if we are not able to write it */
2157 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2159 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2161 if (metadata_path && !access_file(metadata_path, W_OK))
2163 g_free(metadata_path);
2164 metadata_path = NULL;
2171 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2172 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2174 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2176 metadata_path = g_build_filename(dest_dir, filename, NULL);
2180 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2182 file_data_update_ci_dest(fd, metadata_path);
2187 ret |= CHANGE_NO_WRITE_PERM_DEST;
2188 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2190 g_free(metadata_path);
2195 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2200 same = (strcmp(fd->path, fd->change->dest) == 0);
2204 const gchar *dest_ext = extension_from_path(fd->change->dest);
2205 if (!dest_ext) dest_ext = "";
2207 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2209 ret |= CHANGE_WARN_CHANGED_EXT;
2210 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2215 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2217 ret |= CHANGE_WARN_SAME;
2218 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2222 dest_dir = remove_level_from_path(fd->change->dest);
2224 if (!isdir(dest_dir))
2226 ret |= CHANGE_NO_DEST_DIR;
2227 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2229 else if (!access_file(dest_dir, W_OK))
2231 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2232 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2236 if (isfile(fd->change->dest))
2238 if (!access_file(fd->change->dest, W_OK))
2240 ret |= CHANGE_NO_WRITE_PERM_DEST;
2241 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2245 ret |= CHANGE_WARN_DEST_EXISTS;
2246 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2249 else if (isdir(fd->change->dest))
2251 ret |= CHANGE_DEST_EXISTS;
2252 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2259 fd->change->error = ret;
2260 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2267 gint file_data_sc_verify_ci(FileData *fd)
2272 ret = file_data_verify_ci(fd);
2274 work = fd->sidecar_files;
2277 FileData *sfd = work->data;
2279 ret |= file_data_verify_ci(sfd);
2286 gchar *file_data_get_error_string(gint error)
2288 GString *result = g_string_new("");
2290 if (error & CHANGE_NO_SRC)
2292 if (result->len > 0) g_string_append(result, ", ");
2293 g_string_append(result, _("file or directory does not exist"));
2296 if (error & CHANGE_DEST_EXISTS)
2298 if (result->len > 0) g_string_append(result, ", ");
2299 g_string_append(result, _("destination already exists"));
2302 if (error & CHANGE_NO_WRITE_PERM_DEST)
2304 if (result->len > 0) g_string_append(result, ", ");
2305 g_string_append(result, _("destination can't be overwritten"));
2308 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2310 if (result->len > 0) g_string_append(result, ", ");
2311 g_string_append(result, _("destination directory is not writable"));
2314 if (error & CHANGE_NO_DEST_DIR)
2316 if (result->len > 0) g_string_append(result, ", ");
2317 g_string_append(result, _("destination directory does not exist"));
2320 if (error & CHANGE_NO_WRITE_PERM_DIR)
2322 if (result->len > 0) g_string_append(result, ", ");
2323 g_string_append(result, _("source directory is not writable"));
2326 if (error & CHANGE_NO_READ_PERM)
2328 if (result->len > 0) g_string_append(result, ", ");
2329 g_string_append(result, _("no read permission"));
2332 if (error & CHANGE_WARN_NO_WRITE_PERM)
2334 if (result->len > 0) g_string_append(result, ", ");
2335 g_string_append(result, _("file is readonly"));
2338 if (error & CHANGE_WARN_DEST_EXISTS)
2340 if (result->len > 0) g_string_append(result, ", ");
2341 g_string_append(result, _("destination already exists and will be overwritten"));
2344 if (error & CHANGE_WARN_SAME)
2346 if (result->len > 0) g_string_append(result, ", ");
2347 g_string_append(result, _("source and destination are the same"));
2350 if (error & CHANGE_WARN_CHANGED_EXT)
2352 if (result->len > 0) g_string_append(result, ", ");
2353 g_string_append(result, _("source and destination have different extension"));
2356 if (error & CHANGE_WARN_UNSAVED_META)
2358 if (result->len > 0) g_string_append(result, ", ");
2359 g_string_append(result, _("there are unsaved metadata changes for the file"));
2362 return g_string_free(result, FALSE);
2365 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2368 gint all_errors = 0;
2369 gint common_errors = ~0;
2374 if (!list) return 0;
2376 num = g_list_length(list);
2377 errors = g_new(int, num);
2388 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2389 all_errors |= error;
2390 common_errors &= error;
2397 if (desc && all_errors)
2400 GString *result = g_string_new("");
2404 gchar *str = file_data_get_error_string(common_errors);
2405 g_string_append(result, str);
2406 g_string_append(result, "\n");
2420 error = errors[i] & ~common_errors;
2424 gchar *str = file_data_get_error_string(error);
2425 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2430 *desc = g_string_free(result, FALSE);
2439 * perform the change described by FileFataChangeInfo
2440 * it is used for internal operations,
2441 * this function actually operates with files on the filesystem
2442 * it should implement safe delete
2445 static gboolean file_data_perform_move(FileData *fd)
2447 g_assert(!strcmp(fd->change->source, fd->path));
2448 return move_file(fd->change->source, fd->change->dest);
2451 static gboolean file_data_perform_copy(FileData *fd)
2453 g_assert(!strcmp(fd->change->source, fd->path));
2454 return copy_file(fd->change->source, fd->change->dest);
2457 static gboolean file_data_perform_delete(FileData *fd)
2459 if (isdir(fd->path) && !islink(fd->path))
2460 return rmdir_utf8(fd->path);
2462 if (options->file_ops.safe_delete_enable)
2463 return file_util_safe_unlink(fd->path);
2465 return unlink_file(fd->path);
2468 gboolean file_data_perform_ci(FileData *fd)
2470 FileDataChangeType type = fd->change->type;
2474 case FILEDATA_CHANGE_MOVE:
2475 return file_data_perform_move(fd);
2476 case FILEDATA_CHANGE_COPY:
2477 return file_data_perform_copy(fd);
2478 case FILEDATA_CHANGE_RENAME:
2479 return file_data_perform_move(fd); /* the same as move */
2480 case FILEDATA_CHANGE_DELETE:
2481 return file_data_perform_delete(fd);
2482 case FILEDATA_CHANGE_WRITE_METADATA:
2483 return metadata_write_perform(fd);
2484 case FILEDATA_CHANGE_UNSPECIFIED:
2485 /* nothing to do here */
2493 gboolean file_data_sc_perform_ci(FileData *fd)
2496 gboolean ret = TRUE;
2497 FileDataChangeType type = fd->change->type;
2499 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2501 work = fd->sidecar_files;
2504 FileData *sfd = work->data;
2506 if (!file_data_perform_ci(sfd)) ret = FALSE;
2510 if (!file_data_perform_ci(fd)) ret = FALSE;
2516 * updates FileData structure according to FileDataChangeInfo
2519 gboolean file_data_apply_ci(FileData *fd)
2521 FileDataChangeType type = fd->change->type;
2524 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2526 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2527 file_data_planned_change_remove(fd);
2529 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2531 /* this change overwrites another file which is already known to other modules
2532 renaming fd would create duplicate FileData structure
2533 the best thing we can do is nothing
2534 FIXME: maybe we could copy stuff like marks
2536 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2540 file_data_set_path(fd, fd->change->dest);
2543 file_data_increment_version(fd);
2544 file_data_send_notification(fd, NOTIFY_CHANGE);
2549 gboolean file_data_sc_apply_ci(FileData *fd)
2552 FileDataChangeType type = fd->change->type;
2554 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2556 work = fd->sidecar_files;
2559 FileData *sfd = work->data;
2561 file_data_apply_ci(sfd);
2565 file_data_apply_ci(fd);
2570 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2573 if (fd->parent) fd = fd->parent;
2574 if (!g_list_find(list, fd)) return FALSE;
2576 work = fd->sidecar_files;
2579 if (!g_list_find(list, work->data)) return FALSE;
2585 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2590 /* change partial groups to independent files */
2595 FileData *fd = work->data;
2598 if (!file_data_list_contains_whole_group(list, fd))
2600 file_data_disable_grouping(fd, TRUE);
2603 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2609 /* remove sidecars from the list,
2610 they can be still acessed via main_fd->sidecar_files */
2614 FileData *fd = work->data;
2618 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2620 out = g_list_prepend(out, file_data_ref(fd));
2624 filelist_free(list);
2625 out = g_list_reverse(out);
2635 * notify other modules about the change described by FileDataChangeInfo
2638 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2639 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2640 implementation in view_file_list.c */
2643 typedef struct _NotifyIdleData NotifyIdleData;
2645 struct _NotifyIdleData {
2651 typedef struct _NotifyData NotifyData;
2653 struct _NotifyData {
2654 FileDataNotifyFunc func;
2656 NotifyPriority priority;
2659 static GList *notify_func_list = NULL;
2661 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2663 NotifyData *nda = (NotifyData *)a;
2664 NotifyData *ndb = (NotifyData *)b;
2666 if (nda->priority < ndb->priority) return -1;
2667 if (nda->priority > ndb->priority) return 1;
2671 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2674 GList *work = notify_func_list;
2678 NotifyData *nd = (NotifyData *)work->data;
2680 if (nd->func == func && nd->data == data)
2682 g_warning("Notify func already registered");
2688 nd = g_new(NotifyData, 1);
2691 nd->priority = priority;
2693 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2694 DEBUG_2("Notify func registered: %p", nd);
2699 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2701 GList *work = notify_func_list;
2705 NotifyData *nd = (NotifyData *)work->data;
2707 if (nd->func == func && nd->data == data)
2709 notify_func_list = g_list_delete_link(notify_func_list, work);
2711 DEBUG_2("Notify func unregistered: %p", nd);
2717 g_warning("Notify func not found");
2722 gboolean file_data_send_notification_idle_cb(gpointer data)
2724 NotifyIdleData *nid = (NotifyIdleData *)data;
2725 GList *work = notify_func_list;
2729 NotifyData *nd = (NotifyData *)work->data;
2731 nd->func(nid->fd, nid->type, nd->data);
2734 file_data_unref(nid->fd);
2739 void file_data_send_notification(FileData *fd, NotifyType type)
2741 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
2742 nid->fd = file_data_ref(fd);
2744 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
2747 static GHashTable *file_data_monitor_pool = NULL;
2748 static guint realtime_monitor_id = 0; /* event source id */
2750 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2754 file_data_check_changed_files(fd);
2756 DEBUG_1("monitor %s", fd->path);
2759 static gboolean realtime_monitor_cb(gpointer data)
2761 if (!options->update_on_time_change) return TRUE;
2762 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2766 gboolean file_data_register_real_time_monitor(FileData *fd)
2772 if (!file_data_monitor_pool)
2773 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2775 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2777 DEBUG_1("Register realtime %d %s", count, fd->path);
2780 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2782 if (!realtime_monitor_id)
2784 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2790 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2794 g_assert(file_data_monitor_pool);
2796 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2798 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2800 g_assert(count > 0);
2805 g_hash_table_remove(file_data_monitor_pool, fd);
2807 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2809 file_data_unref(fd);
2811 if (g_hash_table_size(file_data_monitor_pool) == 0)
2813 g_source_remove(realtime_monitor_id);
2814 realtime_monitor_id = 0;
2820 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */