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 *path);
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 = 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 *path)
775 const gchar *extension = extension_from_path(path);
779 if (extension == NULL)
782 work = sidecar_ext_get_list();
785 gchar *ext = work->data;
788 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
796 *-----------------------------------------------------------------------------
798 *-----------------------------------------------------------------------------
801 static SortType filelist_sort_method = SORT_NONE;
802 static gboolean filelist_sort_ascend = TRUE;
805 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
808 if (!filelist_sort_ascend)
815 switch (filelist_sort_method)
820 if (fa->size < fb->size) return -1;
821 if (fa->size > fb->size) return 1;
822 /* fall back to name */
825 if (fa->date < fb->date) return -1;
826 if (fa->date > fb->date) return 1;
827 /* fall back to name */
829 #ifdef HAVE_STRVERSCMP
831 ret = strverscmp(fa->name, fb->name);
832 if (ret != 0) return ret;
839 if (options->file_sort.case_sensitive)
840 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
842 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
844 if (ret != 0) return ret;
846 /* do not return 0 unless the files are really the same
847 file_data_pool ensures that original_path is unique
849 return strcmp(fa->original_path, fb->original_path);
852 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
854 filelist_sort_method = method;
855 filelist_sort_ascend = ascend;
856 return filelist_sort_compare_filedata(fa, fb);
859 static gint filelist_sort_file_cb(gpointer a, gpointer b)
861 return filelist_sort_compare_filedata(a, b);
864 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
866 filelist_sort_method = method;
867 filelist_sort_ascend = ascend;
868 return g_list_sort(list, cb);
871 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
873 filelist_sort_method = method;
874 filelist_sort_ascend = ascend;
875 return g_list_insert_sorted(list, data, cb);
878 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
880 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
883 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
885 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
889 static GList *filelist_filter_out_sidecars(GList *flist)
892 GList *flist_filtered = NULL;
896 FileData *fd = work->data;
899 if (fd->parent) /* remove fd's that are children */
902 flist_filtered = g_list_prepend(flist_filtered, fd);
906 return flist_filtered;
909 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
911 GList *basename_list = (GList *)value;
912 file_data_check_sidecars(basename_list);
916 static gboolean is_hidden_file(const gchar *name)
918 if (name[0] != '.') return FALSE;
919 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
923 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
930 gint (*stat_func)(const gchar *path, struct stat *buf);
931 GHashTable *basename_hash = NULL;
933 g_assert(files || dirs);
935 if (files) *files = NULL;
936 if (dirs) *dirs = NULL;
938 pathl = path_from_utf8(dir_path);
939 if (!pathl) return FALSE;
948 if (files) basename_hash = file_data_basename_hash_new();
955 while ((dir = readdir(dp)) != NULL)
957 struct stat ent_sbuf;
958 const gchar *name = dir->d_name;
961 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
964 filepath = g_build_filename(pathl, name, NULL);
965 if (stat_func(filepath, &ent_sbuf) >= 0)
967 if (S_ISDIR(ent_sbuf.st_mode))
969 /* we ignore the .thumbnails dir for cleanliness */
971 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
972 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
973 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
974 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
976 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
981 if (files && filter_name_exists(name))
983 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
984 flist = g_list_prepend(flist, fd);
985 if (fd->sidecar_priority && !fd->disable_grouping)
987 file_data_basename_hash_insert(basename_hash, fd);
994 if (errno == EOVERFLOW)
996 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1006 if (dirs) *dirs = dlist;
1009 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1011 *files = filelist_filter_out_sidecars(flist);
1013 if (basename_hash) file_data_basename_hash_free(basename_hash);
1018 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1020 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1023 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1025 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1028 FileData *file_data_new_simple(const gchar *path_utf8)
1035 if (!stat_utf8(path_utf8, &st))
1041 if (S_ISDIR(st.st_mode))
1042 return file_data_new(path_utf8, &st, TRUE);
1044 dir = remove_level_from_path(path_utf8);
1046 filelist_read_real(dir, &files, NULL, TRUE);
1048 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1052 filelist_free(files);
1057 FileData *file_data_new_no_grouping(const gchar *path_utf8)
1061 if (!stat_utf8(path_utf8, &st))
1067 return file_data_new(path_utf8, &st, TRUE);
1070 FileData *file_data_new_dir(const gchar *path_utf8)
1074 if (!stat_utf8(path_utf8, &st))
1080 g_assert(S_ISDIR(st.st_mode));
1081 return file_data_new(path_utf8, &st, TRUE);
1084 void filelist_free(GList *list)
1091 file_data_unref((FileData *)work->data);
1099 GList *filelist_copy(GList *list)
1101 GList *new_list = NULL;
1112 new_list = g_list_prepend(new_list, file_data_ref(fd));
1115 return g_list_reverse(new_list);
1118 GList *filelist_from_path_list(GList *list)
1120 GList *new_list = NULL;
1131 new_list = g_list_prepend(new_list, file_data_new_simple(path));
1134 return g_list_reverse(new_list);
1137 GList *filelist_to_path_list(GList *list)
1139 GList *new_list = NULL;
1150 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1153 return g_list_reverse(new_list);
1156 GList *filelist_filter(GList *list, gboolean is_dir_list)
1160 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1165 FileData *fd = (FileData *)(work->data);
1166 const gchar *name = fd->name;
1168 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1169 (!is_dir_list && !filter_name_exists(name)) ||
1170 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1171 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1175 list = g_list_remove_link(list, link);
1176 file_data_unref(fd);
1187 *-----------------------------------------------------------------------------
1188 * filelist recursive
1189 *-----------------------------------------------------------------------------
1192 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1194 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1197 GList *filelist_sort_path(GList *list)
1199 return g_list_sort(list, filelist_sort_path_cb);
1202 static void filelist_recursive_append(GList **list, GList *dirs)
1209 FileData *fd = (FileData *)(work->data);
1213 if (filelist_read(fd, &f, &d))
1215 f = filelist_filter(f, FALSE);
1216 f = filelist_sort_path(f);
1217 *list = g_list_concat(*list, f);
1219 d = filelist_filter(d, TRUE);
1220 d = filelist_sort_path(d);
1221 filelist_recursive_append(list, d);
1229 GList *filelist_recursive(FileData *dir_fd)
1234 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1235 list = filelist_filter(list, FALSE);
1236 list = filelist_sort_path(list);
1238 d = filelist_filter(d, TRUE);
1239 d = filelist_sort_path(d);
1240 filelist_recursive_append(&list, d);
1248 * marks and orientation
1251 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1252 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1253 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1254 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1256 gboolean file_data_get_mark(FileData *fd, gint n)
1258 gboolean valid = (fd->valid_marks & (1 << n));
1260 if (file_data_get_mark_func[n] && !valid)
1262 guint old = fd->marks;
1263 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1265 if (!value != !(fd->marks & (1 << n)))
1267 fd->marks = fd->marks ^ (1 << n);
1270 fd->valid_marks |= (1 << n);
1271 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1273 file_data_unref(fd);
1275 else if (!old && fd->marks)
1281 return !!(fd->marks & (1 << n));
1284 guint file_data_get_marks(FileData *fd)
1287 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1291 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1294 if (!value == !file_data_get_mark(fd, n)) return;
1296 if (file_data_set_mark_func[n])
1298 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1303 fd->marks = fd->marks ^ (1 << n);
1305 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1307 file_data_unref(fd);
1309 else if (!old && fd->marks)
1314 file_data_increment_version(fd);
1315 file_data_send_notification(fd, NOTIFY_MARKS);
1318 gboolean file_data_filter_marks(FileData *fd, guint filter)
1321 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1322 return ((fd->marks & filter) == filter);
1325 GList *file_data_filter_marks_list(GList *list, guint filter)
1332 FileData *fd = work->data;
1336 if (!file_data_filter_marks(fd, filter))
1338 list = g_list_remove_link(list, link);
1339 file_data_unref(fd);
1347 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1349 FileData *fd = value;
1350 file_data_increment_version(fd);
1351 file_data_send_notification(fd, NOTIFY_MARKS);
1354 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1356 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1358 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1360 file_data_get_mark_func[n] = get_mark_func;
1361 file_data_set_mark_func[n] = set_mark_func;
1362 file_data_mark_func_data[n] = data;
1363 file_data_destroy_mark_func[n] = notify;
1367 /* this effectively changes all known files */
1368 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1374 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1376 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1377 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1378 if (data) *data = file_data_mark_func_data[n];
1381 gint file_data_get_user_orientation(FileData *fd)
1383 return fd->user_orientation;
1386 void file_data_set_user_orientation(FileData *fd, gint value)
1388 if (fd->user_orientation == value) return;
1390 fd->user_orientation = value;
1391 file_data_increment_version(fd);
1392 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1397 * file_data - operates on the given fd
1398 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1402 /* return list of sidecar file extensions in a string */
1403 gchar *file_data_sc_list_to_string(FileData *fd)
1406 GString *result = g_string_new("");
1408 work = fd->sidecar_files;
1411 FileData *sfd = work->data;
1413 result = g_string_append(result, "+ ");
1414 result = g_string_append(result, sfd->extension);
1416 if (work) result = g_string_append_c(result, ' ');
1419 return g_string_free(result, FALSE);
1425 * add FileDataChangeInfo (see typedefs.h) for the given operation
1426 * uses file_data_add_change_info
1428 * fails if the fd->change already exists - change operations can't run in parallel
1429 * fd->change_info works as a lock
1431 * dest can be NULL - in this case the current name is used for now, it will
1436 FileDataChangeInfo types:
1438 MOVE - path is changed, name may be changed too
1439 RENAME - path remains unchanged, name is changed
1440 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1441 sidecar names are changed too, extensions are not changed
1443 UPDATE - file size, date or grouping has been changed
1446 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1448 FileDataChangeInfo *fdci;
1450 if (fd->change) return FALSE;
1452 fdci = g_new0(FileDataChangeInfo, 1);
1457 fdci->source = g_strdup(src);
1459 fdci->source = g_strdup(fd->path);
1462 fdci->dest = g_strdup(dest);
1469 static void file_data_planned_change_remove(FileData *fd)
1471 if (file_data_planned_change_hash &&
1472 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1474 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1476 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1477 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1478 file_data_unref(fd);
1479 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1481 g_hash_table_destroy(file_data_planned_change_hash);
1482 file_data_planned_change_hash = NULL;
1483 DEBUG_1("planned change: empty");
1490 void file_data_free_ci(FileData *fd)
1492 FileDataChangeInfo *fdci = fd->change;
1496 file_data_planned_change_remove(fd);
1498 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1500 g_free(fdci->source);
1508 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1510 FileDataChangeInfo *fdci = fd->change;
1512 fdci->regroup_when_finished = enable;
1515 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1519 if (fd->parent) fd = fd->parent;
1521 if (fd->change) return FALSE;
1523 work = fd->sidecar_files;
1526 FileData *sfd = work->data;
1528 if (sfd->change) return FALSE;
1532 file_data_add_ci(fd, type, NULL, NULL);
1534 work = fd->sidecar_files;
1537 FileData *sfd = work->data;
1539 file_data_add_ci(sfd, type, NULL, NULL);
1546 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1550 if (fd->parent) fd = fd->parent;
1552 if (!fd->change || fd->change->type != type) return FALSE;
1554 work = fd->sidecar_files;
1557 FileData *sfd = work->data;
1559 if (!sfd->change || sfd->change->type != type) return FALSE;
1567 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1569 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1570 file_data_sc_update_ci_copy(fd, dest_path);
1574 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1576 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1577 file_data_sc_update_ci_move(fd, dest_path);
1581 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1583 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1584 file_data_sc_update_ci_rename(fd, dest_path);
1588 gboolean file_data_sc_add_ci_delete(FileData *fd)
1590 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1593 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1595 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1596 file_data_sc_update_ci_unspecified(fd, dest_path);
1600 gboolean file_data_add_ci_write_metadata(FileData *fd)
1602 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1605 void file_data_sc_free_ci(FileData *fd)
1609 if (fd->parent) fd = fd->parent;
1611 file_data_free_ci(fd);
1613 work = fd->sidecar_files;
1616 FileData *sfd = work->data;
1618 file_data_free_ci(sfd);
1623 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1626 gboolean ret = TRUE;
1631 FileData *fd = work->data;
1633 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1640 static void file_data_sc_revert_ci_list(GList *fd_list)
1647 FileData *fd = work->data;
1649 file_data_sc_free_ci(fd);
1654 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1661 FileData *fd = work->data;
1663 if (!func(fd, dest))
1665 file_data_sc_revert_ci_list(work->prev);
1674 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1676 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1679 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1681 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1684 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1686 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1689 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1691 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1694 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1697 gboolean ret = TRUE;
1702 FileData *fd = work->data;
1704 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1711 void file_data_free_ci_list(GList *fd_list)
1718 FileData *fd = work->data;
1720 file_data_free_ci(fd);
1725 void file_data_sc_free_ci_list(GList *fd_list)
1732 FileData *fd = work->data;
1734 file_data_sc_free_ci(fd);
1740 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1741 * fails if fd->change does not exist or the change type does not match
1744 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1746 FileDataChangeType type = fd->change->type;
1748 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1752 if (!file_data_planned_change_hash)
1753 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1755 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1757 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1758 g_hash_table_remove(file_data_planned_change_hash, old_path);
1759 file_data_unref(fd);
1762 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1767 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1768 g_hash_table_remove(file_data_planned_change_hash, new_path);
1769 file_data_unref(ofd);
1772 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1774 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1779 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1781 gchar *old_path = fd->change->dest;
1783 fd->change->dest = g_strdup(dest_path);
1784 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1788 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1790 const gchar *extension = extension_from_path(fd->change->source);
1791 gchar *base = remove_extension_from_path(dest_path);
1792 gchar *old_path = fd->change->dest;
1794 fd->change->dest = g_strconcat(base, extension, NULL);
1795 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1801 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1804 gchar *dest_path_full = NULL;
1806 if (fd->parent) fd = fd->parent;
1810 dest_path = fd->path;
1812 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1814 gchar *dir = remove_level_from_path(fd->path);
1816 dest_path_full = g_build_filename(dir, dest_path, NULL);
1818 dest_path = dest_path_full;
1820 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1822 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1823 dest_path = dest_path_full;
1826 file_data_update_ci_dest(fd, dest_path);
1828 work = fd->sidecar_files;
1831 FileData *sfd = work->data;
1833 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1837 g_free(dest_path_full);
1840 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1842 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1843 file_data_sc_update_ci(fd, dest_path);
1847 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1849 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1852 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1854 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1857 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1859 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1862 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1864 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1867 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1869 gboolean (*func)(FileData *, const gchar *))
1872 gboolean ret = TRUE;
1877 FileData *fd = work->data;
1879 if (!func(fd, dest)) ret = FALSE;
1886 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1888 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1891 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1893 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1896 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1898 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1903 * verify source and dest paths - dest image exists, etc.
1904 * it should detect all possible problems with the planned operation
1907 gint file_data_verify_ci(FileData *fd)
1909 gint ret = CHANGE_OK;
1914 DEBUG_1("Change checked: no change info: %s", fd->path);
1918 if (!isname(fd->path))
1920 /* this probably should not happen */
1921 ret |= CHANGE_NO_SRC;
1922 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1926 dir = remove_level_from_path(fd->path);
1928 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1929 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
1930 fd->change->type != FILEDATA_CHANGE_RENAME &&
1931 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1934 ret |= CHANGE_WARN_UNSAVED_META;
1935 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
1938 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1939 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1940 !access_file(fd->path, R_OK))
1942 ret |= CHANGE_NO_READ_PERM;
1943 DEBUG_1("Change checked: no read permission: %s", fd->path);
1945 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1946 !access_file(dir, W_OK))
1948 ret |= CHANGE_NO_WRITE_PERM_DIR;
1949 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1951 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1952 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1953 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1954 !access_file(fd->path, W_OK))
1956 ret |= CHANGE_WARN_NO_WRITE_PERM;
1957 DEBUG_1("Change checked: no write permission: %s", fd->path);
1959 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1960 - that means that there are no hard errors and warnings can be disabled
1961 - the destination is determined during the check
1963 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1965 /* determine destination file */
1966 gboolean have_dest = FALSE;
1967 gchar *dest_dir = NULL;
1969 if (options->metadata.save_in_image_file)
1971 if (file_data_can_write_directly(fd))
1973 /* we can write the file directly */
1974 if (access_file(fd->path, W_OK))
1980 if (options->metadata.warn_on_write_problems)
1982 ret |= CHANGE_WARN_NO_WRITE_PERM;
1983 DEBUG_1("Change checked: file is not writable: %s", fd->path);
1987 else if (file_data_can_write_sidecar(fd))
1989 /* we can write sidecar */
1990 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
1991 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
1993 file_data_update_ci_dest(fd, sidecar);
1998 if (options->metadata.warn_on_write_problems)
2000 ret |= CHANGE_WARN_NO_WRITE_PERM;
2001 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2010 /* write private metadata file under ~/.geeqie */
2012 /* If an existing metadata file exists, we will try writing to
2013 * it's location regardless of the user's preference.
2015 gchar *metadata_path = NULL;
2017 /* but ignore XMP if we are not able to write it */
2018 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2020 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2022 if (metadata_path && !access_file(metadata_path, W_OK))
2024 g_free(metadata_path);
2025 metadata_path = NULL;
2032 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2033 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2035 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2037 metadata_path = g_build_filename(dest_dir, filename, NULL);
2041 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2043 file_data_update_ci_dest(fd, metadata_path);
2048 ret |= CHANGE_NO_WRITE_PERM_DEST;
2049 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2051 g_free(metadata_path);
2056 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2061 same = (strcmp(fd->path, fd->change->dest) == 0);
2065 const gchar *dest_ext = extension_from_path(fd->change->dest);
2066 if (!dest_ext) dest_ext = "";
2068 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2070 ret |= CHANGE_WARN_CHANGED_EXT;
2071 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2076 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2078 ret |= CHANGE_WARN_SAME;
2079 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2083 dest_dir = remove_level_from_path(fd->change->dest);
2085 if (!isdir(dest_dir))
2087 ret |= CHANGE_NO_DEST_DIR;
2088 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2090 else if (!access_file(dest_dir, W_OK))
2092 ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
2093 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2097 if (isfile(fd->change->dest))
2099 if (!access_file(fd->change->dest, W_OK))
2101 ret |= CHANGE_NO_WRITE_PERM_DEST;
2102 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2106 ret |= CHANGE_WARN_DEST_EXISTS;
2107 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2110 else if (isdir(fd->change->dest))
2112 ret |= CHANGE_DEST_EXISTS;
2113 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2120 fd->change->error = ret;
2121 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2128 gint file_data_sc_verify_ci(FileData *fd)
2133 ret = file_data_verify_ci(fd);
2135 work = fd->sidecar_files;
2138 FileData *sfd = work->data;
2140 ret |= file_data_verify_ci(sfd);
2147 gchar *file_data_get_error_string(gint error)
2149 GString *result = g_string_new("");
2151 if (error & CHANGE_NO_SRC)
2153 if (result->len > 0) g_string_append(result, ", ");
2154 g_string_append(result, _("file or directory does not exist"));
2157 if (error & CHANGE_DEST_EXISTS)
2159 if (result->len > 0) g_string_append(result, ", ");
2160 g_string_append(result, _("destination already exists"));
2163 if (error & CHANGE_NO_WRITE_PERM_DEST)
2165 if (result->len > 0) g_string_append(result, ", ");
2166 g_string_append(result, _("destination can't be overwritten"));
2169 if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
2171 if (result->len > 0) g_string_append(result, ", ");
2172 g_string_append(result, _("destination directory is not writable"));
2175 if (error & CHANGE_NO_DEST_DIR)
2177 if (result->len > 0) g_string_append(result, ", ");
2178 g_string_append(result, _("destination directory does not exist"));
2181 if (error & CHANGE_NO_WRITE_PERM_DIR)
2183 if (result->len > 0) g_string_append(result, ", ");
2184 g_string_append(result, _("source directory is not writable"));
2187 if (error & CHANGE_NO_READ_PERM)
2189 if (result->len > 0) g_string_append(result, ", ");
2190 g_string_append(result, _("no read permission"));
2193 if (error & CHANGE_WARN_NO_WRITE_PERM)
2195 if (result->len > 0) g_string_append(result, ", ");
2196 g_string_append(result, _("file is readonly"));
2199 if (error & CHANGE_WARN_DEST_EXISTS)
2201 if (result->len > 0) g_string_append(result, ", ");
2202 g_string_append(result, _("destination already exists and will be overwritten"));
2205 if (error & CHANGE_WARN_SAME)
2207 if (result->len > 0) g_string_append(result, ", ");
2208 g_string_append(result, _("source and destination are the same"));
2211 if (error & CHANGE_WARN_CHANGED_EXT)
2213 if (result->len > 0) g_string_append(result, ", ");
2214 g_string_append(result, _("source and destination have different extension"));
2217 if (error & CHANGE_WARN_UNSAVED_META)
2219 if (result->len > 0) g_string_append(result, ", ");
2220 g_string_append(result, _("there are unsaved metadata changes for the file"));
2223 return g_string_free(result, FALSE);
2226 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2229 gint all_errors = 0;
2230 gint common_errors = ~0;
2235 if (!list) return 0;
2237 num = g_list_length(list);
2238 errors = g_new(int, num);
2249 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2250 all_errors |= error;
2251 common_errors &= error;
2258 if (desc && all_errors)
2261 GString *result = g_string_new("");
2265 gchar *str = file_data_get_error_string(common_errors);
2266 g_string_append(result, str);
2267 g_string_append(result, "\n");
2281 error = errors[i] & ~common_errors;
2285 gchar *str = file_data_get_error_string(error);
2286 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2291 *desc = g_string_free(result, FALSE);
2300 * perform the change described by FileFataChangeInfo
2301 * it is used for internal operations,
2302 * this function actually operates with files on the filesystem
2303 * it should implement safe delete
2306 static gboolean file_data_perform_move(FileData *fd)
2308 g_assert(!strcmp(fd->change->source, fd->path));
2309 return move_file(fd->change->source, fd->change->dest);
2312 static gboolean file_data_perform_copy(FileData *fd)
2314 g_assert(!strcmp(fd->change->source, fd->path));
2315 return copy_file(fd->change->source, fd->change->dest);
2318 static gboolean file_data_perform_delete(FileData *fd)
2320 if (isdir(fd->path) && !islink(fd->path))
2321 return rmdir_utf8(fd->path);
2323 if (options->file_ops.safe_delete_enable)
2324 return file_util_safe_unlink(fd->path);
2326 return unlink_file(fd->path);
2329 gboolean file_data_perform_ci(FileData *fd)
2331 FileDataChangeType type = fd->change->type;
2335 case FILEDATA_CHANGE_MOVE:
2336 return file_data_perform_move(fd);
2337 case FILEDATA_CHANGE_COPY:
2338 return file_data_perform_copy(fd);
2339 case FILEDATA_CHANGE_RENAME:
2340 return file_data_perform_move(fd); /* the same as move */
2341 case FILEDATA_CHANGE_DELETE:
2342 return file_data_perform_delete(fd);
2343 case FILEDATA_CHANGE_WRITE_METADATA:
2344 return metadata_write_perform(fd);
2345 case FILEDATA_CHANGE_UNSPECIFIED:
2346 /* nothing to do here */
2354 gboolean file_data_sc_perform_ci(FileData *fd)
2357 gboolean ret = TRUE;
2358 FileDataChangeType type = fd->change->type;
2360 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2362 work = fd->sidecar_files;
2365 FileData *sfd = work->data;
2367 if (!file_data_perform_ci(sfd)) ret = FALSE;
2371 if (!file_data_perform_ci(fd)) ret = FALSE;
2377 * updates FileData structure according to FileDataChangeInfo
2380 gboolean file_data_apply_ci(FileData *fd)
2382 FileDataChangeType type = fd->change->type;
2385 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2387 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2388 file_data_planned_change_remove(fd);
2390 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2392 /* this change overwrites another file which is already known to other modules
2393 renaming fd would create duplicate FileData structure
2394 the best thing we can do is nothing
2395 FIXME: maybe we could copy stuff like marks
2397 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2401 file_data_set_path(fd, fd->change->dest);
2404 file_data_increment_version(fd);
2405 file_data_send_notification(fd, NOTIFY_CHANGE);
2410 gboolean file_data_sc_apply_ci(FileData *fd)
2413 FileDataChangeType type = fd->change->type;
2415 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2417 work = fd->sidecar_files;
2420 FileData *sfd = work->data;
2422 file_data_apply_ci(sfd);
2426 file_data_apply_ci(fd);
2431 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2434 if (fd->parent) fd = fd->parent;
2435 if (!g_list_find(list, fd)) return FALSE;
2437 work = fd->sidecar_files;
2440 if (!g_list_find(list, work->data)) return FALSE;
2447 static gboolean file_data_list_dump(GList *list)
2449 GList *work, *work2;
2454 FileData *fd = work->data;
2455 printf("%s\n", fd->name);
2456 work2 = fd->sidecar_files;
2459 FileData *fd = work2->data;
2460 printf(" %s\n", fd->name);
2461 work2 = work2->next;
2469 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2474 /* change partial groups to independent files */
2479 FileData *fd = work->data;
2482 if (!file_data_list_contains_whole_group(list, fd))
2484 file_data_disable_grouping(fd, TRUE);
2487 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2493 /* remove sidecars from the list,
2494 they can be still acessed via main_fd->sidecar_files */
2498 FileData *fd = work->data;
2502 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2504 out = g_list_prepend(out, file_data_ref(fd));
2508 filelist_free(list);
2509 out = g_list_reverse(out);
2519 * notify other modules about the change described by FileDataChangeInfo
2522 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2523 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2524 implementation in view_file_list.c */
2529 typedef struct _NotifyData NotifyData;
2531 struct _NotifyData {
2532 FileDataNotifyFunc func;
2534 NotifyPriority priority;
2537 static GList *notify_func_list = NULL;
2539 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2541 NotifyData *nda = (NotifyData *)a;
2542 NotifyData *ndb = (NotifyData *)b;
2544 if (nda->priority < ndb->priority) return -1;
2545 if (nda->priority > ndb->priority) return 1;
2549 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2552 GList *work = notify_func_list;
2556 NotifyData *nd = (NotifyData *)work->data;
2558 if (nd->func == func && nd->data == data)
2560 g_warning("Notify func already registered");
2566 nd = g_new(NotifyData, 1);
2569 nd->priority = priority;
2571 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2572 DEBUG_2("Notify func registered: %p", nd);
2577 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2579 GList *work = notify_func_list;
2583 NotifyData *nd = (NotifyData *)work->data;
2585 if (nd->func == func && nd->data == data)
2587 notify_func_list = g_list_delete_link(notify_func_list, work);
2589 DEBUG_2("Notify func unregistered: %p", nd);
2595 g_warning("Notify func not found");
2600 void file_data_send_notification(FileData *fd, NotifyType type)
2602 GList *work = notify_func_list;
2606 NotifyData *nd = (NotifyData *)work->data;
2608 nd->func(fd, type, nd->data);
2613 static GHashTable *file_data_monitor_pool = NULL;
2614 static guint realtime_monitor_id = 0; /* event source id */
2616 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2620 file_data_check_changed_files(fd);
2622 DEBUG_1("monitor %s", fd->path);
2625 static gboolean realtime_monitor_cb(gpointer data)
2627 if (!options->update_on_time_change) return TRUE;
2628 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2632 gboolean file_data_register_real_time_monitor(FileData *fd)
2638 if (!file_data_monitor_pool)
2639 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2641 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2643 DEBUG_1("Register realtime %d %s", count, fd->path);
2646 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2648 if (!realtime_monitor_id)
2650 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2656 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2660 g_assert(file_data_monitor_pool);
2662 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2664 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2666 g_assert(count > 0);
2671 g_hash_table_remove(file_data_monitor_pool, fd);
2673 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2675 file_data_unref(fd);
2677 if (g_hash_table_size(file_data_monitor_pool) == 0)
2679 g_source_remove(realtime_monitor_id);
2680 realtime_monitor_id = 0;
2686 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */