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);
1156 filelist_free(files);
1162 void filelist_free(GList *list)
1169 file_data_unref((FileData *)work->data);
1177 GList *filelist_copy(GList *list)
1179 GList *new_list = NULL;
1190 new_list = g_list_prepend(new_list, file_data_ref(fd));
1193 return g_list_reverse(new_list);
1196 GList *filelist_from_path_list(GList *list)
1198 GList *new_list = NULL;
1209 new_list = g_list_prepend(new_list, file_data_new_group(path));
1212 return g_list_reverse(new_list);
1215 GList *filelist_to_path_list(GList *list)
1217 GList *new_list = NULL;
1228 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1231 return g_list_reverse(new_list);
1234 GList *filelist_filter(GList *list, gboolean is_dir_list)
1238 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1243 FileData *fd = (FileData *)(work->data);
1244 const gchar *name = fd->name;
1246 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1247 (!is_dir_list && !filter_name_exists(name)) ||
1248 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1249 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1253 list = g_list_remove_link(list, link);
1254 file_data_unref(fd);
1265 *-----------------------------------------------------------------------------
1266 * filelist recursive
1267 *-----------------------------------------------------------------------------
1270 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1272 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1275 GList *filelist_sort_path(GList *list)
1277 return g_list_sort(list, filelist_sort_path_cb);
1280 static void filelist_recursive_append(GList **list, GList *dirs)
1287 FileData *fd = (FileData *)(work->data);
1291 if (filelist_read(fd, &f, &d))
1293 f = filelist_filter(f, FALSE);
1294 f = filelist_sort_path(f);
1295 *list = g_list_concat(*list, f);
1297 d = filelist_filter(d, TRUE);
1298 d = filelist_sort_path(d);
1299 filelist_recursive_append(list, d);
1307 GList *filelist_recursive(FileData *dir_fd)
1312 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1313 list = filelist_filter(list, FALSE);
1314 list = filelist_sort_path(list);
1316 d = filelist_filter(d, TRUE);
1317 d = filelist_sort_path(d);
1318 filelist_recursive_append(&list, d);
1325 *-----------------------------------------------------------------------------
1326 * file modification support
1327 *-----------------------------------------------------------------------------
1331 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1333 if (!fdci && fd) fdci = fd->change;
1337 g_free(fdci->source);
1342 if (fd) fd->change = NULL;
1345 static gboolean file_data_can_write_directly(FileData *fd)
1347 return filter_name_is_writable(fd->extension);
1350 static gboolean file_data_can_write_sidecar(FileData *fd)
1352 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1355 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1357 gchar *sidecar_path = NULL;
1360 if (!file_data_can_write_sidecar(fd)) return NULL;
1362 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1365 FileData *sfd = work->data;
1367 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
1369 sidecar_path = g_strdup(sfd->path);
1374 if (!existing_only && !sidecar_path)
1376 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1377 sidecar_path = g_strconcat(base, ".xmp", NULL);
1381 return sidecar_path;
1385 * marks and orientation
1388 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1389 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1390 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1391 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1393 gboolean file_data_get_mark(FileData *fd, gint n)
1395 gboolean valid = (fd->valid_marks & (1 << n));
1397 if (file_data_get_mark_func[n] && !valid)
1399 guint old = fd->marks;
1400 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1402 if (!value != !(fd->marks & (1 << n)))
1404 fd->marks = fd->marks ^ (1 << n);
1407 fd->valid_marks |= (1 << n);
1408 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1410 file_data_unref(fd);
1412 else if (!old && fd->marks)
1418 return !!(fd->marks & (1 << n));
1421 guint file_data_get_marks(FileData *fd)
1424 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1428 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1431 if (!value == !file_data_get_mark(fd, n)) return;
1433 if (file_data_set_mark_func[n])
1435 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1440 fd->marks = fd->marks ^ (1 << n);
1442 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1444 file_data_unref(fd);
1446 else if (!old && fd->marks)
1451 file_data_increment_version(fd);
1452 file_data_send_notification(fd, NOTIFY_MARKS);
1455 gboolean file_data_filter_marks(FileData *fd, guint filter)
1458 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1459 return ((fd->marks & filter) == filter);
1462 GList *file_data_filter_marks_list(GList *list, guint filter)
1469 FileData *fd = work->data;
1473 if (!file_data_filter_marks(fd, filter))
1475 list = g_list_remove_link(list, link);
1476 file_data_unref(fd);
1484 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1486 FileData *fd = value;
1487 file_data_increment_version(fd);
1488 file_data_send_notification(fd, NOTIFY_MARKS);
1491 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1493 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1495 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1497 file_data_get_mark_func[n] = get_mark_func;
1498 file_data_set_mark_func[n] = set_mark_func;
1499 file_data_mark_func_data[n] = data;
1500 file_data_destroy_mark_func[n] = notify;
1504 /* this effectively changes all known files */
1505 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1511 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1513 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1514 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1515 if (data) *data = file_data_mark_func_data[n];
1518 gint file_data_get_user_orientation(FileData *fd)
1520 return fd->user_orientation;
1523 void file_data_set_user_orientation(FileData *fd, gint value)
1525 if (fd->user_orientation == value) return;
1527 fd->user_orientation = value;
1528 file_data_increment_version(fd);
1529 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1534 * file_data - operates on the given fd
1535 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1539 /* return list of sidecar file extensions in a string */
1540 gchar *file_data_sc_list_to_string(FileData *fd)
1543 GString *result = g_string_new("");
1545 work = fd->sidecar_files;
1548 FileData *sfd = work->data;
1550 result = g_string_append(result, "+ ");
1551 result = g_string_append(result, sfd->extension);
1553 if (work) result = g_string_append_c(result, ' ');
1556 return g_string_free(result, FALSE);
1562 * add FileDataChangeInfo (see typedefs.h) for the given operation
1563 * uses file_data_add_change_info
1565 * fails if the fd->change already exists - change operations can't run in parallel
1566 * fd->change_info works as a lock
1568 * dest can be NULL - in this case the current name is used for now, it will
1573 FileDataChangeInfo types:
1575 MOVE - path is changed, name may be changed too
1576 RENAME - path remains unchanged, name is changed
1577 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1578 sidecar names are changed too, extensions are not changed
1580 UPDATE - file size, date or grouping has been changed
1583 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1585 FileDataChangeInfo *fdci;
1587 if (fd->change) return FALSE;
1589 fdci = g_new0(FileDataChangeInfo, 1);
1594 fdci->source = g_strdup(src);
1596 fdci->source = g_strdup(fd->path);
1599 fdci->dest = g_strdup(dest);
1606 static void file_data_planned_change_remove(FileData *fd)
1608 if (file_data_planned_change_hash &&
1609 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1611 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1613 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1614 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1615 file_data_unref(fd);
1616 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1618 g_hash_table_destroy(file_data_planned_change_hash);
1619 file_data_planned_change_hash = NULL;
1620 DEBUG_1("planned change: empty");
1627 void file_data_free_ci(FileData *fd)
1629 FileDataChangeInfo *fdci = fd->change;
1633 file_data_planned_change_remove(fd);
1635 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1637 g_free(fdci->source);
1645 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1647 FileDataChangeInfo *fdci = fd->change;
1649 fdci->regroup_when_finished = enable;
1652 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1656 if (fd->parent) fd = fd->parent;
1658 if (fd->change) return FALSE;
1660 work = fd->sidecar_files;
1663 FileData *sfd = work->data;
1665 if (sfd->change) return FALSE;
1669 file_data_add_ci(fd, type, NULL, NULL);
1671 work = fd->sidecar_files;
1674 FileData *sfd = work->data;
1676 file_data_add_ci(sfd, type, NULL, NULL);
1683 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1687 if (fd->parent) fd = fd->parent;
1689 if (!fd->change || fd->change->type != type) return FALSE;
1691 work = fd->sidecar_files;
1694 FileData *sfd = work->data;
1696 if (!sfd->change || sfd->change->type != type) return FALSE;
1704 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1706 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1707 file_data_sc_update_ci_copy(fd, dest_path);
1711 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1713 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1714 file_data_sc_update_ci_move(fd, dest_path);
1718 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1720 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1721 file_data_sc_update_ci_rename(fd, dest_path);
1725 gboolean file_data_sc_add_ci_delete(FileData *fd)
1727 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1730 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1732 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1733 file_data_sc_update_ci_unspecified(fd, dest_path);
1737 gboolean file_data_add_ci_write_metadata(FileData *fd)
1739 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1742 void file_data_sc_free_ci(FileData *fd)
1746 if (fd->parent) fd = fd->parent;
1748 file_data_free_ci(fd);
1750 work = fd->sidecar_files;
1753 FileData *sfd = work->data;
1755 file_data_free_ci(sfd);
1760 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1763 gboolean ret = TRUE;
1768 FileData *fd = work->data;
1770 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1777 static void file_data_sc_revert_ci_list(GList *fd_list)
1784 FileData *fd = work->data;
1786 file_data_sc_free_ci(fd);
1791 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1798 FileData *fd = work->data;
1800 if (!func(fd, dest))
1802 file_data_sc_revert_ci_list(work->prev);
1811 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1813 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1816 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1818 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1821 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1823 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1826 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1828 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1831 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1834 gboolean ret = TRUE;
1839 FileData *fd = work->data;
1841 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1848 void file_data_free_ci_list(GList *fd_list)
1855 FileData *fd = work->data;
1857 file_data_free_ci(fd);
1862 void file_data_sc_free_ci_list(GList *fd_list)
1869 FileData *fd = work->data;
1871 file_data_sc_free_ci(fd);
1877 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1878 * fails if fd->change does not exist or the change type does not match
1881 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1883 FileDataChangeType type = fd->change->type;
1885 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1889 if (!file_data_planned_change_hash)
1890 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1892 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1894 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1895 g_hash_table_remove(file_data_planned_change_hash, old_path);
1896 file_data_unref(fd);
1899 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1904 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1905 g_hash_table_remove(file_data_planned_change_hash, new_path);
1906 file_data_unref(ofd);
1909 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1911 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1916 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1918 gchar *old_path = fd->change->dest;
1920 fd->change->dest = g_strdup(dest_path);
1921 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1925 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1927 const gchar *extension = extension_from_path(fd->change->source);
1928 gchar *base = remove_extension_from_path(dest_path);
1929 gchar *old_path = fd->change->dest;
1931 fd->change->dest = g_strconcat(base, extension, NULL);
1932 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1938 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1941 gchar *dest_path_full = NULL;
1943 if (fd->parent) fd = fd->parent;
1947 dest_path = fd->path;
1949 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1951 gchar *dir = remove_level_from_path(fd->path);
1953 dest_path_full = g_build_filename(dir, dest_path, NULL);
1955 dest_path = dest_path_full;
1957 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1959 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1960 dest_path = dest_path_full;
1963 file_data_update_ci_dest(fd, dest_path);
1965 work = fd->sidecar_files;
1968 FileData *sfd = work->data;
1970 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1974 g_free(dest_path_full);
1977 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1979 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1980 file_data_sc_update_ci(fd, dest_path);
1984 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1986 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1989 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1991 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1994 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1996 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1999 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2001 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2004 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2006 gboolean (*func)(FileData *, const gchar *))
2009 gboolean ret = TRUE;
2014 FileData *fd = work->data;
2016 if (!func(fd, dest)) ret = FALSE;
2023 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2025 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2028 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2030 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2033 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2035 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2040 * verify source and dest paths - dest image exists, etc.
2041 * it should detect all possible problems with the planned operation
2044 gint file_data_verify_ci(FileData *fd)
2046 gint ret = CHANGE_OK;
2051 DEBUG_1("Change checked: no change info: %s", fd->path);
2055 if (!isname(fd->path))
2057 /* this probably should not happen */
2058 ret |= CHANGE_NO_SRC;
2059 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2063 dir = remove_level_from_path(fd->path);
2065 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2066 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2067 fd->change->type != FILEDATA_CHANGE_RENAME &&
2068 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2071 ret |= CHANGE_WARN_UNSAVED_META;
2072 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2075 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2076 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2077 !access_file(fd->path, R_OK))
2079 ret |= CHANGE_NO_READ_PERM;
2080 DEBUG_1("Change checked: no read permission: %s", fd->path);
2082 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2083 !access_file(dir, W_OK))
2085 ret |= CHANGE_NO_WRITE_PERM_DIR;
2086 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2088 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2089 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2090 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2091 !access_file(fd->path, W_OK))
2093 ret |= CHANGE_WARN_NO_WRITE_PERM;
2094 DEBUG_1("Change checked: no write permission: %s", fd->path);
2096 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2097 - that means that there are no hard errors and warnings can be disabled
2098 - the destination is determined during the check
2100 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2102 /* determine destination file */
2103 gboolean have_dest = FALSE;
2104 gchar *dest_dir = NULL;
2106 if (options->metadata.save_in_image_file)
2108 if (file_data_can_write_directly(fd))
2110 /* we can write the file directly */
2111 if (access_file(fd->path, W_OK))
2117 if (options->metadata.warn_on_write_problems)
2119 ret |= CHANGE_WARN_NO_WRITE_PERM;
2120 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2124 else if (file_data_can_write_sidecar(fd))
2126 /* we can write sidecar */
2127 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2128 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2130 file_data_update_ci_dest(fd, sidecar);
2135 if (options->metadata.warn_on_write_problems)
2137 ret |= CHANGE_WARN_NO_WRITE_PERM;
2138 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2147 /* write private metadata file under ~/.geeqie */
2149 /* If an existing metadata file exists, we will try writing to
2150 * it's location regardless of the user's preference.
2152 gchar *metadata_path = NULL;
2154 /* but ignore XMP if we are not able to write it */
2155 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2157 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2159 if (metadata_path && !access_file(metadata_path, W_OK))
2161 g_free(metadata_path);
2162 metadata_path = NULL;
2169 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2170 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2172 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2174 metadata_path = g_build_filename(dest_dir, filename, NULL);
2178 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2180 file_data_update_ci_dest(fd, metadata_path);
2185 ret |= CHANGE_NO_WRITE_PERM_DEST;
2186 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2188 g_free(metadata_path);
2193 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2198 same = (strcmp(fd->path, fd->change->dest) == 0);
2202 const gchar *dest_ext = extension_from_path(fd->change->dest);
2203 if (!dest_ext) dest_ext = "";
2205 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2207 ret |= CHANGE_WARN_CHANGED_EXT;
2208 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2213 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2215 ret |= CHANGE_WARN_SAME;
2216 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2220 dest_dir = remove_level_from_path(fd->change->dest);
2222 if (!isdir(dest_dir))
2224 ret |= CHANGE_NO_DEST_DIR;
2225 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2227 else if (!access_file(dest_dir, W_OK))
2229 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2230 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2234 if (isfile(fd->change->dest))
2236 if (!access_file(fd->change->dest, W_OK))
2238 ret |= CHANGE_NO_WRITE_PERM_DEST;
2239 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2243 ret |= CHANGE_WARN_DEST_EXISTS;
2244 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2247 else if (isdir(fd->change->dest))
2249 ret |= CHANGE_DEST_EXISTS;
2250 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2257 fd->change->error = ret;
2258 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2265 gint file_data_sc_verify_ci(FileData *fd)
2270 ret = file_data_verify_ci(fd);
2272 work = fd->sidecar_files;
2275 FileData *sfd = work->data;
2277 ret |= file_data_verify_ci(sfd);
2284 gchar *file_data_get_error_string(gint error)
2286 GString *result = g_string_new("");
2288 if (error & CHANGE_NO_SRC)
2290 if (result->len > 0) g_string_append(result, ", ");
2291 g_string_append(result, _("file or directory does not exist"));
2294 if (error & CHANGE_DEST_EXISTS)
2296 if (result->len > 0) g_string_append(result, ", ");
2297 g_string_append(result, _("destination already exists"));
2300 if (error & CHANGE_NO_WRITE_PERM_DEST)
2302 if (result->len > 0) g_string_append(result, ", ");
2303 g_string_append(result, _("destination can't be overwritten"));
2306 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2308 if (result->len > 0) g_string_append(result, ", ");
2309 g_string_append(result, _("destination directory is not writable"));
2312 if (error & CHANGE_NO_DEST_DIR)
2314 if (result->len > 0) g_string_append(result, ", ");
2315 g_string_append(result, _("destination directory does not exist"));
2318 if (error & CHANGE_NO_WRITE_PERM_DIR)
2320 if (result->len > 0) g_string_append(result, ", ");
2321 g_string_append(result, _("source directory is not writable"));
2324 if (error & CHANGE_NO_READ_PERM)
2326 if (result->len > 0) g_string_append(result, ", ");
2327 g_string_append(result, _("no read permission"));
2330 if (error & CHANGE_WARN_NO_WRITE_PERM)
2332 if (result->len > 0) g_string_append(result, ", ");
2333 g_string_append(result, _("file is readonly"));
2336 if (error & CHANGE_WARN_DEST_EXISTS)
2338 if (result->len > 0) g_string_append(result, ", ");
2339 g_string_append(result, _("destination already exists and will be overwritten"));
2342 if (error & CHANGE_WARN_SAME)
2344 if (result->len > 0) g_string_append(result, ", ");
2345 g_string_append(result, _("source and destination are the same"));
2348 if (error & CHANGE_WARN_CHANGED_EXT)
2350 if (result->len > 0) g_string_append(result, ", ");
2351 g_string_append(result, _("source and destination have different extension"));
2354 if (error & CHANGE_WARN_UNSAVED_META)
2356 if (result->len > 0) g_string_append(result, ", ");
2357 g_string_append(result, _("there are unsaved metadata changes for the file"));
2360 return g_string_free(result, FALSE);
2363 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2366 gint all_errors = 0;
2367 gint common_errors = ~0;
2372 if (!list) return 0;
2374 num = g_list_length(list);
2375 errors = g_new(int, num);
2386 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2387 all_errors |= error;
2388 common_errors &= error;
2395 if (desc && all_errors)
2398 GString *result = g_string_new("");
2402 gchar *str = file_data_get_error_string(common_errors);
2403 g_string_append(result, str);
2404 g_string_append(result, "\n");
2418 error = errors[i] & ~common_errors;
2422 gchar *str = file_data_get_error_string(error);
2423 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2428 *desc = g_string_free(result, FALSE);
2437 * perform the change described by FileFataChangeInfo
2438 * it is used for internal operations,
2439 * this function actually operates with files on the filesystem
2440 * it should implement safe delete
2443 static gboolean file_data_perform_move(FileData *fd)
2445 g_assert(!strcmp(fd->change->source, fd->path));
2446 return move_file(fd->change->source, fd->change->dest);
2449 static gboolean file_data_perform_copy(FileData *fd)
2451 g_assert(!strcmp(fd->change->source, fd->path));
2452 return copy_file(fd->change->source, fd->change->dest);
2455 static gboolean file_data_perform_delete(FileData *fd)
2457 if (isdir(fd->path) && !islink(fd->path))
2458 return rmdir_utf8(fd->path);
2460 if (options->file_ops.safe_delete_enable)
2461 return file_util_safe_unlink(fd->path);
2463 return unlink_file(fd->path);
2466 gboolean file_data_perform_ci(FileData *fd)
2468 FileDataChangeType type = fd->change->type;
2472 case FILEDATA_CHANGE_MOVE:
2473 return file_data_perform_move(fd);
2474 case FILEDATA_CHANGE_COPY:
2475 return file_data_perform_copy(fd);
2476 case FILEDATA_CHANGE_RENAME:
2477 return file_data_perform_move(fd); /* the same as move */
2478 case FILEDATA_CHANGE_DELETE:
2479 return file_data_perform_delete(fd);
2480 case FILEDATA_CHANGE_WRITE_METADATA:
2481 return metadata_write_perform(fd);
2482 case FILEDATA_CHANGE_UNSPECIFIED:
2483 /* nothing to do here */
2491 gboolean file_data_sc_perform_ci(FileData *fd)
2494 gboolean ret = TRUE;
2495 FileDataChangeType type = fd->change->type;
2497 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2499 work = fd->sidecar_files;
2502 FileData *sfd = work->data;
2504 if (!file_data_perform_ci(sfd)) ret = FALSE;
2508 if (!file_data_perform_ci(fd)) ret = FALSE;
2514 * updates FileData structure according to FileDataChangeInfo
2517 gboolean file_data_apply_ci(FileData *fd)
2519 FileDataChangeType type = fd->change->type;
2522 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2524 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2525 file_data_planned_change_remove(fd);
2527 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2529 /* this change overwrites another file which is already known to other modules
2530 renaming fd would create duplicate FileData structure
2531 the best thing we can do is nothing
2532 FIXME: maybe we could copy stuff like marks
2534 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2538 file_data_set_path(fd, fd->change->dest);
2541 file_data_increment_version(fd);
2542 file_data_send_notification(fd, NOTIFY_CHANGE);
2547 gboolean file_data_sc_apply_ci(FileData *fd)
2550 FileDataChangeType type = fd->change->type;
2552 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2554 work = fd->sidecar_files;
2557 FileData *sfd = work->data;
2559 file_data_apply_ci(sfd);
2563 file_data_apply_ci(fd);
2568 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2571 if (fd->parent) fd = fd->parent;
2572 if (!g_list_find(list, fd)) return FALSE;
2574 work = fd->sidecar_files;
2577 if (!g_list_find(list, work->data)) return FALSE;
2583 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2588 /* change partial groups to independent files */
2593 FileData *fd = work->data;
2596 if (!file_data_list_contains_whole_group(list, fd))
2598 file_data_disable_grouping(fd, TRUE);
2601 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2607 /* remove sidecars from the list,
2608 they can be still acessed via main_fd->sidecar_files */
2612 FileData *fd = work->data;
2616 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2618 out = g_list_prepend(out, file_data_ref(fd));
2622 filelist_free(list);
2623 out = g_list_reverse(out);
2633 * notify other modules about the change described by FileDataChangeInfo
2636 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2637 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2638 implementation in view_file_list.c */
2641 typedef struct _NotifyIdleData NotifyIdleData;
2643 struct _NotifyIdleData {
2649 typedef struct _NotifyData NotifyData;
2651 struct _NotifyData {
2652 FileDataNotifyFunc func;
2654 NotifyPriority priority;
2657 static GList *notify_func_list = NULL;
2659 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2661 NotifyData *nda = (NotifyData *)a;
2662 NotifyData *ndb = (NotifyData *)b;
2664 if (nda->priority < ndb->priority) return -1;
2665 if (nda->priority > ndb->priority) return 1;
2669 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2672 GList *work = notify_func_list;
2676 NotifyData *nd = (NotifyData *)work->data;
2678 if (nd->func == func && nd->data == data)
2680 g_warning("Notify func already registered");
2686 nd = g_new(NotifyData, 1);
2689 nd->priority = priority;
2691 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2692 DEBUG_2("Notify func registered: %p", nd);
2697 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2699 GList *work = notify_func_list;
2703 NotifyData *nd = (NotifyData *)work->data;
2705 if (nd->func == func && nd->data == data)
2707 notify_func_list = g_list_delete_link(notify_func_list, work);
2709 DEBUG_2("Notify func unregistered: %p", nd);
2715 g_warning("Notify func not found");
2720 gboolean file_data_send_notification_idle_cb(gpointer data)
2722 NotifyIdleData *nid = (NotifyIdleData *)data;
2723 GList *work = notify_func_list;
2727 NotifyData *nd = (NotifyData *)work->data;
2729 nd->func(nid->fd, nid->type, nd->data);
2732 file_data_unref(nid->fd);
2737 void file_data_send_notification(FileData *fd, NotifyType type)
2739 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
2740 nid->fd = file_data_ref(fd);
2742 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
2745 static GHashTable *file_data_monitor_pool = NULL;
2746 static guint realtime_monitor_id = 0; /* event source id */
2748 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2752 file_data_check_changed_files(fd);
2754 DEBUG_1("monitor %s", fd->path);
2757 static gboolean realtime_monitor_cb(gpointer data)
2759 if (!options->update_on_time_change) return TRUE;
2760 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2764 gboolean file_data_register_real_time_monitor(FileData *fd)
2770 if (!file_data_monitor_pool)
2771 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2773 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2775 DEBUG_1("Register realtime %d %s", count, fd->path);
2778 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2780 if (!realtime_monitor_id)
2782 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2788 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2792 g_assert(file_data_monitor_pool);
2794 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2796 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2798 g_assert(count > 0);
2803 g_hash_table_remove(file_data_monitor_pool, fd);
2805 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2807 file_data_unref(fd);
2809 if (g_hash_table_size(file_data_monitor_pool) == 0)
2811 g_source_remove(realtime_monitor_id);
2812 realtime_monitor_id = 0;
2818 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */