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 FileData *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 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
575 sfd->parent = target;
576 if (!g_list_find(target->sidecar_files, sfd))
577 target->sidecar_files = g_list_insert_sorted(target->sidecar_files, sfd, file_data_sort_by_ext);
578 file_data_increment_version(sfd); /* increments both sfd and target */
583 static FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
587 file_data_add_sidecar_file(target, source);
589 work = source->sidecar_files;
592 FileData *sfd = work->data;
593 file_data_add_sidecar_file(target, sfd);
597 g_list_free(source->sidecar_files);
598 source->sidecar_files = NULL;
603 static void file_data_check_sidecars(const GList *basename_list)
607 if (!basename_list) return;
608 /* process the group list - the first one is the parent file, others are sidecars */
609 parent_fd = basename_list->data;
610 work = basename_list->next;
613 FileData *sfd = work->data;
616 file_data_merge_sidecar_files(parent_fd, sfd);
619 /* there may be some sidecars that are already deleted - disconnect them */
620 work = parent_fd->sidecar_files;
623 FileData *sfd = work->data;
626 if (!g_list_find((GList *)basename_list, sfd))
628 printf("removing unknown %s: %s \n", parent_fd->path, sfd->path);
629 file_data_disconnect_sidecar_file(parent_fd, sfd);
630 file_data_send_notification(sfd, NOTIFY_REREAD);
631 file_data_send_notification(parent_fd, NOTIFY_REREAD);
636 static FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
638 sfd->parent = target;
639 g_assert(g_list_find(target->sidecar_files, sfd));
641 file_data_increment_version(sfd); /* increments both sfd and target */
643 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
655 /* disables / enables grouping for particular file, sends UPDATE notification */
656 void file_data_disable_grouping(FileData *fd, gboolean disable)
658 if (!fd->disable_grouping == !disable) return;
660 fd->disable_grouping = !!disable;
666 FileData *parent = file_data_ref(fd->parent);
667 file_data_disconnect_sidecar_file(parent, fd);
668 file_data_send_notification(parent, NOTIFY_GROUPING);
669 file_data_unref(parent);
671 else if (fd->sidecar_files)
673 GList *sidecar_files = filelist_copy(fd->sidecar_files);
674 GList *work = sidecar_files;
677 FileData *sfd = work->data;
679 file_data_disconnect_sidecar_file(fd, sfd);
680 file_data_send_notification(sfd, NOTIFY_GROUPING);
682 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
683 filelist_free(sidecar_files);
687 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
692 file_data_increment_version(fd);
693 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
695 file_data_send_notification(fd, NOTIFY_GROUPING);
698 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
705 FileData *fd = work->data;
707 file_data_disable_grouping(fd, disable);
715 *-----------------------------------------------------------------------------
717 *-----------------------------------------------------------------------------
720 static SortType filelist_sort_method = SORT_NONE;
721 static gboolean filelist_sort_ascend = TRUE;
724 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
727 if (!filelist_sort_ascend)
734 switch (filelist_sort_method)
739 if (fa->size < fb->size) return -1;
740 if (fa->size > fb->size) return 1;
741 /* fall back to name */
744 if (fa->date < fb->date) return -1;
745 if (fa->date > fb->date) return 1;
746 /* fall back to name */
748 #ifdef HAVE_STRVERSCMP
750 ret = strverscmp(fa->name, fb->name);
751 if (ret != 0) return ret;
758 if (options->file_sort.case_sensitive)
759 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
761 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
763 if (ret != 0) return ret;
765 /* do not return 0 unless the files are really the same
766 file_data_pool ensures that original_path is unique
768 return strcmp(fa->original_path, fb->original_path);
771 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
773 filelist_sort_method = method;
774 filelist_sort_ascend = ascend;
775 return filelist_sort_compare_filedata(fa, fb);
778 static gint filelist_sort_file_cb(gpointer a, gpointer b)
780 return filelist_sort_compare_filedata(a, b);
783 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
785 filelist_sort_method = method;
786 filelist_sort_ascend = ascend;
787 return g_list_sort(list, cb);
790 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
792 filelist_sort_method = method;
793 filelist_sort_ascend = ascend;
794 return g_list_insert_sorted(list, data, cb);
797 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
799 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
802 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
804 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
808 *-----------------------------------------------------------------------------
809 * basename hash - grouping of sidecars in filelist
810 *-----------------------------------------------------------------------------
814 static GHashTable *file_data_basename_hash_new(void)
816 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
819 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
822 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
824 list = g_hash_table_lookup(basename_hash, basename);
826 if (!g_list_find(list, fd))
828 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
829 g_hash_table_insert(basename_hash, basename, list);
839 static void file_data_basename_hash_remove(GHashTable *basename_hash, FileData *fd)
842 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
844 list = g_hash_table_lookup(basename_hash, basename);
846 if (!g_list_find(list, fd)) return;
848 list = g_list_remove(list, fd);
853 g_hash_table_insert(basename_hash, basename, list);
857 g_hash_table_remove(basename_hash, basename);
863 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
865 filelist_free((GList *)value);
868 static void file_data_basename_hash_free(GHashTable *basename_hash)
870 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
871 g_hash_table_destroy(basename_hash);
875 *-----------------------------------------------------------------------------
876 * handling sidecars in filelist
877 *-----------------------------------------------------------------------------
880 static GList *filelist_filter_out_sidecars(GList *flist)
883 GList *flist_filtered = NULL;
887 FileData *fd = work->data;
890 if (fd->parent) /* remove fd's that are children */
893 flist_filtered = g_list_prepend(flist_filtered, fd);
897 return flist_filtered;
900 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
902 GList *basename_list = (GList *)value;
903 file_data_check_sidecars(basename_list);
907 static gboolean is_hidden_file(const gchar *name)
909 if (name[0] != '.') return FALSE;
910 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
915 *-----------------------------------------------------------------------------
916 * the main filelist function
917 *-----------------------------------------------------------------------------
920 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
927 gint (*stat_func)(const gchar *path, struct stat *buf);
928 GHashTable *basename_hash = NULL;
930 g_assert(files || dirs);
932 if (files) *files = NULL;
933 if (dirs) *dirs = NULL;
935 pathl = path_from_utf8(dir_path);
936 if (!pathl) return FALSE;
945 if (files) basename_hash = file_data_basename_hash_new();
952 while ((dir = readdir(dp)) != NULL)
954 struct stat ent_sbuf;
955 const gchar *name = dir->d_name;
958 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
961 filepath = g_build_filename(pathl, name, NULL);
962 if (stat_func(filepath, &ent_sbuf) >= 0)
964 if (S_ISDIR(ent_sbuf.st_mode))
966 /* we ignore the .thumbnails dir for cleanliness */
968 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
969 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
970 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
971 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
973 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
978 if (files && filter_name_exists(name))
980 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
981 flist = g_list_prepend(flist, fd);
982 if (fd->sidecar_priority && !fd->disable_grouping)
984 file_data_basename_hash_insert(basename_hash, fd);
991 if (errno == EOVERFLOW)
993 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1003 if (dirs) *dirs = dlist;
1006 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1008 *files = filelist_filter_out_sidecars(flist);
1010 if (basename_hash) file_data_basename_hash_free(basename_hash);
1015 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1017 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1020 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1022 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1025 FileData *file_data_new_group(const gchar *path_utf8)
1032 if (!stat_utf8(path_utf8, &st))
1038 if (S_ISDIR(st.st_mode))
1039 return file_data_new(path_utf8, &st, TRUE);
1041 dir = remove_level_from_path(path_utf8);
1043 filelist_read_real(dir, &files, NULL, TRUE);
1045 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1049 filelist_free(files);
1055 void filelist_free(GList *list)
1062 file_data_unref((FileData *)work->data);
1070 GList *filelist_copy(GList *list)
1072 GList *new_list = NULL;
1083 new_list = g_list_prepend(new_list, file_data_ref(fd));
1086 return g_list_reverse(new_list);
1089 GList *filelist_from_path_list(GList *list)
1091 GList *new_list = NULL;
1102 new_list = g_list_prepend(new_list, file_data_new_group(path));
1105 return g_list_reverse(new_list);
1108 GList *filelist_to_path_list(GList *list)
1110 GList *new_list = NULL;
1121 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1124 return g_list_reverse(new_list);
1127 GList *filelist_filter(GList *list, gboolean is_dir_list)
1131 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1136 FileData *fd = (FileData *)(work->data);
1137 const gchar *name = fd->name;
1139 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1140 (!is_dir_list && !filter_name_exists(name)) ||
1141 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1142 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1146 list = g_list_remove_link(list, link);
1147 file_data_unref(fd);
1158 *-----------------------------------------------------------------------------
1159 * filelist recursive
1160 *-----------------------------------------------------------------------------
1163 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1165 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1168 GList *filelist_sort_path(GList *list)
1170 return g_list_sort(list, filelist_sort_path_cb);
1173 static void filelist_recursive_append(GList **list, GList *dirs)
1180 FileData *fd = (FileData *)(work->data);
1184 if (filelist_read(fd, &f, &d))
1186 f = filelist_filter(f, FALSE);
1187 f = filelist_sort_path(f);
1188 *list = g_list_concat(*list, f);
1190 d = filelist_filter(d, TRUE);
1191 d = filelist_sort_path(d);
1192 filelist_recursive_append(list, d);
1200 GList *filelist_recursive(FileData *dir_fd)
1205 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1206 list = filelist_filter(list, FALSE);
1207 list = filelist_sort_path(list);
1209 d = filelist_filter(d, TRUE);
1210 d = filelist_sort_path(d);
1211 filelist_recursive_append(&list, d);
1218 *-----------------------------------------------------------------------------
1219 * file modification support
1220 *-----------------------------------------------------------------------------
1224 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1226 if (!fdci && fd) fdci = fd->change;
1230 g_free(fdci->source);
1235 if (fd) fd->change = NULL;
1238 static gboolean file_data_can_write_directly(FileData *fd)
1240 return filter_name_is_writable(fd->extension);
1243 static gboolean file_data_can_write_sidecar(FileData *fd)
1245 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1248 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1250 gchar *sidecar_path = NULL;
1253 if (!file_data_can_write_sidecar(fd)) return NULL;
1255 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1258 FileData *sfd = work->data;
1260 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
1262 sidecar_path = g_strdup(sfd->path);
1267 if (!existing_only && !sidecar_path)
1269 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1270 sidecar_path = g_strconcat(base, ".xmp", NULL);
1274 return sidecar_path;
1278 * marks and orientation
1281 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1282 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1283 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1284 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1286 gboolean file_data_get_mark(FileData *fd, gint n)
1288 gboolean valid = (fd->valid_marks & (1 << n));
1290 if (file_data_get_mark_func[n] && !valid)
1292 guint old = fd->marks;
1293 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1295 if (!value != !(fd->marks & (1 << n)))
1297 fd->marks = fd->marks ^ (1 << n);
1300 fd->valid_marks |= (1 << n);
1301 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1303 file_data_unref(fd);
1305 else if (!old && fd->marks)
1311 return !!(fd->marks & (1 << n));
1314 guint file_data_get_marks(FileData *fd)
1317 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1321 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1324 if (!value == !file_data_get_mark(fd, n)) return;
1326 if (file_data_set_mark_func[n])
1328 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1333 fd->marks = fd->marks ^ (1 << n);
1335 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1337 file_data_unref(fd);
1339 else if (!old && fd->marks)
1344 file_data_increment_version(fd);
1345 file_data_send_notification(fd, NOTIFY_MARKS);
1348 gboolean file_data_filter_marks(FileData *fd, guint filter)
1351 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1352 return ((fd->marks & filter) == filter);
1355 GList *file_data_filter_marks_list(GList *list, guint filter)
1362 FileData *fd = work->data;
1366 if (!file_data_filter_marks(fd, filter))
1368 list = g_list_remove_link(list, link);
1369 file_data_unref(fd);
1377 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1379 FileData *fd = value;
1380 file_data_increment_version(fd);
1381 file_data_send_notification(fd, NOTIFY_MARKS);
1384 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1386 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1388 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1390 file_data_get_mark_func[n] = get_mark_func;
1391 file_data_set_mark_func[n] = set_mark_func;
1392 file_data_mark_func_data[n] = data;
1393 file_data_destroy_mark_func[n] = notify;
1397 /* this effectively changes all known files */
1398 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1404 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1406 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1407 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1408 if (data) *data = file_data_mark_func_data[n];
1411 gint file_data_get_user_orientation(FileData *fd)
1413 return fd->user_orientation;
1416 void file_data_set_user_orientation(FileData *fd, gint value)
1418 if (fd->user_orientation == value) return;
1420 fd->user_orientation = value;
1421 file_data_increment_version(fd);
1422 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1427 * file_data - operates on the given fd
1428 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1432 /* return list of sidecar file extensions in a string */
1433 gchar *file_data_sc_list_to_string(FileData *fd)
1436 GString *result = g_string_new("");
1438 work = fd->sidecar_files;
1441 FileData *sfd = work->data;
1443 result = g_string_append(result, "+ ");
1444 result = g_string_append(result, sfd->extension);
1446 if (work) result = g_string_append_c(result, ' ');
1449 return g_string_free(result, FALSE);
1455 * add FileDataChangeInfo (see typedefs.h) for the given operation
1456 * uses file_data_add_change_info
1458 * fails if the fd->change already exists - change operations can't run in parallel
1459 * fd->change_info works as a lock
1461 * dest can be NULL - in this case the current name is used for now, it will
1466 FileDataChangeInfo types:
1468 MOVE - path is changed, name may be changed too
1469 RENAME - path remains unchanged, name is changed
1470 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1471 sidecar names are changed too, extensions are not changed
1473 UPDATE - file size, date or grouping has been changed
1476 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1478 FileDataChangeInfo *fdci;
1480 if (fd->change) return FALSE;
1482 fdci = g_new0(FileDataChangeInfo, 1);
1487 fdci->source = g_strdup(src);
1489 fdci->source = g_strdup(fd->path);
1492 fdci->dest = g_strdup(dest);
1499 static void file_data_planned_change_remove(FileData *fd)
1501 if (file_data_planned_change_hash &&
1502 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1504 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1506 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1507 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1508 file_data_unref(fd);
1509 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1511 g_hash_table_destroy(file_data_planned_change_hash);
1512 file_data_planned_change_hash = NULL;
1513 DEBUG_1("planned change: empty");
1520 void file_data_free_ci(FileData *fd)
1522 FileDataChangeInfo *fdci = fd->change;
1526 file_data_planned_change_remove(fd);
1528 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1530 g_free(fdci->source);
1538 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1540 FileDataChangeInfo *fdci = fd->change;
1542 fdci->regroup_when_finished = enable;
1545 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1549 if (fd->parent) fd = fd->parent;
1551 if (fd->change) return FALSE;
1553 work = fd->sidecar_files;
1556 FileData *sfd = work->data;
1558 if (sfd->change) return FALSE;
1562 file_data_add_ci(fd, type, NULL, NULL);
1564 work = fd->sidecar_files;
1567 FileData *sfd = work->data;
1569 file_data_add_ci(sfd, type, NULL, NULL);
1576 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1580 if (fd->parent) fd = fd->parent;
1582 if (!fd->change || fd->change->type != type) return FALSE;
1584 work = fd->sidecar_files;
1587 FileData *sfd = work->data;
1589 if (!sfd->change || sfd->change->type != type) return FALSE;
1597 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1599 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1600 file_data_sc_update_ci_copy(fd, dest_path);
1604 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1606 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1607 file_data_sc_update_ci_move(fd, dest_path);
1611 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1613 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1614 file_data_sc_update_ci_rename(fd, dest_path);
1618 gboolean file_data_sc_add_ci_delete(FileData *fd)
1620 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1623 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1625 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1626 file_data_sc_update_ci_unspecified(fd, dest_path);
1630 gboolean file_data_add_ci_write_metadata(FileData *fd)
1632 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1635 void file_data_sc_free_ci(FileData *fd)
1639 if (fd->parent) fd = fd->parent;
1641 file_data_free_ci(fd);
1643 work = fd->sidecar_files;
1646 FileData *sfd = work->data;
1648 file_data_free_ci(sfd);
1653 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1656 gboolean ret = TRUE;
1661 FileData *fd = work->data;
1663 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1670 static void file_data_sc_revert_ci_list(GList *fd_list)
1677 FileData *fd = work->data;
1679 file_data_sc_free_ci(fd);
1684 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1691 FileData *fd = work->data;
1693 if (!func(fd, dest))
1695 file_data_sc_revert_ci_list(work->prev);
1704 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1706 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1709 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1711 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1714 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1716 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1719 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1721 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1724 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1727 gboolean ret = TRUE;
1732 FileData *fd = work->data;
1734 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1741 void file_data_free_ci_list(GList *fd_list)
1748 FileData *fd = work->data;
1750 file_data_free_ci(fd);
1755 void file_data_sc_free_ci_list(GList *fd_list)
1762 FileData *fd = work->data;
1764 file_data_sc_free_ci(fd);
1770 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1771 * fails if fd->change does not exist or the change type does not match
1774 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1776 FileDataChangeType type = fd->change->type;
1778 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1782 if (!file_data_planned_change_hash)
1783 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1785 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1787 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1788 g_hash_table_remove(file_data_planned_change_hash, old_path);
1789 file_data_unref(fd);
1792 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1797 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1798 g_hash_table_remove(file_data_planned_change_hash, new_path);
1799 file_data_unref(ofd);
1802 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1804 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1809 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1811 gchar *old_path = fd->change->dest;
1813 fd->change->dest = g_strdup(dest_path);
1814 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1818 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1820 const gchar *extension = extension_from_path(fd->change->source);
1821 gchar *base = remove_extension_from_path(dest_path);
1822 gchar *old_path = fd->change->dest;
1824 fd->change->dest = g_strconcat(base, extension, NULL);
1825 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1831 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1834 gchar *dest_path_full = NULL;
1836 if (fd->parent) fd = fd->parent;
1840 dest_path = fd->path;
1842 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1844 gchar *dir = remove_level_from_path(fd->path);
1846 dest_path_full = g_build_filename(dir, dest_path, NULL);
1848 dest_path = dest_path_full;
1850 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1852 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1853 dest_path = dest_path_full;
1856 file_data_update_ci_dest(fd, dest_path);
1858 work = fd->sidecar_files;
1861 FileData *sfd = work->data;
1863 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1867 g_free(dest_path_full);
1870 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1872 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1873 file_data_sc_update_ci(fd, dest_path);
1877 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1879 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1882 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1884 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1887 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1889 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1892 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1894 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1897 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1899 gboolean (*func)(FileData *, const gchar *))
1902 gboolean ret = TRUE;
1907 FileData *fd = work->data;
1909 if (!func(fd, dest)) ret = FALSE;
1916 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1918 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1921 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1923 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1926 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1928 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1933 * verify source and dest paths - dest image exists, etc.
1934 * it should detect all possible problems with the planned operation
1937 gint file_data_verify_ci(FileData *fd)
1939 gint ret = CHANGE_OK;
1944 DEBUG_1("Change checked: no change info: %s", fd->path);
1948 if (!isname(fd->path))
1950 /* this probably should not happen */
1951 ret |= CHANGE_NO_SRC;
1952 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1956 dir = remove_level_from_path(fd->path);
1958 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1959 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
1960 fd->change->type != FILEDATA_CHANGE_RENAME &&
1961 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1964 ret |= CHANGE_WARN_UNSAVED_META;
1965 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
1968 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1969 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1970 !access_file(fd->path, R_OK))
1972 ret |= CHANGE_NO_READ_PERM;
1973 DEBUG_1("Change checked: no read permission: %s", fd->path);
1975 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1976 !access_file(dir, W_OK))
1978 ret |= CHANGE_NO_WRITE_PERM_DIR;
1979 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1981 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1982 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1983 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1984 !access_file(fd->path, W_OK))
1986 ret |= CHANGE_WARN_NO_WRITE_PERM;
1987 DEBUG_1("Change checked: no write permission: %s", fd->path);
1989 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1990 - that means that there are no hard errors and warnings can be disabled
1991 - the destination is determined during the check
1993 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1995 /* determine destination file */
1996 gboolean have_dest = FALSE;
1997 gchar *dest_dir = NULL;
1999 if (options->metadata.save_in_image_file)
2001 if (file_data_can_write_directly(fd))
2003 /* we can write the file directly */
2004 if (access_file(fd->path, W_OK))
2010 if (options->metadata.warn_on_write_problems)
2012 ret |= CHANGE_WARN_NO_WRITE_PERM;
2013 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2017 else if (file_data_can_write_sidecar(fd))
2019 /* we can write sidecar */
2020 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2021 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2023 file_data_update_ci_dest(fd, sidecar);
2028 if (options->metadata.warn_on_write_problems)
2030 ret |= CHANGE_WARN_NO_WRITE_PERM;
2031 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2040 /* write private metadata file under ~/.geeqie */
2042 /* If an existing metadata file exists, we will try writing to
2043 * it's location regardless of the user's preference.
2045 gchar *metadata_path = NULL;
2047 /* but ignore XMP if we are not able to write it */
2048 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2050 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2052 if (metadata_path && !access_file(metadata_path, W_OK))
2054 g_free(metadata_path);
2055 metadata_path = NULL;
2062 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2063 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2065 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2067 metadata_path = g_build_filename(dest_dir, filename, NULL);
2071 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2073 file_data_update_ci_dest(fd, metadata_path);
2078 ret |= CHANGE_NO_WRITE_PERM_DEST;
2079 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2081 g_free(metadata_path);
2086 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2091 same = (strcmp(fd->path, fd->change->dest) == 0);
2095 const gchar *dest_ext = extension_from_path(fd->change->dest);
2096 if (!dest_ext) dest_ext = "";
2098 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2100 ret |= CHANGE_WARN_CHANGED_EXT;
2101 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2106 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2108 ret |= CHANGE_WARN_SAME;
2109 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2113 dest_dir = remove_level_from_path(fd->change->dest);
2115 if (!isdir(dest_dir))
2117 ret |= CHANGE_NO_DEST_DIR;
2118 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2120 else if (!access_file(dest_dir, W_OK))
2122 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2123 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2127 if (isfile(fd->change->dest))
2129 if (!access_file(fd->change->dest, W_OK))
2131 ret |= CHANGE_NO_WRITE_PERM_DEST;
2132 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2136 ret |= CHANGE_WARN_DEST_EXISTS;
2137 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2140 else if (isdir(fd->change->dest))
2142 ret |= CHANGE_DEST_EXISTS;
2143 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2150 fd->change->error = ret;
2151 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2158 gint file_data_sc_verify_ci(FileData *fd)
2163 ret = file_data_verify_ci(fd);
2165 work = fd->sidecar_files;
2168 FileData *sfd = work->data;
2170 ret |= file_data_verify_ci(sfd);
2177 gchar *file_data_get_error_string(gint error)
2179 GString *result = g_string_new("");
2181 if (error & CHANGE_NO_SRC)
2183 if (result->len > 0) g_string_append(result, ", ");
2184 g_string_append(result, _("file or directory does not exist"));
2187 if (error & CHANGE_DEST_EXISTS)
2189 if (result->len > 0) g_string_append(result, ", ");
2190 g_string_append(result, _("destination already exists"));
2193 if (error & CHANGE_NO_WRITE_PERM_DEST)
2195 if (result->len > 0) g_string_append(result, ", ");
2196 g_string_append(result, _("destination can't be overwritten"));
2199 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2201 if (result->len > 0) g_string_append(result, ", ");
2202 g_string_append(result, _("destination directory is not writable"));
2205 if (error & CHANGE_NO_DEST_DIR)
2207 if (result->len > 0) g_string_append(result, ", ");
2208 g_string_append(result, _("destination directory does not exist"));
2211 if (error & CHANGE_NO_WRITE_PERM_DIR)
2213 if (result->len > 0) g_string_append(result, ", ");
2214 g_string_append(result, _("source directory is not writable"));
2217 if (error & CHANGE_NO_READ_PERM)
2219 if (result->len > 0) g_string_append(result, ", ");
2220 g_string_append(result, _("no read permission"));
2223 if (error & CHANGE_WARN_NO_WRITE_PERM)
2225 if (result->len > 0) g_string_append(result, ", ");
2226 g_string_append(result, _("file is readonly"));
2229 if (error & CHANGE_WARN_DEST_EXISTS)
2231 if (result->len > 0) g_string_append(result, ", ");
2232 g_string_append(result, _("destination already exists and will be overwritten"));
2235 if (error & CHANGE_WARN_SAME)
2237 if (result->len > 0) g_string_append(result, ", ");
2238 g_string_append(result, _("source and destination are the same"));
2241 if (error & CHANGE_WARN_CHANGED_EXT)
2243 if (result->len > 0) g_string_append(result, ", ");
2244 g_string_append(result, _("source and destination have different extension"));
2247 if (error & CHANGE_WARN_UNSAVED_META)
2249 if (result->len > 0) g_string_append(result, ", ");
2250 g_string_append(result, _("there are unsaved metadata changes for the file"));
2253 return g_string_free(result, FALSE);
2256 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2259 gint all_errors = 0;
2260 gint common_errors = ~0;
2265 if (!list) return 0;
2267 num = g_list_length(list);
2268 errors = g_new(int, num);
2279 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2280 all_errors |= error;
2281 common_errors &= error;
2288 if (desc && all_errors)
2291 GString *result = g_string_new("");
2295 gchar *str = file_data_get_error_string(common_errors);
2296 g_string_append(result, str);
2297 g_string_append(result, "\n");
2311 error = errors[i] & ~common_errors;
2315 gchar *str = file_data_get_error_string(error);
2316 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2321 *desc = g_string_free(result, FALSE);
2330 * perform the change described by FileFataChangeInfo
2331 * it is used for internal operations,
2332 * this function actually operates with files on the filesystem
2333 * it should implement safe delete
2336 static gboolean file_data_perform_move(FileData *fd)
2338 g_assert(!strcmp(fd->change->source, fd->path));
2339 return move_file(fd->change->source, fd->change->dest);
2342 static gboolean file_data_perform_copy(FileData *fd)
2344 g_assert(!strcmp(fd->change->source, fd->path));
2345 return copy_file(fd->change->source, fd->change->dest);
2348 static gboolean file_data_perform_delete(FileData *fd)
2350 if (isdir(fd->path) && !islink(fd->path))
2351 return rmdir_utf8(fd->path);
2353 if (options->file_ops.safe_delete_enable)
2354 return file_util_safe_unlink(fd->path);
2356 return unlink_file(fd->path);
2359 gboolean file_data_perform_ci(FileData *fd)
2361 FileDataChangeType type = fd->change->type;
2365 case FILEDATA_CHANGE_MOVE:
2366 return file_data_perform_move(fd);
2367 case FILEDATA_CHANGE_COPY:
2368 return file_data_perform_copy(fd);
2369 case FILEDATA_CHANGE_RENAME:
2370 return file_data_perform_move(fd); /* the same as move */
2371 case FILEDATA_CHANGE_DELETE:
2372 return file_data_perform_delete(fd);
2373 case FILEDATA_CHANGE_WRITE_METADATA:
2374 return metadata_write_perform(fd);
2375 case FILEDATA_CHANGE_UNSPECIFIED:
2376 /* nothing to do here */
2384 gboolean file_data_sc_perform_ci(FileData *fd)
2387 gboolean ret = TRUE;
2388 FileDataChangeType type = fd->change->type;
2390 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2392 work = fd->sidecar_files;
2395 FileData *sfd = work->data;
2397 if (!file_data_perform_ci(sfd)) ret = FALSE;
2401 if (!file_data_perform_ci(fd)) ret = FALSE;
2407 * updates FileData structure according to FileDataChangeInfo
2410 gboolean file_data_apply_ci(FileData *fd)
2412 FileDataChangeType type = fd->change->type;
2415 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2417 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2418 file_data_planned_change_remove(fd);
2420 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2422 /* this change overwrites another file which is already known to other modules
2423 renaming fd would create duplicate FileData structure
2424 the best thing we can do is nothing
2425 FIXME: maybe we could copy stuff like marks
2427 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2431 file_data_set_path(fd, fd->change->dest);
2434 file_data_increment_version(fd);
2435 file_data_send_notification(fd, NOTIFY_CHANGE);
2440 gboolean file_data_sc_apply_ci(FileData *fd)
2443 FileDataChangeType type = fd->change->type;
2445 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2447 work = fd->sidecar_files;
2450 FileData *sfd = work->data;
2452 file_data_apply_ci(sfd);
2456 file_data_apply_ci(fd);
2461 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2464 if (fd->parent) fd = fd->parent;
2465 if (!g_list_find(list, fd)) return FALSE;
2467 work = fd->sidecar_files;
2470 if (!g_list_find(list, work->data)) return FALSE;
2477 static gboolean file_data_list_dump(GList *list)
2479 GList *work, *work2;
2484 FileData *fd = work->data;
2485 printf("%s\n", fd->name);
2486 work2 = fd->sidecar_files;
2489 FileData *fd = work2->data;
2490 printf(" %s\n", fd->name);
2491 work2 = work2->next;
2499 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2504 /* change partial groups to independent files */
2509 FileData *fd = work->data;
2512 if (!file_data_list_contains_whole_group(list, fd))
2514 file_data_disable_grouping(fd, TRUE);
2517 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2523 /* remove sidecars from the list,
2524 they can be still acessed via main_fd->sidecar_files */
2528 FileData *fd = work->data;
2532 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2534 out = g_list_prepend(out, file_data_ref(fd));
2538 filelist_free(list);
2539 out = g_list_reverse(out);
2549 * notify other modules about the change described by FileDataChangeInfo
2552 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2553 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2554 implementation in view_file_list.c */
2557 typedef struct _NotifyIdleData NotifyIdleData;
2559 struct _NotifyIdleData {
2565 typedef struct _NotifyData NotifyData;
2567 struct _NotifyData {
2568 FileDataNotifyFunc func;
2570 NotifyPriority priority;
2573 static GList *notify_func_list = NULL;
2575 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2577 NotifyData *nda = (NotifyData *)a;
2578 NotifyData *ndb = (NotifyData *)b;
2580 if (nda->priority < ndb->priority) return -1;
2581 if (nda->priority > ndb->priority) return 1;
2585 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2588 GList *work = notify_func_list;
2592 NotifyData *nd = (NotifyData *)work->data;
2594 if (nd->func == func && nd->data == data)
2596 g_warning("Notify func already registered");
2602 nd = g_new(NotifyData, 1);
2605 nd->priority = priority;
2607 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2608 DEBUG_2("Notify func registered: %p", nd);
2613 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2615 GList *work = notify_func_list;
2619 NotifyData *nd = (NotifyData *)work->data;
2621 if (nd->func == func && nd->data == data)
2623 notify_func_list = g_list_delete_link(notify_func_list, work);
2625 DEBUG_2("Notify func unregistered: %p", nd);
2631 g_warning("Notify func not found");
2636 gboolean file_data_send_notification_idle_cb(gpointer data)
2638 NotifyIdleData *nid = (NotifyIdleData *)data;
2639 GList *work = notify_func_list;
2643 NotifyData *nd = (NotifyData *)work->data;
2645 nd->func(nid->fd, nid->type, nd->data);
2648 file_data_unref(nid->fd);
2653 void file_data_send_notification(FileData *fd, NotifyType type)
2655 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
2656 nid->fd = file_data_ref(fd);
2658 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
2661 static GHashTable *file_data_monitor_pool = NULL;
2662 static guint realtime_monitor_id = 0; /* event source id */
2664 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2668 file_data_check_changed_files(fd);
2670 DEBUG_1("monitor %s", fd->path);
2673 static gboolean realtime_monitor_cb(gpointer data)
2675 if (!options->update_on_time_change) return TRUE;
2676 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2680 gboolean file_data_register_real_time_monitor(FileData *fd)
2686 if (!file_data_monitor_pool)
2687 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2689 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2691 DEBUG_1("Register realtime %d %s", count, fd->path);
2694 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2696 if (!realtime_monitor_id)
2698 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2704 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2708 g_assert(file_data_monitor_pool);
2710 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2712 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2714 g_assert(count > 0);
2719 g_hash_table_remove(file_data_monitor_pool, fd);
2721 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2723 file_data_unref(fd);
2725 if (g_hash_table_size(file_data_monitor_pool) == 0)
2727 g_source_remove(realtime_monitor_id);
2728 realtime_monitor_id = 0;
2734 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */