4 * Copyright (C) 2008 - 2010 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 FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars);
35 *-----------------------------------------------------------------------------
36 * text conversion utils
37 *-----------------------------------------------------------------------------
40 gchar *text_from_size(gint64 size)
46 /* what I would like to use is printf("%'d", size)
47 * BUT: not supported on every libc :(
51 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
52 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
56 a = g_strdup_printf("%d", (guint)size);
62 b = g_new(gchar, l + n + 1);
87 gchar *text_from_size_abrev(gint64 size)
89 if (size < (gint64)1024)
91 return g_strdup_printf(_("%d bytes"), (gint)size);
93 if (size < (gint64)1048576)
95 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
97 if (size < (gint64)1073741824)
99 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
102 /* to avoid overflowing the gdouble, do division in two steps */
104 return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
107 /* note: returned string is valid until next call to text_from_time() */
108 const gchar *text_from_time(time_t t)
110 static gchar *ret = NULL;
114 GError *error = NULL;
116 btime = localtime(&t);
118 /* the %x warning about 2 digit years is not an error */
119 buflen = strftime(buf, sizeof(buf), "%x %X", btime);
120 if (buflen < 1) return "";
123 ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
126 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
135 *-----------------------------------------------------------------------------
137 *-----------------------------------------------------------------------------
140 static FileData *file_data_merge_sidecar_files(FileData *target, FileData *source);
141 static void file_data_check_sidecars(const GList *basename_list);
142 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
145 void file_data_increment_version(FileData *fd)
151 fd->parent->version++;
152 fd->parent->valid_marks = 0;
156 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
158 const FileData *fda = a;
159 const FileData *fdb = b;
161 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
162 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
164 return strcmp(fdb->extension, fda->extension);
167 static GHashTable *file_data_basename_hash_new(void)
169 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
172 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
175 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
177 list = g_hash_table_lookup(basename_hash, basename);
179 if (!g_list_find(list, fd))
181 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
182 g_hash_table_insert(basename_hash, basename, list);
192 static void file_data_basename_hash_remove(GHashTable *basename_hash, FileData *fd)
195 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
197 list = g_hash_table_lookup(basename_hash, basename);
199 if (!g_list_find(list, fd)) return;
201 list = g_list_remove(list, fd);
206 g_hash_table_insert(basename_hash, basename, list);
210 g_hash_table_remove(basename_hash, basename);
216 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
218 filelist_free((GList *)value);
221 static void file_data_basename_hash_free(GHashTable *basename_hash)
223 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
224 g_hash_table_destroy(basename_hash);
227 static void file_data_set_collate_keys(FileData *fd)
229 gchar *caseless_name;
231 caseless_name = g_utf8_casefold(fd->name, -1);
233 g_free(fd->collate_key_name);
234 g_free(fd->collate_key_name_nocase);
236 #if 0 && GLIB_CHECK_VERSION(2, 8, 0)
237 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
238 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
240 fd->collate_key_name = g_utf8_collate_key(fd->name, -1);
241 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
243 g_free(caseless_name);
246 static void file_data_set_path(FileData *fd, const gchar *path)
248 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
249 g_assert(file_data_pool);
253 if (fd->original_path)
255 g_hash_table_remove(file_data_pool, fd->original_path);
256 g_free(fd->original_path);
259 g_assert(!g_hash_table_lookup(file_data_pool, path));
261 fd->original_path = g_strdup(path);
262 g_hash_table_insert(file_data_pool, fd->original_path, fd);
264 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
266 fd->path = g_strdup(path);
268 fd->extension = fd->name + 1;
269 file_data_set_collate_keys(fd);
273 fd->path = g_strdup(path);
274 fd->name = filename_from_path(fd->path);
276 if (strcmp(fd->name, "..") == 0)
278 gchar *dir = remove_level_from_path(path);
280 fd->path = remove_level_from_path(dir);
283 fd->extension = fd->name + 2;
284 file_data_set_collate_keys(fd);
287 else if (strcmp(fd->name, ".") == 0)
290 fd->path = remove_level_from_path(path);
292 fd->extension = fd->name + 1;
293 file_data_set_collate_keys(fd);
297 fd->extension = registered_extension_from_path(fd->path);
298 if (fd->extension == NULL)
300 fd->extension = fd->name + strlen(fd->name);
303 fd->sidecar_priority = sidecar_file_priority(fd->extension);
304 file_data_set_collate_keys(fd);
307 static gboolean file_data_check_changed(FileData *fd, struct stat *st)
309 if (fd->size != st->st_size ||
310 fd->date != st->st_mtime)
312 fd->size = st->st_size;
313 fd->date = st->st_mtime;
314 fd->mode = st->st_mode;
315 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
316 fd->thumb_pixbuf = NULL;
317 file_data_increment_version(fd);
318 file_data_send_notification(fd, NOTIFY_REREAD);
324 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
326 gboolean ret = FALSE;
329 ret = file_data_check_changed(fd, st);
331 work = fd->sidecar_files;
334 FileData *sfd = work->data;
338 if (!stat_utf8(sfd->path, &st))
342 file_data_disconnect_sidecar_file(fd, sfd);
347 ret |= file_data_check_changed_files_recursive(sfd, &st);
353 gboolean file_data_check_changed_files(FileData *fd)
355 gboolean ret = FALSE;
358 if (fd->parent) fd = fd->parent;
360 if (!stat_utf8(fd->path, &st))
364 FileData *sfd = NULL;
366 /* parent is missing, we have to rebuild whole group */
371 /* file_data_disconnect_sidecar_file might delete the file,
372 we have to keep the reference to prevent this */
373 sidecars = filelist_copy(fd->sidecar_files);
380 file_data_disconnect_sidecar_file(fd, sfd);
382 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
383 /* now we can release the sidecars */
384 filelist_free(sidecars);
385 file_data_send_notification(fd, NOTIFY_REREAD);
389 ret |= file_data_check_changed_files_recursive(fd, &st);
395 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
399 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
401 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
404 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
406 fd = g_hash_table_lookup(file_data_pool, path_utf8);
412 if (!fd && file_data_planned_change_hash)
414 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
417 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
419 file_data_apply_ci(fd);
427 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
430 changed = file_data_check_changed(fd, st);
432 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
437 fd = g_new0(FileData, 1);
439 fd->size = st->st_size;
440 fd->date = st->st_mtime;
441 fd->mode = st->st_mode;
443 fd->magick = 0x12345678;
445 if (disable_sidecars) fd->disable_grouping = TRUE;
447 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
453 static void file_data_check_sidecars(const GList *basename_list)
457 if (!basename_list) return;
458 /* process the group list - the first one is the parent file, others are sidecars */
459 parent_fd = basename_list->data;
460 work = basename_list->next;
463 FileData *sfd = work->data;
466 file_data_merge_sidecar_files(parent_fd, sfd);
469 /* there may be some sidecars that are already deleted - disconnect them */
470 work = parent_fd->sidecar_files;
473 FileData *sfd = work->data;
476 if (!g_list_find((GList *)basename_list, sfd))
478 printf("removing unknown %s: %s \n", parent_fd->path, sfd->path);
479 file_data_disconnect_sidecar_file(parent_fd, sfd);
480 file_data_send_notification(sfd, NOTIFY_REREAD);
481 file_data_send_notification(parent_fd, NOTIFY_REREAD);
487 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
489 gchar *path_utf8 = path_to_utf8(path);
490 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
496 static FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
498 sfd->parent = target;
499 if (!g_list_find(target->sidecar_files, sfd))
500 target->sidecar_files = g_list_insert_sorted(target->sidecar_files, sfd, file_data_sort_by_ext);
501 file_data_increment_version(sfd); /* increments both sfd and target */
506 static FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
510 file_data_add_sidecar_file(target, source);
512 work = source->sidecar_files;
515 FileData *sfd = work->data;
516 file_data_add_sidecar_file(target, sfd);
520 g_list_free(source->sidecar_files);
521 source->sidecar_files = NULL;
526 #ifdef DEBUG_FILEDATA
527 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
529 FileData *file_data_ref(FileData *fd)
532 if (fd == NULL) return NULL;
533 #ifdef DEBUG_FILEDATA
534 if (fd->magick != 0x12345678)
535 DEBUG_0("fd magick mismatch at %s:%d", file, line);
537 g_assert(fd->magick == 0x12345678);
540 #ifdef DEBUG_FILEDATA
541 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
543 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
548 static void file_data_free(FileData *fd)
550 g_assert(fd->magick == 0x12345678);
551 g_assert(fd->ref == 0);
553 metadata_cache_free(fd);
554 g_hash_table_remove(file_data_pool, fd->original_path);
557 g_free(fd->original_path);
558 g_free(fd->collate_key_name);
559 g_free(fd->collate_key_name_nocase);
560 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
561 histmap_free(fd->histmap);
563 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
565 file_data_change_info_free(NULL, fd);
569 #ifdef DEBUG_FILEDATA
570 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
572 void file_data_unref(FileData *fd)
575 if (fd == NULL) return;
576 #ifdef DEBUG_FILEDATA
577 if (fd->magick != 0x12345678)
578 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
580 g_assert(fd->magick == 0x12345678);
583 #ifdef DEBUG_FILEDATA
584 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
586 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
591 FileData *parent = fd->parent ? fd->parent : fd;
593 if (parent->ref > 0) return;
595 work = parent->sidecar_files;
598 FileData *sfd = work->data;
599 if (sfd->ref > 0) return;
603 /* none of parent/children is referenced, we can free everything */
605 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
607 work = parent->sidecar_files;
610 FileData *sfd = work->data;
615 g_list_free(parent->sidecar_files);
616 parent->sidecar_files = NULL;
618 file_data_free(parent);
622 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
624 sfd->parent = target;
625 g_assert(g_list_find(target->sidecar_files, sfd));
627 file_data_increment_version(sfd); /* increments both sfd and target */
629 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
641 /* disables / enables grouping for particular file, sends UPDATE notification */
642 void file_data_disable_grouping(FileData *fd, gboolean disable)
644 if (!fd->disable_grouping == !disable) return;
646 fd->disable_grouping = !!disable;
652 FileData *parent = file_data_ref(fd->parent);
653 file_data_disconnect_sidecar_file(parent, fd);
654 file_data_send_notification(parent, NOTIFY_GROUPING);
655 file_data_unref(parent);
657 else if (fd->sidecar_files)
659 GList *sidecar_files = filelist_copy(fd->sidecar_files);
660 GList *work = sidecar_files;
663 FileData *sfd = work->data;
665 file_data_disconnect_sidecar_file(fd, sfd);
666 file_data_send_notification(sfd, NOTIFY_GROUPING);
668 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
669 filelist_free(sidecar_files);
673 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
678 file_data_increment_version(fd);
679 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
681 file_data_send_notification(fd, NOTIFY_GROUPING);
684 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
691 FileData *fd = work->data;
693 file_data_disable_grouping(fd, disable);
699 /* compare name without extension */
700 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
702 size_t len1 = fd1->extension - fd1->name;
703 size_t len2 = fd2->extension - fd2->name;
705 if (len1 < len2) return -1;
706 if (len1 > len2) return 1;
708 return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
711 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
713 if (!fdci && fd) fdci = fd->change;
717 g_free(fdci->source);
722 if (fd) fd->change = NULL;
725 static gboolean file_data_can_write_directly(FileData *fd)
727 return filter_name_is_writable(fd->extension);
730 static gboolean file_data_can_write_sidecar(FileData *fd)
732 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
735 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
737 gchar *sidecar_path = NULL;
740 if (!file_data_can_write_sidecar(fd)) return NULL;
742 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
745 FileData *sfd = work->data;
747 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
749 sidecar_path = g_strdup(sfd->path);
754 if (!existing_only && !sidecar_path)
756 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
757 sidecar_path = g_strconcat(base, ".xmp", NULL);
766 *-----------------------------------------------------------------------------
767 * sidecar file info struct
768 *-----------------------------------------------------------------------------
773 static gint sidecar_file_priority(const gchar *extension)
778 if (extension == NULL)
781 work = sidecar_ext_get_list();
784 gchar *ext = work->data;
787 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
795 *-----------------------------------------------------------------------------
797 *-----------------------------------------------------------------------------
800 static SortType filelist_sort_method = SORT_NONE;
801 static gboolean filelist_sort_ascend = TRUE;
804 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
807 if (!filelist_sort_ascend)
814 switch (filelist_sort_method)
819 if (fa->size < fb->size) return -1;
820 if (fa->size > fb->size) return 1;
821 /* fall back to name */
824 if (fa->date < fb->date) return -1;
825 if (fa->date > fb->date) return 1;
826 /* fall back to name */
828 #ifdef HAVE_STRVERSCMP
830 ret = strverscmp(fa->name, fb->name);
831 if (ret != 0) return ret;
838 if (options->file_sort.case_sensitive)
839 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
841 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
843 if (ret != 0) return ret;
845 /* do not return 0 unless the files are really the same
846 file_data_pool ensures that original_path is unique
848 return strcmp(fa->original_path, fb->original_path);
851 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
853 filelist_sort_method = method;
854 filelist_sort_ascend = ascend;
855 return filelist_sort_compare_filedata(fa, fb);
858 static gint filelist_sort_file_cb(gpointer a, gpointer b)
860 return filelist_sort_compare_filedata(a, b);
863 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
865 filelist_sort_method = method;
866 filelist_sort_ascend = ascend;
867 return g_list_sort(list, cb);
870 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
872 filelist_sort_method = method;
873 filelist_sort_ascend = ascend;
874 return g_list_insert_sorted(list, data, cb);
877 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
879 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
882 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
884 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
888 static GList *filelist_filter_out_sidecars(GList *flist)
891 GList *flist_filtered = NULL;
895 FileData *fd = work->data;
898 if (fd->parent) /* remove fd's that are children */
901 flist_filtered = g_list_prepend(flist_filtered, fd);
905 return flist_filtered;
908 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
910 GList *basename_list = (GList *)value;
911 file_data_check_sidecars(basename_list);
915 static gboolean is_hidden_file(const gchar *name)
917 if (name[0] != '.') return FALSE;
918 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
922 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
929 gint (*stat_func)(const gchar *path, struct stat *buf);
930 GHashTable *basename_hash = NULL;
932 g_assert(files || dirs);
934 if (files) *files = NULL;
935 if (dirs) *dirs = NULL;
937 pathl = path_from_utf8(dir_path);
938 if (!pathl) return FALSE;
947 if (files) basename_hash = file_data_basename_hash_new();
954 while ((dir = readdir(dp)) != NULL)
956 struct stat ent_sbuf;
957 const gchar *name = dir->d_name;
960 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
963 filepath = g_build_filename(pathl, name, NULL);
964 if (stat_func(filepath, &ent_sbuf) >= 0)
966 if (S_ISDIR(ent_sbuf.st_mode))
968 /* we ignore the .thumbnails dir for cleanliness */
970 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
971 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
972 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
973 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
975 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
980 if (files && filter_name_exists(name))
982 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
983 flist = g_list_prepend(flist, fd);
984 if (fd->sidecar_priority && !fd->disable_grouping)
986 file_data_basename_hash_insert(basename_hash, fd);
993 if (errno == EOVERFLOW)
995 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1005 if (dirs) *dirs = dlist;
1008 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1010 *files = filelist_filter_out_sidecars(flist);
1012 if (basename_hash) file_data_basename_hash_free(basename_hash);
1017 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1019 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1022 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1024 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1027 FileData *file_data_new_simple(const gchar *path_utf8)
1034 if (!stat_utf8(path_utf8, &st))
1040 if (S_ISDIR(st.st_mode))
1041 return file_data_new(path_utf8, &st, TRUE);
1043 dir = remove_level_from_path(path_utf8);
1045 filelist_read_real(dir, &files, NULL, TRUE);
1047 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1051 filelist_free(files);
1056 FileData *file_data_new_no_grouping(const gchar *path_utf8)
1060 if (!stat_utf8(path_utf8, &st))
1066 return file_data_new(path_utf8, &st, TRUE);
1069 FileData *file_data_new_dir(const gchar *path_utf8)
1073 if (!stat_utf8(path_utf8, &st))
1079 g_assert(S_ISDIR(st.st_mode));
1080 return file_data_new(path_utf8, &st, TRUE);
1083 void filelist_free(GList *list)
1090 file_data_unref((FileData *)work->data);
1098 GList *filelist_copy(GList *list)
1100 GList *new_list = NULL;
1111 new_list = g_list_prepend(new_list, file_data_ref(fd));
1114 return g_list_reverse(new_list);
1117 GList *filelist_from_path_list(GList *list)
1119 GList *new_list = NULL;
1130 new_list = g_list_prepend(new_list, file_data_new_simple(path));
1133 return g_list_reverse(new_list);
1136 GList *filelist_to_path_list(GList *list)
1138 GList *new_list = NULL;
1149 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1152 return g_list_reverse(new_list);
1155 GList *filelist_filter(GList *list, gboolean is_dir_list)
1159 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1164 FileData *fd = (FileData *)(work->data);
1165 const gchar *name = fd->name;
1167 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1168 (!is_dir_list && !filter_name_exists(name)) ||
1169 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1170 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1174 list = g_list_remove_link(list, link);
1175 file_data_unref(fd);
1186 *-----------------------------------------------------------------------------
1187 * filelist recursive
1188 *-----------------------------------------------------------------------------
1191 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1193 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1196 GList *filelist_sort_path(GList *list)
1198 return g_list_sort(list, filelist_sort_path_cb);
1201 static void filelist_recursive_append(GList **list, GList *dirs)
1208 FileData *fd = (FileData *)(work->data);
1212 if (filelist_read(fd, &f, &d))
1214 f = filelist_filter(f, FALSE);
1215 f = filelist_sort_path(f);
1216 *list = g_list_concat(*list, f);
1218 d = filelist_filter(d, TRUE);
1219 d = filelist_sort_path(d);
1220 filelist_recursive_append(list, d);
1228 GList *filelist_recursive(FileData *dir_fd)
1233 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1234 list = filelist_filter(list, FALSE);
1235 list = filelist_sort_path(list);
1237 d = filelist_filter(d, TRUE);
1238 d = filelist_sort_path(d);
1239 filelist_recursive_append(&list, d);
1247 * marks and orientation
1250 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1251 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1252 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1253 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1255 gboolean file_data_get_mark(FileData *fd, gint n)
1257 gboolean valid = (fd->valid_marks & (1 << n));
1259 if (file_data_get_mark_func[n] && !valid)
1261 guint old = fd->marks;
1262 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1264 if (!value != !(fd->marks & (1 << n)))
1266 fd->marks = fd->marks ^ (1 << n);
1269 fd->valid_marks |= (1 << n);
1270 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1272 file_data_unref(fd);
1274 else if (!old && fd->marks)
1280 return !!(fd->marks & (1 << n));
1283 guint file_data_get_marks(FileData *fd)
1286 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1290 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1293 if (!value == !file_data_get_mark(fd, n)) return;
1295 if (file_data_set_mark_func[n])
1297 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1302 fd->marks = fd->marks ^ (1 << n);
1304 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1306 file_data_unref(fd);
1308 else if (!old && fd->marks)
1313 file_data_increment_version(fd);
1314 file_data_send_notification(fd, NOTIFY_MARKS);
1317 gboolean file_data_filter_marks(FileData *fd, guint filter)
1320 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1321 return ((fd->marks & filter) == filter);
1324 GList *file_data_filter_marks_list(GList *list, guint filter)
1331 FileData *fd = work->data;
1335 if (!file_data_filter_marks(fd, filter))
1337 list = g_list_remove_link(list, link);
1338 file_data_unref(fd);
1346 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1348 FileData *fd = value;
1349 file_data_increment_version(fd);
1350 file_data_send_notification(fd, NOTIFY_MARKS);
1353 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1355 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1357 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1359 file_data_get_mark_func[n] = get_mark_func;
1360 file_data_set_mark_func[n] = set_mark_func;
1361 file_data_mark_func_data[n] = data;
1362 file_data_destroy_mark_func[n] = notify;
1366 /* this effectively changes all known files */
1367 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1373 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1375 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1376 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1377 if (data) *data = file_data_mark_func_data[n];
1380 gint file_data_get_user_orientation(FileData *fd)
1382 return fd->user_orientation;
1385 void file_data_set_user_orientation(FileData *fd, gint value)
1387 if (fd->user_orientation == value) return;
1389 fd->user_orientation = value;
1390 file_data_increment_version(fd);
1391 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1396 * file_data - operates on the given fd
1397 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1401 /* return list of sidecar file extensions in a string */
1402 gchar *file_data_sc_list_to_string(FileData *fd)
1405 GString *result = g_string_new("");
1407 work = fd->sidecar_files;
1410 FileData *sfd = work->data;
1412 result = g_string_append(result, "+ ");
1413 result = g_string_append(result, sfd->extension);
1415 if (work) result = g_string_append_c(result, ' ');
1418 return g_string_free(result, FALSE);
1424 * add FileDataChangeInfo (see typedefs.h) for the given operation
1425 * uses file_data_add_change_info
1427 * fails if the fd->change already exists - change operations can't run in parallel
1428 * fd->change_info works as a lock
1430 * dest can be NULL - in this case the current name is used for now, it will
1435 FileDataChangeInfo types:
1437 MOVE - path is changed, name may be changed too
1438 RENAME - path remains unchanged, name is changed
1439 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1440 sidecar names are changed too, extensions are not changed
1442 UPDATE - file size, date or grouping has been changed
1445 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1447 FileDataChangeInfo *fdci;
1449 if (fd->change) return FALSE;
1451 fdci = g_new0(FileDataChangeInfo, 1);
1456 fdci->source = g_strdup(src);
1458 fdci->source = g_strdup(fd->path);
1461 fdci->dest = g_strdup(dest);
1468 static void file_data_planned_change_remove(FileData *fd)
1470 if (file_data_planned_change_hash &&
1471 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1473 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1475 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1476 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1477 file_data_unref(fd);
1478 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1480 g_hash_table_destroy(file_data_planned_change_hash);
1481 file_data_planned_change_hash = NULL;
1482 DEBUG_1("planned change: empty");
1489 void file_data_free_ci(FileData *fd)
1491 FileDataChangeInfo *fdci = fd->change;
1495 file_data_planned_change_remove(fd);
1497 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1499 g_free(fdci->source);
1507 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1509 FileDataChangeInfo *fdci = fd->change;
1511 fdci->regroup_when_finished = enable;
1514 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1518 if (fd->parent) fd = fd->parent;
1520 if (fd->change) return FALSE;
1522 work = fd->sidecar_files;
1525 FileData *sfd = work->data;
1527 if (sfd->change) return FALSE;
1531 file_data_add_ci(fd, type, NULL, NULL);
1533 work = fd->sidecar_files;
1536 FileData *sfd = work->data;
1538 file_data_add_ci(sfd, type, NULL, NULL);
1545 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1549 if (fd->parent) fd = fd->parent;
1551 if (!fd->change || fd->change->type != type) return FALSE;
1553 work = fd->sidecar_files;
1556 FileData *sfd = work->data;
1558 if (!sfd->change || sfd->change->type != type) return FALSE;
1566 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1568 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1569 file_data_sc_update_ci_copy(fd, dest_path);
1573 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1575 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1576 file_data_sc_update_ci_move(fd, dest_path);
1580 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1582 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1583 file_data_sc_update_ci_rename(fd, dest_path);
1587 gboolean file_data_sc_add_ci_delete(FileData *fd)
1589 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1592 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1594 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1595 file_data_sc_update_ci_unspecified(fd, dest_path);
1599 gboolean file_data_add_ci_write_metadata(FileData *fd)
1601 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1604 void file_data_sc_free_ci(FileData *fd)
1608 if (fd->parent) fd = fd->parent;
1610 file_data_free_ci(fd);
1612 work = fd->sidecar_files;
1615 FileData *sfd = work->data;
1617 file_data_free_ci(sfd);
1622 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1625 gboolean ret = TRUE;
1630 FileData *fd = work->data;
1632 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1639 static void file_data_sc_revert_ci_list(GList *fd_list)
1646 FileData *fd = work->data;
1648 file_data_sc_free_ci(fd);
1653 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1660 FileData *fd = work->data;
1662 if (!func(fd, dest))
1664 file_data_sc_revert_ci_list(work->prev);
1673 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1675 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1678 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1680 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1683 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1685 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1688 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1690 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1693 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1696 gboolean ret = TRUE;
1701 FileData *fd = work->data;
1703 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1710 void file_data_free_ci_list(GList *fd_list)
1717 FileData *fd = work->data;
1719 file_data_free_ci(fd);
1724 void file_data_sc_free_ci_list(GList *fd_list)
1731 FileData *fd = work->data;
1733 file_data_sc_free_ci(fd);
1739 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1740 * fails if fd->change does not exist or the change type does not match
1743 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1745 FileDataChangeType type = fd->change->type;
1747 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1751 if (!file_data_planned_change_hash)
1752 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1754 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1756 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1757 g_hash_table_remove(file_data_planned_change_hash, old_path);
1758 file_data_unref(fd);
1761 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1766 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1767 g_hash_table_remove(file_data_planned_change_hash, new_path);
1768 file_data_unref(ofd);
1771 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1773 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1778 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1780 gchar *old_path = fd->change->dest;
1782 fd->change->dest = g_strdup(dest_path);
1783 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1787 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1789 const gchar *extension = extension_from_path(fd->change->source);
1790 gchar *base = remove_extension_from_path(dest_path);
1791 gchar *old_path = fd->change->dest;
1793 fd->change->dest = g_strconcat(base, extension, NULL);
1794 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1800 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1803 gchar *dest_path_full = NULL;
1805 if (fd->parent) fd = fd->parent;
1809 dest_path = fd->path;
1811 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1813 gchar *dir = remove_level_from_path(fd->path);
1815 dest_path_full = g_build_filename(dir, dest_path, NULL);
1817 dest_path = dest_path_full;
1819 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1821 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1822 dest_path = dest_path_full;
1825 file_data_update_ci_dest(fd, dest_path);
1827 work = fd->sidecar_files;
1830 FileData *sfd = work->data;
1832 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1836 g_free(dest_path_full);
1839 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1841 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1842 file_data_sc_update_ci(fd, dest_path);
1846 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1848 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1851 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1853 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1856 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1858 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1861 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1863 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1866 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1868 gboolean (*func)(FileData *, const gchar *))
1871 gboolean ret = TRUE;
1876 FileData *fd = work->data;
1878 if (!func(fd, dest)) ret = FALSE;
1885 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1887 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1890 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1892 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1895 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1897 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1902 * verify source and dest paths - dest image exists, etc.
1903 * it should detect all possible problems with the planned operation
1906 gint file_data_verify_ci(FileData *fd)
1908 gint ret = CHANGE_OK;
1913 DEBUG_1("Change checked: no change info: %s", fd->path);
1917 if (!isname(fd->path))
1919 /* this probably should not happen */
1920 ret |= CHANGE_NO_SRC;
1921 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1925 dir = remove_level_from_path(fd->path);
1927 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1928 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
1929 fd->change->type != FILEDATA_CHANGE_RENAME &&
1930 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1933 ret |= CHANGE_WARN_UNSAVED_META;
1934 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
1937 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1938 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1939 !access_file(fd->path, R_OK))
1941 ret |= CHANGE_NO_READ_PERM;
1942 DEBUG_1("Change checked: no read permission: %s", fd->path);
1944 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1945 !access_file(dir, W_OK))
1947 ret |= CHANGE_NO_WRITE_PERM_DIR;
1948 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1950 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1951 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1952 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1953 !access_file(fd->path, W_OK))
1955 ret |= CHANGE_WARN_NO_WRITE_PERM;
1956 DEBUG_1("Change checked: no write permission: %s", fd->path);
1958 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1959 - that means that there are no hard errors and warnings can be disabled
1960 - the destination is determined during the check
1962 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1964 /* determine destination file */
1965 gboolean have_dest = FALSE;
1966 gchar *dest_dir = NULL;
1968 if (options->metadata.save_in_image_file)
1970 if (file_data_can_write_directly(fd))
1972 /* we can write the file directly */
1973 if (access_file(fd->path, W_OK))
1979 if (options->metadata.warn_on_write_problems)
1981 ret |= CHANGE_WARN_NO_WRITE_PERM;
1982 DEBUG_1("Change checked: file is not writable: %s", fd->path);
1986 else if (file_data_can_write_sidecar(fd))
1988 /* we can write sidecar */
1989 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
1990 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
1992 file_data_update_ci_dest(fd, sidecar);
1997 if (options->metadata.warn_on_write_problems)
1999 ret |= CHANGE_WARN_NO_WRITE_PERM;
2000 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2009 /* write private metadata file under ~/.geeqie */
2011 /* If an existing metadata file exists, we will try writing to
2012 * it's location regardless of the user's preference.
2014 gchar *metadata_path = NULL;
2016 /* but ignore XMP if we are not able to write it */
2017 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2019 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2021 if (metadata_path && !access_file(metadata_path, W_OK))
2023 g_free(metadata_path);
2024 metadata_path = NULL;
2031 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2032 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2034 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2036 metadata_path = g_build_filename(dest_dir, filename, NULL);
2040 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2042 file_data_update_ci_dest(fd, metadata_path);
2047 ret |= CHANGE_NO_WRITE_PERM_DEST;
2048 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2050 g_free(metadata_path);
2055 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2060 same = (strcmp(fd->path, fd->change->dest) == 0);
2064 const gchar *dest_ext = extension_from_path(fd->change->dest);
2065 if (!dest_ext) dest_ext = "";
2067 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2069 ret |= CHANGE_WARN_CHANGED_EXT;
2070 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2075 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2077 ret |= CHANGE_WARN_SAME;
2078 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2082 dest_dir = remove_level_from_path(fd->change->dest);
2084 if (!isdir(dest_dir))
2086 ret |= CHANGE_NO_DEST_DIR;
2087 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2089 else if (!access_file(dest_dir, W_OK))
2091 ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
2092 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2096 if (isfile(fd->change->dest))
2098 if (!access_file(fd->change->dest, W_OK))
2100 ret |= CHANGE_NO_WRITE_PERM_DEST;
2101 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2105 ret |= CHANGE_WARN_DEST_EXISTS;
2106 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2109 else if (isdir(fd->change->dest))
2111 ret |= CHANGE_DEST_EXISTS;
2112 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2119 fd->change->error = ret;
2120 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2127 gint file_data_sc_verify_ci(FileData *fd)
2132 ret = file_data_verify_ci(fd);
2134 work = fd->sidecar_files;
2137 FileData *sfd = work->data;
2139 ret |= file_data_verify_ci(sfd);
2146 gchar *file_data_get_error_string(gint error)
2148 GString *result = g_string_new("");
2150 if (error & CHANGE_NO_SRC)
2152 if (result->len > 0) g_string_append(result, ", ");
2153 g_string_append(result, _("file or directory does not exist"));
2156 if (error & CHANGE_DEST_EXISTS)
2158 if (result->len > 0) g_string_append(result, ", ");
2159 g_string_append(result, _("destination already exists"));
2162 if (error & CHANGE_NO_WRITE_PERM_DEST)
2164 if (result->len > 0) g_string_append(result, ", ");
2165 g_string_append(result, _("destination can't be overwritten"));
2168 if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
2170 if (result->len > 0) g_string_append(result, ", ");
2171 g_string_append(result, _("destination directory is not writable"));
2174 if (error & CHANGE_NO_DEST_DIR)
2176 if (result->len > 0) g_string_append(result, ", ");
2177 g_string_append(result, _("destination directory does not exist"));
2180 if (error & CHANGE_NO_WRITE_PERM_DIR)
2182 if (result->len > 0) g_string_append(result, ", ");
2183 g_string_append(result, _("source directory is not writable"));
2186 if (error & CHANGE_NO_READ_PERM)
2188 if (result->len > 0) g_string_append(result, ", ");
2189 g_string_append(result, _("no read permission"));
2192 if (error & CHANGE_WARN_NO_WRITE_PERM)
2194 if (result->len > 0) g_string_append(result, ", ");
2195 g_string_append(result, _("file is readonly"));
2198 if (error & CHANGE_WARN_DEST_EXISTS)
2200 if (result->len > 0) g_string_append(result, ", ");
2201 g_string_append(result, _("destination already exists and will be overwritten"));
2204 if (error & CHANGE_WARN_SAME)
2206 if (result->len > 0) g_string_append(result, ", ");
2207 g_string_append(result, _("source and destination are the same"));
2210 if (error & CHANGE_WARN_CHANGED_EXT)
2212 if (result->len > 0) g_string_append(result, ", ");
2213 g_string_append(result, _("source and destination have different extension"));
2216 if (error & CHANGE_WARN_UNSAVED_META)
2218 if (result->len > 0) g_string_append(result, ", ");
2219 g_string_append(result, _("there are unsaved metadata changes for the file"));
2222 return g_string_free(result, FALSE);
2225 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2228 gint all_errors = 0;
2229 gint common_errors = ~0;
2234 if (!list) return 0;
2236 num = g_list_length(list);
2237 errors = g_new(int, num);
2248 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2249 all_errors |= error;
2250 common_errors &= error;
2257 if (desc && all_errors)
2260 GString *result = g_string_new("");
2264 gchar *str = file_data_get_error_string(common_errors);
2265 g_string_append(result, str);
2266 g_string_append(result, "\n");
2280 error = errors[i] & ~common_errors;
2284 gchar *str = file_data_get_error_string(error);
2285 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2290 *desc = g_string_free(result, FALSE);
2299 * perform the change described by FileFataChangeInfo
2300 * it is used for internal operations,
2301 * this function actually operates with files on the filesystem
2302 * it should implement safe delete
2305 static gboolean file_data_perform_move(FileData *fd)
2307 g_assert(!strcmp(fd->change->source, fd->path));
2308 return move_file(fd->change->source, fd->change->dest);
2311 static gboolean file_data_perform_copy(FileData *fd)
2313 g_assert(!strcmp(fd->change->source, fd->path));
2314 return copy_file(fd->change->source, fd->change->dest);
2317 static gboolean file_data_perform_delete(FileData *fd)
2319 if (isdir(fd->path) && !islink(fd->path))
2320 return rmdir_utf8(fd->path);
2322 if (options->file_ops.safe_delete_enable)
2323 return file_util_safe_unlink(fd->path);
2325 return unlink_file(fd->path);
2328 gboolean file_data_perform_ci(FileData *fd)
2330 FileDataChangeType type = fd->change->type;
2334 case FILEDATA_CHANGE_MOVE:
2335 return file_data_perform_move(fd);
2336 case FILEDATA_CHANGE_COPY:
2337 return file_data_perform_copy(fd);
2338 case FILEDATA_CHANGE_RENAME:
2339 return file_data_perform_move(fd); /* the same as move */
2340 case FILEDATA_CHANGE_DELETE:
2341 return file_data_perform_delete(fd);
2342 case FILEDATA_CHANGE_WRITE_METADATA:
2343 return metadata_write_perform(fd);
2344 case FILEDATA_CHANGE_UNSPECIFIED:
2345 /* nothing to do here */
2353 gboolean file_data_sc_perform_ci(FileData *fd)
2356 gboolean ret = TRUE;
2357 FileDataChangeType type = fd->change->type;
2359 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2361 work = fd->sidecar_files;
2364 FileData *sfd = work->data;
2366 if (!file_data_perform_ci(sfd)) ret = FALSE;
2370 if (!file_data_perform_ci(fd)) ret = FALSE;
2376 * updates FileData structure according to FileDataChangeInfo
2379 gboolean file_data_apply_ci(FileData *fd)
2381 FileDataChangeType type = fd->change->type;
2384 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2386 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2387 file_data_planned_change_remove(fd);
2389 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2391 /* this change overwrites another file which is already known to other modules
2392 renaming fd would create duplicate FileData structure
2393 the best thing we can do is nothing
2394 FIXME: maybe we could copy stuff like marks
2396 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2400 file_data_set_path(fd, fd->change->dest);
2403 file_data_increment_version(fd);
2404 file_data_send_notification(fd, NOTIFY_CHANGE);
2409 gboolean file_data_sc_apply_ci(FileData *fd)
2412 FileDataChangeType type = fd->change->type;
2414 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2416 work = fd->sidecar_files;
2419 FileData *sfd = work->data;
2421 file_data_apply_ci(sfd);
2425 file_data_apply_ci(fd);
2430 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2433 if (fd->parent) fd = fd->parent;
2434 if (!g_list_find(list, fd)) return FALSE;
2436 work = fd->sidecar_files;
2439 if (!g_list_find(list, work->data)) return FALSE;
2446 static gboolean file_data_list_dump(GList *list)
2448 GList *work, *work2;
2453 FileData *fd = work->data;
2454 printf("%s\n", fd->name);
2455 work2 = fd->sidecar_files;
2458 FileData *fd = work2->data;
2459 printf(" %s\n", fd->name);
2460 work2 = work2->next;
2468 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2473 /* change partial groups to independent files */
2478 FileData *fd = work->data;
2481 if (!file_data_list_contains_whole_group(list, fd))
2483 file_data_disable_grouping(fd, TRUE);
2486 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2492 /* remove sidecars from the list,
2493 they can be still acessed via main_fd->sidecar_files */
2497 FileData *fd = work->data;
2501 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2503 out = g_list_prepend(out, file_data_ref(fd));
2507 filelist_free(list);
2508 out = g_list_reverse(out);
2518 * notify other modules about the change described by FileDataChangeInfo
2521 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2522 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2523 implementation in view_file_list.c */
2526 typedef struct _NotifyIdleData NotifyIdleData;
2528 struct _NotifyIdleData {
2534 typedef struct _NotifyData NotifyData;
2536 struct _NotifyData {
2537 FileDataNotifyFunc func;
2539 NotifyPriority priority;
2542 static GList *notify_func_list = NULL;
2544 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2546 NotifyData *nda = (NotifyData *)a;
2547 NotifyData *ndb = (NotifyData *)b;
2549 if (nda->priority < ndb->priority) return -1;
2550 if (nda->priority > ndb->priority) return 1;
2554 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2557 GList *work = notify_func_list;
2561 NotifyData *nd = (NotifyData *)work->data;
2563 if (nd->func == func && nd->data == data)
2565 g_warning("Notify func already registered");
2571 nd = g_new(NotifyData, 1);
2574 nd->priority = priority;
2576 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2577 DEBUG_2("Notify func registered: %p", nd);
2582 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2584 GList *work = notify_func_list;
2588 NotifyData *nd = (NotifyData *)work->data;
2590 if (nd->func == func && nd->data == data)
2592 notify_func_list = g_list_delete_link(notify_func_list, work);
2594 DEBUG_2("Notify func unregistered: %p", nd);
2600 g_warning("Notify func not found");
2605 gboolean file_data_send_notification_idle_cb(gpointer data)
2607 NotifyIdleData *nid = (NotifyIdleData *)data;
2608 GList *work = notify_func_list;
2612 NotifyData *nd = (NotifyData *)work->data;
2614 nd->func(nid->fd, nid->type, nd->data);
2617 file_data_unref(nid->fd);
2622 void file_data_send_notification(FileData *fd, NotifyType type)
2624 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
2625 nid->fd = file_data_ref(fd);
2627 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
2630 static GHashTable *file_data_monitor_pool = NULL;
2631 static guint realtime_monitor_id = 0; /* event source id */
2633 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2637 file_data_check_changed_files(fd);
2639 DEBUG_1("monitor %s", fd->path);
2642 static gboolean realtime_monitor_cb(gpointer data)
2644 if (!options->update_on_time_change) return TRUE;
2645 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2649 gboolean file_data_register_real_time_monitor(FileData *fd)
2655 if (!file_data_monitor_pool)
2656 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2658 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2660 DEBUG_1("Register realtime %d %s", count, fd->path);
2663 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2665 if (!realtime_monitor_id)
2667 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2673 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2677 g_assert(file_data_monitor_pool);
2679 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2681 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2683 g_assert(count > 0);
2688 g_hash_table_remove(file_data_monitor_pool, fd);
2690 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2692 file_data_unref(fd);
2694 if (g_hash_table_size(file_data_monitor_pool) == 0)
2696 g_source_remove(realtime_monitor_id);
2697 realtime_monitor_id = 0;
2703 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */