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"
27 static GHashTable *file_data_pool = NULL;
28 static GHashTable *file_data_planned_change_hash = NULL;
30 static gint sidecar_file_priority(const gchar *extension);
31 static void file_data_check_sidecars(const GList *basename_list);
32 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
37 *-----------------------------------------------------------------------------
38 * text conversion utils
39 *-----------------------------------------------------------------------------
42 gchar *text_from_size(gint64 size)
48 /* what I would like to use is printf("%'d", size)
49 * BUT: not supported on every libc :(
53 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
54 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
58 a = g_strdup_printf("%d", (guint)size);
64 b = g_new(gchar, l + n + 1);
89 gchar *text_from_size_abrev(gint64 size)
91 if (size < (gint64)1024)
93 return g_strdup_printf(_("%d bytes"), (gint)size);
95 if (size < (gint64)1048576)
97 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
99 if (size < (gint64)1073741824)
101 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
104 /* to avoid overflowing the gdouble, do division in two steps */
106 return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
109 /* note: returned string is valid until next call to text_from_time() */
110 const gchar *text_from_time(time_t t)
112 static gchar *ret = NULL;
116 GError *error = NULL;
118 btime = localtime(&t);
120 /* the %x warning about 2 digit years is not an error */
121 buflen = strftime(buf, sizeof(buf), "%x %X", btime);
122 if (buflen < 1) return "";
125 ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
128 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
137 *-----------------------------------------------------------------------------
138 * changed files detection and notification
139 *-----------------------------------------------------------------------------
142 void file_data_increment_version(FileData *fd)
148 fd->parent->version++;
149 fd->parent->valid_marks = 0;
153 static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st)
155 if (fd->size != st->st_size ||
156 fd->date != st->st_mtime)
158 fd->size = st->st_size;
159 fd->date = st->st_mtime;
160 fd->mode = st->st_mode;
161 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
162 fd->thumb_pixbuf = NULL;
163 file_data_increment_version(fd);
164 file_data_send_notification(fd, NOTIFY_REREAD);
170 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
172 gboolean ret = FALSE;
175 ret = file_data_check_changed_single_file(fd, st);
177 work = fd->sidecar_files;
180 FileData *sfd = work->data;
184 if (!stat_utf8(sfd->path, &st))
188 file_data_disconnect_sidecar_file(fd, sfd);
193 ret |= file_data_check_changed_files_recursive(sfd, &st);
199 gboolean file_data_check_changed_files(FileData *fd)
201 gboolean ret = FALSE;
204 if (fd->parent) fd = fd->parent;
206 if (!stat_utf8(fd->path, &st))
210 FileData *sfd = NULL;
212 /* parent is missing, we have to rebuild whole group */
217 /* file_data_disconnect_sidecar_file might delete the file,
218 we have to keep the reference to prevent this */
219 sidecars = filelist_copy(fd->sidecar_files);
226 file_data_disconnect_sidecar_file(fd, sfd);
228 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
229 /* now we can release the sidecars */
230 filelist_free(sidecars);
231 file_data_send_notification(fd, NOTIFY_REREAD);
235 ret |= file_data_check_changed_files_recursive(fd, &st);
242 *-----------------------------------------------------------------------------
243 * file name, extension, sorting, ...
244 *-----------------------------------------------------------------------------
247 static void file_data_set_collate_keys(FileData *fd)
249 gchar *caseless_name;
252 valid_name = g_filename_display_name(fd->name);
253 caseless_name = g_utf8_casefold(valid_name, -1);
255 g_free(fd->collate_key_name);
256 g_free(fd->collate_key_name_nocase);
258 #if 0 && GLIB_CHECK_VERSION(2, 8, 0)
259 fd->collate_key_name = g_utf8_collate_key_for_filename(valid_name, -1);
260 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
262 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
263 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
266 g_free(caseless_name);
269 static void file_data_set_path(FileData *fd, const gchar *path)
271 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
272 g_assert(file_data_pool);
276 if (fd->original_path)
278 g_hash_table_remove(file_data_pool, fd->original_path);
279 g_free(fd->original_path);
282 g_assert(!g_hash_table_lookup(file_data_pool, path));
284 fd->original_path = g_strdup(path);
285 g_hash_table_insert(file_data_pool, fd->original_path, fd);
287 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
289 fd->path = g_strdup(path);
291 fd->extension = fd->name + 1;
292 file_data_set_collate_keys(fd);
296 fd->path = g_strdup(path);
297 fd->name = filename_from_path(fd->path);
299 if (strcmp(fd->name, "..") == 0)
301 gchar *dir = remove_level_from_path(path);
303 fd->path = remove_level_from_path(dir);
306 fd->extension = fd->name + 2;
307 file_data_set_collate_keys(fd);
310 else if (strcmp(fd->name, ".") == 0)
313 fd->path = remove_level_from_path(path);
315 fd->extension = fd->name + 1;
316 file_data_set_collate_keys(fd);
320 fd->extension = registered_extension_from_path(fd->path);
321 if (fd->extension == NULL)
323 fd->extension = fd->name + strlen(fd->name);
326 fd->sidecar_priority = sidecar_file_priority(fd->extension);
327 file_data_set_collate_keys(fd);
331 *-----------------------------------------------------------------------------
332 * create or reuse Filedata
333 *-----------------------------------------------------------------------------
336 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
340 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
342 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
345 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
347 fd = g_hash_table_lookup(file_data_pool, path_utf8);
353 if (!fd && file_data_planned_change_hash)
355 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
358 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
360 file_data_apply_ci(fd);
368 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
371 changed = file_data_check_changed_single_file(fd, st);
373 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
378 fd = g_new0(FileData, 1);
380 fd->size = st->st_size;
381 fd->date = st->st_mtime;
382 fd->mode = st->st_mode;
384 fd->magick = 0x12345678;
386 if (disable_sidecars) fd->disable_grouping = TRUE;
388 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
393 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
395 gchar *path_utf8 = path_to_utf8(path);
396 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
402 FileData *file_data_new_no_grouping(const gchar *path_utf8)
406 if (!stat_utf8(path_utf8, &st))
412 return file_data_new(path_utf8, &st, TRUE);
415 FileData *file_data_new_dir(const gchar *path_utf8)
419 if (!stat_utf8(path_utf8, &st))
425 /* dir or non-existing yet */
426 g_assert(S_ISDIR(st.st_mode));
428 return file_data_new(path_utf8, &st, TRUE);
432 *-----------------------------------------------------------------------------
434 *-----------------------------------------------------------------------------
437 #ifdef DEBUG_FILEDATA
438 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
440 FileData *file_data_ref(FileData *fd)
443 if (fd == NULL) return NULL;
444 #ifdef DEBUG_FILEDATA
445 if (fd->magick != 0x12345678)
446 DEBUG_0("fd magick mismatch at %s:%d", file, line);
448 g_assert(fd->magick == 0x12345678);
451 #ifdef DEBUG_FILEDATA
452 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
454 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
459 static void file_data_free(FileData *fd)
461 g_assert(fd->magick == 0x12345678);
462 g_assert(fd->ref == 0);
464 metadata_cache_free(fd);
465 g_hash_table_remove(file_data_pool, fd->original_path);
468 g_free(fd->original_path);
469 g_free(fd->collate_key_name);
470 g_free(fd->collate_key_name_nocase);
471 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
472 histmap_free(fd->histmap);
474 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
476 file_data_change_info_free(NULL, fd);
480 #ifdef DEBUG_FILEDATA
481 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
483 void file_data_unref(FileData *fd)
486 if (fd == NULL) return;
487 #ifdef DEBUG_FILEDATA
488 if (fd->magick != 0x12345678)
489 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
491 g_assert(fd->magick == 0x12345678);
494 #ifdef DEBUG_FILEDATA
495 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
497 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
502 FileData *parent = fd->parent ? fd->parent : fd;
504 if (parent->ref > 0) return;
506 work = parent->sidecar_files;
509 FileData *sfd = work->data;
510 if (sfd->ref > 0) return;
514 /* none of parent/children is referenced, we can free everything */
516 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
518 work = parent->sidecar_files;
521 FileData *sfd = work->data;
526 g_list_free(parent->sidecar_files);
527 parent->sidecar_files = NULL;
529 file_data_free(parent);
536 *-----------------------------------------------------------------------------
537 * sidecar file info struct
538 *-----------------------------------------------------------------------------
541 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
543 const FileData *fda = a;
544 const FileData *fdb = b;
546 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
547 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
549 return strcmp(fdb->extension, fda->extension);
553 static gint sidecar_file_priority(const gchar *extension)
558 if (extension == NULL)
561 work = sidecar_ext_get_list();
564 gchar *ext = work->data;
567 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
573 static void file_data_check_sidecars(const GList *basename_list)
575 /* basename_list contains the new group - first is the parent, then sorted sidecars */
576 /* all files in the list have ref count > 0 */
579 GList *s_work, *new_sidecars;
582 if (!basename_list) return;
585 DEBUG_2("basename start");
586 work = basename_list;
589 FileData *fd = work->data;
591 g_assert(fd->magick == 0x12345678);
592 DEBUG_2("basename: %p %s", fd, fd->name);
595 g_assert(fd->parent->magick == 0x12345678);
596 DEBUG_2(" parent: %p", fd->parent);
598 s_work = fd->sidecar_files;
601 FileData *sfd = s_work->data;
602 s_work = s_work->next;
603 g_assert(sfd->magick == 0x12345678);
604 DEBUG_2(" sidecar: %p %s", sfd, sfd->name);
607 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
610 parent_fd = basename_list->data;
612 /* check if the second and next entries of basename_list are already connected
613 as sidecars of the first entry (parent_fd) */
614 work = basename_list->next;
615 s_work = parent_fd->sidecar_files;
617 while (work && s_work)
619 if (work->data != s_work->data) break;
621 s_work = s_work->next;
624 if (!work && !s_work)
626 DEBUG_2("basename no change");
627 return; /* no change in grouping */
630 /* we have to regroup it */
632 /* first, disconnect everything and send notification*/
634 work = basename_list;
637 FileData *fd = work->data;
639 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
643 FileData *old_parent = fd->parent;
644 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
645 file_data_ref(old_parent);
646 file_data_disconnect_sidecar_file(old_parent, fd);
647 file_data_send_notification(old_parent, NOTIFY_REREAD);
648 file_data_unref(old_parent);
651 while (fd->sidecar_files)
653 FileData *sfd = fd->sidecar_files->data;
654 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
656 file_data_disconnect_sidecar_file(fd, sfd);
657 file_data_send_notification(sfd, NOTIFY_REREAD);
658 file_data_unref(sfd);
660 file_data_send_notification(fd, NOTIFY_GROUPING);
662 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
665 /* now we can form the new group */
666 work = basename_list->next;
670 FileData *sfd = work->data;
671 g_assert(sfd->magick == 0x12345678);
672 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
673 sfd->parent = parent_fd;
674 new_sidecars = g_list_prepend(new_sidecars, sfd);
677 g_assert(parent_fd->sidecar_files == NULL);
678 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
679 DEBUG_1("basename group changed for %s", parent_fd->path);
683 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
685 g_assert(target->magick == 0x12345678);
686 g_assert(sfd->magick == 0x12345678);
687 g_assert(g_list_find(target->sidecar_files, sfd));
689 file_data_ref(target);
692 g_assert(sfd->parent == target);
694 file_data_increment_version(sfd); /* increments both sfd and target */
696 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
699 file_data_unref(target);
700 file_data_unref(sfd);
703 /* disables / enables grouping for particular file, sends UPDATE notification */
704 void file_data_disable_grouping(FileData *fd, gboolean disable)
706 if (!fd->disable_grouping == !disable) return;
708 fd->disable_grouping = !!disable;
714 FileData *parent = file_data_ref(fd->parent);
715 file_data_disconnect_sidecar_file(parent, fd);
716 file_data_send_notification(parent, NOTIFY_GROUPING);
717 file_data_unref(parent);
719 else if (fd->sidecar_files)
721 GList *sidecar_files = filelist_copy(fd->sidecar_files);
722 GList *work = sidecar_files;
725 FileData *sfd = work->data;
727 file_data_disconnect_sidecar_file(fd, sfd);
728 file_data_send_notification(sfd, NOTIFY_GROUPING);
730 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
731 filelist_free(sidecar_files);
735 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
740 file_data_increment_version(fd);
741 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
743 file_data_send_notification(fd, NOTIFY_GROUPING);
746 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
753 FileData *fd = work->data;
755 file_data_disable_grouping(fd, disable);
763 *-----------------------------------------------------------------------------
765 *-----------------------------------------------------------------------------
768 static SortType filelist_sort_method = SORT_NONE;
769 static gboolean filelist_sort_ascend = TRUE;
772 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
775 if (!filelist_sort_ascend)
782 switch (filelist_sort_method)
787 if (fa->size < fb->size) return -1;
788 if (fa->size > fb->size) return 1;
789 /* fall back to name */
792 if (fa->date < fb->date) return -1;
793 if (fa->date > fb->date) return 1;
794 /* fall back to name */
796 #ifdef HAVE_STRVERSCMP
798 ret = strverscmp(fa->name, fb->name);
799 if (ret != 0) return ret;
806 if (options->file_sort.case_sensitive)
807 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
809 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
811 if (ret != 0) return ret;
813 /* do not return 0 unless the files are really the same
814 file_data_pool ensures that original_path is unique
816 return strcmp(fa->original_path, fb->original_path);
819 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
821 filelist_sort_method = method;
822 filelist_sort_ascend = ascend;
823 return filelist_sort_compare_filedata(fa, fb);
826 static gint filelist_sort_file_cb(gpointer a, gpointer b)
828 return filelist_sort_compare_filedata(a, b);
831 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
833 filelist_sort_method = method;
834 filelist_sort_ascend = ascend;
835 return g_list_sort(list, cb);
838 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
840 filelist_sort_method = method;
841 filelist_sort_ascend = ascend;
842 return g_list_insert_sorted(list, data, cb);
845 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
847 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
850 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
852 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
856 *-----------------------------------------------------------------------------
857 * basename hash - grouping of sidecars in filelist
858 *-----------------------------------------------------------------------------
862 static GHashTable *file_data_basename_hash_new(void)
864 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
867 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
870 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
872 list = g_hash_table_lookup(basename_hash, basename);
874 if (!g_list_find(list, fd))
876 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
877 g_hash_table_insert(basename_hash, basename, list);
887 static void file_data_basename_hash_remove(GHashTable *basename_hash, FileData *fd)
890 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
892 list = g_hash_table_lookup(basename_hash, basename);
894 if (!g_list_find(list, fd)) return;
896 list = g_list_remove(list, fd);
901 g_hash_table_insert(basename_hash, basename, list);
905 g_hash_table_remove(basename_hash, basename);
911 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
913 filelist_free((GList *)value);
916 static void file_data_basename_hash_free(GHashTable *basename_hash)
918 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
919 g_hash_table_destroy(basename_hash);
923 *-----------------------------------------------------------------------------
924 * handling sidecars in filelist
925 *-----------------------------------------------------------------------------
928 static GList *filelist_filter_out_sidecars(GList *flist)
931 GList *flist_filtered = NULL;
935 FileData *fd = work->data;
938 if (fd->parent) /* remove fd's that are children */
941 flist_filtered = g_list_prepend(flist_filtered, fd);
945 return flist_filtered;
948 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
950 GList *basename_list = (GList *)value;
951 file_data_check_sidecars(basename_list);
955 static gboolean is_hidden_file(const gchar *name)
957 if (name[0] != '.') return FALSE;
958 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
963 *-----------------------------------------------------------------------------
964 * the main filelist function
965 *-----------------------------------------------------------------------------
968 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
975 gint (*stat_func)(const gchar *path, struct stat *buf);
976 GHashTable *basename_hash = NULL;
978 g_assert(files || dirs);
980 if (files) *files = NULL;
981 if (dirs) *dirs = NULL;
983 pathl = path_from_utf8(dir_path);
984 if (!pathl) return FALSE;
993 if (files) basename_hash = file_data_basename_hash_new();
1000 while ((dir = readdir(dp)) != NULL)
1002 struct stat ent_sbuf;
1003 const gchar *name = dir->d_name;
1006 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1009 filepath = g_build_filename(pathl, name, NULL);
1010 if (stat_func(filepath, &ent_sbuf) >= 0)
1012 if (S_ISDIR(ent_sbuf.st_mode))
1014 /* we ignore the .thumbnails dir for cleanliness */
1016 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1017 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1018 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1019 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1021 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1026 if (files && filter_name_exists(name))
1028 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1029 flist = g_list_prepend(flist, fd);
1030 if (fd->sidecar_priority && !fd->disable_grouping)
1032 file_data_basename_hash_insert(basename_hash, fd);
1039 if (errno == EOVERFLOW)
1041 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1051 if (dirs) *dirs = dlist;
1054 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1056 *files = filelist_filter_out_sidecars(flist);
1058 if (basename_hash) file_data_basename_hash_free(basename_hash);
1063 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1065 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1068 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1070 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1073 FileData *file_data_new_group(const gchar *path_utf8)
1080 if (!stat_utf8(path_utf8, &st))
1086 if (S_ISDIR(st.st_mode))
1087 return file_data_new(path_utf8, &st, TRUE);
1089 dir = remove_level_from_path(path_utf8);
1091 filelist_read_real(dir, &files, NULL, TRUE);
1093 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1097 filelist_free(files);
1103 void filelist_free(GList *list)
1110 file_data_unref((FileData *)work->data);
1118 GList *filelist_copy(GList *list)
1120 GList *new_list = NULL;
1131 new_list = g_list_prepend(new_list, file_data_ref(fd));
1134 return g_list_reverse(new_list);
1137 GList *filelist_from_path_list(GList *list)
1139 GList *new_list = NULL;
1150 new_list = g_list_prepend(new_list, file_data_new_group(path));
1153 return g_list_reverse(new_list);
1156 GList *filelist_to_path_list(GList *list)
1158 GList *new_list = NULL;
1169 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1172 return g_list_reverse(new_list);
1175 GList *filelist_filter(GList *list, gboolean is_dir_list)
1179 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1184 FileData *fd = (FileData *)(work->data);
1185 const gchar *name = fd->name;
1187 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1188 (!is_dir_list && !filter_name_exists(name)) ||
1189 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1190 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1194 list = g_list_remove_link(list, link);
1195 file_data_unref(fd);
1206 *-----------------------------------------------------------------------------
1207 * filelist recursive
1208 *-----------------------------------------------------------------------------
1211 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1213 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1216 GList *filelist_sort_path(GList *list)
1218 return g_list_sort(list, filelist_sort_path_cb);
1221 static void filelist_recursive_append(GList **list, GList *dirs)
1228 FileData *fd = (FileData *)(work->data);
1232 if (filelist_read(fd, &f, &d))
1234 f = filelist_filter(f, FALSE);
1235 f = filelist_sort_path(f);
1236 *list = g_list_concat(*list, f);
1238 d = filelist_filter(d, TRUE);
1239 d = filelist_sort_path(d);
1240 filelist_recursive_append(list, d);
1248 GList *filelist_recursive(FileData *dir_fd)
1253 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1254 list = filelist_filter(list, FALSE);
1255 list = filelist_sort_path(list);
1257 d = filelist_filter(d, TRUE);
1258 d = filelist_sort_path(d);
1259 filelist_recursive_append(&list, d);
1266 *-----------------------------------------------------------------------------
1267 * file modification support
1268 *-----------------------------------------------------------------------------
1272 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1274 if (!fdci && fd) fdci = fd->change;
1278 g_free(fdci->source);
1283 if (fd) fd->change = NULL;
1286 static gboolean file_data_can_write_directly(FileData *fd)
1288 return filter_name_is_writable(fd->extension);
1291 static gboolean file_data_can_write_sidecar(FileData *fd)
1293 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1296 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1298 gchar *sidecar_path = NULL;
1301 if (!file_data_can_write_sidecar(fd)) return NULL;
1303 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1306 FileData *sfd = work->data;
1308 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
1310 sidecar_path = g_strdup(sfd->path);
1315 if (!existing_only && !sidecar_path)
1317 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1318 sidecar_path = g_strconcat(base, ".xmp", NULL);
1322 return sidecar_path;
1326 * marks and orientation
1329 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1330 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1331 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1332 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1334 gboolean file_data_get_mark(FileData *fd, gint n)
1336 gboolean valid = (fd->valid_marks & (1 << n));
1338 if (file_data_get_mark_func[n] && !valid)
1340 guint old = fd->marks;
1341 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1343 if (!value != !(fd->marks & (1 << n)))
1345 fd->marks = fd->marks ^ (1 << n);
1348 fd->valid_marks |= (1 << n);
1349 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1351 file_data_unref(fd);
1353 else if (!old && fd->marks)
1359 return !!(fd->marks & (1 << n));
1362 guint file_data_get_marks(FileData *fd)
1365 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1369 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1372 if (!value == !file_data_get_mark(fd, n)) return;
1374 if (file_data_set_mark_func[n])
1376 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1381 fd->marks = fd->marks ^ (1 << n);
1383 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1385 file_data_unref(fd);
1387 else if (!old && fd->marks)
1392 file_data_increment_version(fd);
1393 file_data_send_notification(fd, NOTIFY_MARKS);
1396 gboolean file_data_filter_marks(FileData *fd, guint filter)
1399 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1400 return ((fd->marks & filter) == filter);
1403 GList *file_data_filter_marks_list(GList *list, guint filter)
1410 FileData *fd = work->data;
1414 if (!file_data_filter_marks(fd, filter))
1416 list = g_list_remove_link(list, link);
1417 file_data_unref(fd);
1425 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1427 FileData *fd = value;
1428 file_data_increment_version(fd);
1429 file_data_send_notification(fd, NOTIFY_MARKS);
1432 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1434 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1436 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1438 file_data_get_mark_func[n] = get_mark_func;
1439 file_data_set_mark_func[n] = set_mark_func;
1440 file_data_mark_func_data[n] = data;
1441 file_data_destroy_mark_func[n] = notify;
1445 /* this effectively changes all known files */
1446 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1452 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1454 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1455 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1456 if (data) *data = file_data_mark_func_data[n];
1459 gint file_data_get_user_orientation(FileData *fd)
1461 return fd->user_orientation;
1464 void file_data_set_user_orientation(FileData *fd, gint value)
1466 if (fd->user_orientation == value) return;
1468 fd->user_orientation = value;
1469 file_data_increment_version(fd);
1470 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1475 * file_data - operates on the given fd
1476 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1480 /* return list of sidecar file extensions in a string */
1481 gchar *file_data_sc_list_to_string(FileData *fd)
1484 GString *result = g_string_new("");
1486 work = fd->sidecar_files;
1489 FileData *sfd = work->data;
1491 result = g_string_append(result, "+ ");
1492 result = g_string_append(result, sfd->extension);
1494 if (work) result = g_string_append_c(result, ' ');
1497 return g_string_free(result, FALSE);
1503 * add FileDataChangeInfo (see typedefs.h) for the given operation
1504 * uses file_data_add_change_info
1506 * fails if the fd->change already exists - change operations can't run in parallel
1507 * fd->change_info works as a lock
1509 * dest can be NULL - in this case the current name is used for now, it will
1514 FileDataChangeInfo types:
1516 MOVE - path is changed, name may be changed too
1517 RENAME - path remains unchanged, name is changed
1518 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1519 sidecar names are changed too, extensions are not changed
1521 UPDATE - file size, date or grouping has been changed
1524 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1526 FileDataChangeInfo *fdci;
1528 if (fd->change) return FALSE;
1530 fdci = g_new0(FileDataChangeInfo, 1);
1535 fdci->source = g_strdup(src);
1537 fdci->source = g_strdup(fd->path);
1540 fdci->dest = g_strdup(dest);
1547 static void file_data_planned_change_remove(FileData *fd)
1549 if (file_data_planned_change_hash &&
1550 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1552 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1554 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1555 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1556 file_data_unref(fd);
1557 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1559 g_hash_table_destroy(file_data_planned_change_hash);
1560 file_data_planned_change_hash = NULL;
1561 DEBUG_1("planned change: empty");
1568 void file_data_free_ci(FileData *fd)
1570 FileDataChangeInfo *fdci = fd->change;
1574 file_data_planned_change_remove(fd);
1576 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1578 g_free(fdci->source);
1586 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1588 FileDataChangeInfo *fdci = fd->change;
1590 fdci->regroup_when_finished = enable;
1593 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1597 if (fd->parent) fd = fd->parent;
1599 if (fd->change) return FALSE;
1601 work = fd->sidecar_files;
1604 FileData *sfd = work->data;
1606 if (sfd->change) return FALSE;
1610 file_data_add_ci(fd, type, NULL, NULL);
1612 work = fd->sidecar_files;
1615 FileData *sfd = work->data;
1617 file_data_add_ci(sfd, type, NULL, NULL);
1624 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1628 if (fd->parent) fd = fd->parent;
1630 if (!fd->change || fd->change->type != type) return FALSE;
1632 work = fd->sidecar_files;
1635 FileData *sfd = work->data;
1637 if (!sfd->change || sfd->change->type != type) return FALSE;
1645 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1647 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1648 file_data_sc_update_ci_copy(fd, dest_path);
1652 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1654 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1655 file_data_sc_update_ci_move(fd, dest_path);
1659 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1661 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1662 file_data_sc_update_ci_rename(fd, dest_path);
1666 gboolean file_data_sc_add_ci_delete(FileData *fd)
1668 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1671 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1673 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1674 file_data_sc_update_ci_unspecified(fd, dest_path);
1678 gboolean file_data_add_ci_write_metadata(FileData *fd)
1680 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1683 void file_data_sc_free_ci(FileData *fd)
1687 if (fd->parent) fd = fd->parent;
1689 file_data_free_ci(fd);
1691 work = fd->sidecar_files;
1694 FileData *sfd = work->data;
1696 file_data_free_ci(sfd);
1701 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1704 gboolean ret = TRUE;
1709 FileData *fd = work->data;
1711 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1718 static void file_data_sc_revert_ci_list(GList *fd_list)
1725 FileData *fd = work->data;
1727 file_data_sc_free_ci(fd);
1732 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1739 FileData *fd = work->data;
1741 if (!func(fd, dest))
1743 file_data_sc_revert_ci_list(work->prev);
1752 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1754 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1757 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1759 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1762 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1764 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1767 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1769 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1772 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1775 gboolean ret = TRUE;
1780 FileData *fd = work->data;
1782 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1789 void file_data_free_ci_list(GList *fd_list)
1796 FileData *fd = work->data;
1798 file_data_free_ci(fd);
1803 void file_data_sc_free_ci_list(GList *fd_list)
1810 FileData *fd = work->data;
1812 file_data_sc_free_ci(fd);
1818 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1819 * fails if fd->change does not exist or the change type does not match
1822 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1824 FileDataChangeType type = fd->change->type;
1826 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1830 if (!file_data_planned_change_hash)
1831 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1833 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1835 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1836 g_hash_table_remove(file_data_planned_change_hash, old_path);
1837 file_data_unref(fd);
1840 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1845 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1846 g_hash_table_remove(file_data_planned_change_hash, new_path);
1847 file_data_unref(ofd);
1850 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1852 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1857 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1859 gchar *old_path = fd->change->dest;
1861 fd->change->dest = g_strdup(dest_path);
1862 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1866 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1868 const gchar *extension = extension_from_path(fd->change->source);
1869 gchar *base = remove_extension_from_path(dest_path);
1870 gchar *old_path = fd->change->dest;
1872 fd->change->dest = g_strconcat(base, extension, NULL);
1873 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1879 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1882 gchar *dest_path_full = NULL;
1884 if (fd->parent) fd = fd->parent;
1888 dest_path = fd->path;
1890 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1892 gchar *dir = remove_level_from_path(fd->path);
1894 dest_path_full = g_build_filename(dir, dest_path, NULL);
1896 dest_path = dest_path_full;
1898 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1900 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1901 dest_path = dest_path_full;
1904 file_data_update_ci_dest(fd, dest_path);
1906 work = fd->sidecar_files;
1909 FileData *sfd = work->data;
1911 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1915 g_free(dest_path_full);
1918 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1920 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1921 file_data_sc_update_ci(fd, dest_path);
1925 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1927 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1930 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1932 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1935 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1937 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1940 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1942 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1945 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1947 gboolean (*func)(FileData *, const gchar *))
1950 gboolean ret = TRUE;
1955 FileData *fd = work->data;
1957 if (!func(fd, dest)) ret = FALSE;
1964 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1966 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1969 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1971 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1974 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1976 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1981 * verify source and dest paths - dest image exists, etc.
1982 * it should detect all possible problems with the planned operation
1985 gint file_data_verify_ci(FileData *fd)
1987 gint ret = CHANGE_OK;
1992 DEBUG_1("Change checked: no change info: %s", fd->path);
1996 if (!isname(fd->path))
1998 /* this probably should not happen */
1999 ret |= CHANGE_NO_SRC;
2000 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2004 dir = remove_level_from_path(fd->path);
2006 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2007 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2008 fd->change->type != FILEDATA_CHANGE_RENAME &&
2009 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2012 ret |= CHANGE_WARN_UNSAVED_META;
2013 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2016 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2017 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2018 !access_file(fd->path, R_OK))
2020 ret |= CHANGE_NO_READ_PERM;
2021 DEBUG_1("Change checked: no read permission: %s", fd->path);
2023 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2024 !access_file(dir, W_OK))
2026 ret |= CHANGE_NO_WRITE_PERM_DIR;
2027 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2029 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2030 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2031 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2032 !access_file(fd->path, W_OK))
2034 ret |= CHANGE_WARN_NO_WRITE_PERM;
2035 DEBUG_1("Change checked: no write permission: %s", fd->path);
2037 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2038 - that means that there are no hard errors and warnings can be disabled
2039 - the destination is determined during the check
2041 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2043 /* determine destination file */
2044 gboolean have_dest = FALSE;
2045 gchar *dest_dir = NULL;
2047 if (options->metadata.save_in_image_file)
2049 if (file_data_can_write_directly(fd))
2051 /* we can write the file directly */
2052 if (access_file(fd->path, W_OK))
2058 if (options->metadata.warn_on_write_problems)
2060 ret |= CHANGE_WARN_NO_WRITE_PERM;
2061 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2065 else if (file_data_can_write_sidecar(fd))
2067 /* we can write sidecar */
2068 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2069 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2071 file_data_update_ci_dest(fd, sidecar);
2076 if (options->metadata.warn_on_write_problems)
2078 ret |= CHANGE_WARN_NO_WRITE_PERM;
2079 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2088 /* write private metadata file under ~/.geeqie */
2090 /* If an existing metadata file exists, we will try writing to
2091 * it's location regardless of the user's preference.
2093 gchar *metadata_path = NULL;
2095 /* but ignore XMP if we are not able to write it */
2096 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2098 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2100 if (metadata_path && !access_file(metadata_path, W_OK))
2102 g_free(metadata_path);
2103 metadata_path = NULL;
2110 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2111 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2113 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2115 metadata_path = g_build_filename(dest_dir, filename, NULL);
2119 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2121 file_data_update_ci_dest(fd, metadata_path);
2126 ret |= CHANGE_NO_WRITE_PERM_DEST;
2127 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2129 g_free(metadata_path);
2134 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2139 same = (strcmp(fd->path, fd->change->dest) == 0);
2143 const gchar *dest_ext = extension_from_path(fd->change->dest);
2144 if (!dest_ext) dest_ext = "";
2146 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2148 ret |= CHANGE_WARN_CHANGED_EXT;
2149 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2154 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2156 ret |= CHANGE_WARN_SAME;
2157 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2161 dest_dir = remove_level_from_path(fd->change->dest);
2163 if (!isdir(dest_dir))
2165 ret |= CHANGE_NO_DEST_DIR;
2166 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2168 else if (!access_file(dest_dir, W_OK))
2170 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2171 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2175 if (isfile(fd->change->dest))
2177 if (!access_file(fd->change->dest, W_OK))
2179 ret |= CHANGE_NO_WRITE_PERM_DEST;
2180 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2184 ret |= CHANGE_WARN_DEST_EXISTS;
2185 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2188 else if (isdir(fd->change->dest))
2190 ret |= CHANGE_DEST_EXISTS;
2191 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2198 fd->change->error = ret;
2199 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2206 gint file_data_sc_verify_ci(FileData *fd)
2211 ret = file_data_verify_ci(fd);
2213 work = fd->sidecar_files;
2216 FileData *sfd = work->data;
2218 ret |= file_data_verify_ci(sfd);
2225 gchar *file_data_get_error_string(gint error)
2227 GString *result = g_string_new("");
2229 if (error & CHANGE_NO_SRC)
2231 if (result->len > 0) g_string_append(result, ", ");
2232 g_string_append(result, _("file or directory does not exist"));
2235 if (error & CHANGE_DEST_EXISTS)
2237 if (result->len > 0) g_string_append(result, ", ");
2238 g_string_append(result, _("destination already exists"));
2241 if (error & CHANGE_NO_WRITE_PERM_DEST)
2243 if (result->len > 0) g_string_append(result, ", ");
2244 g_string_append(result, _("destination can't be overwritten"));
2247 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2249 if (result->len > 0) g_string_append(result, ", ");
2250 g_string_append(result, _("destination directory is not writable"));
2253 if (error & CHANGE_NO_DEST_DIR)
2255 if (result->len > 0) g_string_append(result, ", ");
2256 g_string_append(result, _("destination directory does not exist"));
2259 if (error & CHANGE_NO_WRITE_PERM_DIR)
2261 if (result->len > 0) g_string_append(result, ", ");
2262 g_string_append(result, _("source directory is not writable"));
2265 if (error & CHANGE_NO_READ_PERM)
2267 if (result->len > 0) g_string_append(result, ", ");
2268 g_string_append(result, _("no read permission"));
2271 if (error & CHANGE_WARN_NO_WRITE_PERM)
2273 if (result->len > 0) g_string_append(result, ", ");
2274 g_string_append(result, _("file is readonly"));
2277 if (error & CHANGE_WARN_DEST_EXISTS)
2279 if (result->len > 0) g_string_append(result, ", ");
2280 g_string_append(result, _("destination already exists and will be overwritten"));
2283 if (error & CHANGE_WARN_SAME)
2285 if (result->len > 0) g_string_append(result, ", ");
2286 g_string_append(result, _("source and destination are the same"));
2289 if (error & CHANGE_WARN_CHANGED_EXT)
2291 if (result->len > 0) g_string_append(result, ", ");
2292 g_string_append(result, _("source and destination have different extension"));
2295 if (error & CHANGE_WARN_UNSAVED_META)
2297 if (result->len > 0) g_string_append(result, ", ");
2298 g_string_append(result, _("there are unsaved metadata changes for the file"));
2301 return g_string_free(result, FALSE);
2304 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2307 gint all_errors = 0;
2308 gint common_errors = ~0;
2313 if (!list) return 0;
2315 num = g_list_length(list);
2316 errors = g_new(int, num);
2327 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2328 all_errors |= error;
2329 common_errors &= error;
2336 if (desc && all_errors)
2339 GString *result = g_string_new("");
2343 gchar *str = file_data_get_error_string(common_errors);
2344 g_string_append(result, str);
2345 g_string_append(result, "\n");
2359 error = errors[i] & ~common_errors;
2363 gchar *str = file_data_get_error_string(error);
2364 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2369 *desc = g_string_free(result, FALSE);
2378 * perform the change described by FileFataChangeInfo
2379 * it is used for internal operations,
2380 * this function actually operates with files on the filesystem
2381 * it should implement safe delete
2384 static gboolean file_data_perform_move(FileData *fd)
2386 g_assert(!strcmp(fd->change->source, fd->path));
2387 return move_file(fd->change->source, fd->change->dest);
2390 static gboolean file_data_perform_copy(FileData *fd)
2392 g_assert(!strcmp(fd->change->source, fd->path));
2393 return copy_file(fd->change->source, fd->change->dest);
2396 static gboolean file_data_perform_delete(FileData *fd)
2398 if (isdir(fd->path) && !islink(fd->path))
2399 return rmdir_utf8(fd->path);
2401 if (options->file_ops.safe_delete_enable)
2402 return file_util_safe_unlink(fd->path);
2404 return unlink_file(fd->path);
2407 gboolean file_data_perform_ci(FileData *fd)
2409 FileDataChangeType type = fd->change->type;
2413 case FILEDATA_CHANGE_MOVE:
2414 return file_data_perform_move(fd);
2415 case FILEDATA_CHANGE_COPY:
2416 return file_data_perform_copy(fd);
2417 case FILEDATA_CHANGE_RENAME:
2418 return file_data_perform_move(fd); /* the same as move */
2419 case FILEDATA_CHANGE_DELETE:
2420 return file_data_perform_delete(fd);
2421 case FILEDATA_CHANGE_WRITE_METADATA:
2422 return metadata_write_perform(fd);
2423 case FILEDATA_CHANGE_UNSPECIFIED:
2424 /* nothing to do here */
2432 gboolean file_data_sc_perform_ci(FileData *fd)
2435 gboolean ret = TRUE;
2436 FileDataChangeType type = fd->change->type;
2438 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2440 work = fd->sidecar_files;
2443 FileData *sfd = work->data;
2445 if (!file_data_perform_ci(sfd)) ret = FALSE;
2449 if (!file_data_perform_ci(fd)) ret = FALSE;
2455 * updates FileData structure according to FileDataChangeInfo
2458 gboolean file_data_apply_ci(FileData *fd)
2460 FileDataChangeType type = fd->change->type;
2463 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2465 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2466 file_data_planned_change_remove(fd);
2468 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2470 /* this change overwrites another file which is already known to other modules
2471 renaming fd would create duplicate FileData structure
2472 the best thing we can do is nothing
2473 FIXME: maybe we could copy stuff like marks
2475 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2479 file_data_set_path(fd, fd->change->dest);
2482 file_data_increment_version(fd);
2483 file_data_send_notification(fd, NOTIFY_CHANGE);
2488 gboolean file_data_sc_apply_ci(FileData *fd)
2491 FileDataChangeType type = fd->change->type;
2493 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2495 work = fd->sidecar_files;
2498 FileData *sfd = work->data;
2500 file_data_apply_ci(sfd);
2504 file_data_apply_ci(fd);
2509 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2512 if (fd->parent) fd = fd->parent;
2513 if (!g_list_find(list, fd)) return FALSE;
2515 work = fd->sidecar_files;
2518 if (!g_list_find(list, work->data)) return FALSE;
2525 static gboolean file_data_list_dump(GList *list)
2527 GList *work, *work2;
2532 FileData *fd = work->data;
2533 printf("%s\n", fd->name);
2534 work2 = fd->sidecar_files;
2537 FileData *fd = work2->data;
2538 printf(" %s\n", fd->name);
2539 work2 = work2->next;
2547 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2552 /* change partial groups to independent files */
2557 FileData *fd = work->data;
2560 if (!file_data_list_contains_whole_group(list, fd))
2562 file_data_disable_grouping(fd, TRUE);
2565 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2571 /* remove sidecars from the list,
2572 they can be still acessed via main_fd->sidecar_files */
2576 FileData *fd = work->data;
2580 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2582 out = g_list_prepend(out, file_data_ref(fd));
2586 filelist_free(list);
2587 out = g_list_reverse(out);
2597 * notify other modules about the change described by FileDataChangeInfo
2600 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2601 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2602 implementation in view_file_list.c */
2605 typedef struct _NotifyIdleData NotifyIdleData;
2607 struct _NotifyIdleData {
2613 typedef struct _NotifyData NotifyData;
2615 struct _NotifyData {
2616 FileDataNotifyFunc func;
2618 NotifyPriority priority;
2621 static GList *notify_func_list = NULL;
2623 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2625 NotifyData *nda = (NotifyData *)a;
2626 NotifyData *ndb = (NotifyData *)b;
2628 if (nda->priority < ndb->priority) return -1;
2629 if (nda->priority > ndb->priority) return 1;
2633 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2636 GList *work = notify_func_list;
2640 NotifyData *nd = (NotifyData *)work->data;
2642 if (nd->func == func && nd->data == data)
2644 g_warning("Notify func already registered");
2650 nd = g_new(NotifyData, 1);
2653 nd->priority = priority;
2655 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2656 DEBUG_2("Notify func registered: %p", nd);
2661 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2663 GList *work = notify_func_list;
2667 NotifyData *nd = (NotifyData *)work->data;
2669 if (nd->func == func && nd->data == data)
2671 notify_func_list = g_list_delete_link(notify_func_list, work);
2673 DEBUG_2("Notify func unregistered: %p", nd);
2679 g_warning("Notify func not found");
2684 gboolean file_data_send_notification_idle_cb(gpointer data)
2686 NotifyIdleData *nid = (NotifyIdleData *)data;
2687 GList *work = notify_func_list;
2691 NotifyData *nd = (NotifyData *)work->data;
2693 nd->func(nid->fd, nid->type, nd->data);
2696 file_data_unref(nid->fd);
2701 void file_data_send_notification(FileData *fd, NotifyType type)
2703 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
2704 nid->fd = file_data_ref(fd);
2706 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
2709 static GHashTable *file_data_monitor_pool = NULL;
2710 static guint realtime_monitor_id = 0; /* event source id */
2712 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2716 file_data_check_changed_files(fd);
2718 DEBUG_1("monitor %s", fd->path);
2721 static gboolean realtime_monitor_cb(gpointer data)
2723 if (!options->update_on_time_change) return TRUE;
2724 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2728 gboolean file_data_register_real_time_monitor(FileData *fd)
2734 if (!file_data_monitor_pool)
2735 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2737 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2739 DEBUG_1("Register realtime %d %s", count, fd->path);
2742 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2744 if (!realtime_monitor_id)
2746 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2752 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2756 g_assert(file_data_monitor_pool);
2758 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2760 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2762 g_assert(count > 0);
2767 g_hash_table_remove(file_data_monitor_pool, fd);
2769 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2771 file_data_unref(fd);
2773 if (g_hash_table_size(file_data_monitor_pool) == 0)
2775 g_source_remove(realtime_monitor_id);
2776 realtime_monitor_id = 0;
2782 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */