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 check_sidecars, GHashTable *basename_hash);
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 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source);
141 static void file_data_check_sidecars(FileData *fd, GHashTable *basename_hash);
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 return strcmp(fdb->extension, fda->extension);
164 static GHashTable *file_data_basename_hash_new(void)
166 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
169 static void file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
172 const gchar *ext = extension_from_path(fd->path);
173 gchar *basename = ext ? g_strndup(fd->path, ext - fd->path) : g_strdup(fd->path);
175 list = g_hash_table_lookup(basename_hash, basename);
177 if (!g_list_find(list, fd))
179 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
180 g_hash_table_insert(basename_hash, basename, list);
189 static void file_data_basename_hash_remove(GHashTable *basename_hash, FileData *fd)
192 const gchar *ext = extension_from_path(fd->path);
193 gchar *basename = ext ? g_strndup(fd->path, ext - fd->path) : g_strdup(fd->path);
195 list = g_hash_table_lookup(basename_hash, basename);
197 if (!g_list_find(list, fd)) return;
199 list = g_list_remove(list, fd);
204 g_hash_table_insert(basename_hash, basename, list);
208 g_hash_table_remove(basename_hash, basename);
214 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
216 filelist_free((GList *)value);
219 static void file_data_basename_hash_free(GHashTable *basename_hash)
221 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
222 g_hash_table_destroy(basename_hash);
225 static void file_data_set_collate_keys(FileData *fd)
227 gchar *caseless_name;
229 caseless_name = g_utf8_casefold(fd->name, -1);
231 g_free(fd->collate_key_name);
232 g_free(fd->collate_key_name_nocase);
234 #if 0 && GLIB_CHECK_VERSION(2, 8, 0)
235 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
236 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
238 fd->collate_key_name = g_utf8_collate_key(fd->name, -1);
239 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
241 g_free(caseless_name);
244 static void file_data_set_path(FileData *fd, const gchar *path)
246 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
247 g_assert(file_data_pool);
251 if (fd->original_path)
253 g_hash_table_remove(file_data_pool, fd->original_path);
254 g_free(fd->original_path);
257 g_assert(!g_hash_table_lookup(file_data_pool, path));
259 fd->original_path = g_strdup(path);
260 g_hash_table_insert(file_data_pool, fd->original_path, fd);
262 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
264 fd->path = g_strdup(path);
266 fd->extension = fd->name + 1;
267 file_data_set_collate_keys(fd);
271 fd->path = g_strdup(path);
272 fd->name = filename_from_path(fd->path);
274 if (strcmp(fd->name, "..") == 0)
276 gchar *dir = remove_level_from_path(path);
278 fd->path = remove_level_from_path(dir);
281 fd->extension = fd->name + 2;
282 file_data_set_collate_keys(fd);
285 else if (strcmp(fd->name, ".") == 0)
288 fd->path = remove_level_from_path(path);
290 fd->extension = fd->name + 1;
291 file_data_set_collate_keys(fd);
295 fd->extension = extension_from_path(fd->path);
296 if (fd->extension == NULL)
298 fd->extension = fd->name + strlen(fd->name);
301 file_data_set_collate_keys(fd);
304 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
306 gboolean ret = FALSE;
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);
322 work = fd->sidecar_files;
325 FileData *sfd = work->data;
329 if (!stat_utf8(sfd->path, &st))
333 file_data_disconnect_sidecar_file(fd, sfd);
338 ret |= file_data_check_changed_files_recursive(sfd, &st);
344 gboolean file_data_check_changed_files(FileData *fd)
346 gboolean ret = FALSE;
349 if (fd->parent) fd = fd->parent;
351 if (!stat_utf8(fd->path, &st))
355 FileData *sfd = NULL;
357 /* parent is missing, we have to rebuild whole group */
362 /* file_data_disconnect_sidecar_file might delete the file,
363 we have to keep the reference to prevent this */
364 sidecars = filelist_copy(fd->sidecar_files);
371 file_data_disconnect_sidecar_file(fd, sfd);
373 if (sfd) file_data_check_sidecars(sfd, NULL); /* this will group the sidecars back together */
374 /* now we can release the sidecars */
375 filelist_free(sidecars);
376 file_data_send_notification(fd, NOTIFY_REREAD);
380 ret |= file_data_check_changed_files_recursive(fd, &st);
386 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean check_sidecars, GHashTable *basename_hash)
390 DEBUG_2("file_data_new: '%s' %d %d", path_utf8, check_sidecars, !!basename_hash);
393 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
395 fd = g_hash_table_lookup(file_data_pool, path_utf8);
401 if (!fd && file_data_planned_change_hash)
403 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
406 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
408 file_data_apply_ci(fd);
417 file_data_basename_hash_insert(basename_hash, fd);
419 file_data_check_sidecars(fd, basename_hash);
423 changed = file_data_check_changed_files(fd);
425 changed = file_data_check_changed_files_recursive(fd, st);
426 if (changed && check_sidecars && sidecar_file_priority(fd->extension))
427 file_data_check_sidecars(fd, basename_hash);
428 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
433 fd = g_new0(FileData, 1);
435 fd->size = st->st_size;
436 fd->date = st->st_mtime;
437 fd->mode = st->st_mode;
439 fd->magick = 0x12345678;
441 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
442 if (basename_hash) file_data_basename_hash_insert(basename_hash, fd);
445 file_data_check_sidecars(fd, basename_hash);
450 /* extension must contain only ASCII characters */
451 static GList *check_case_insensitive_ext(gchar *path)
458 sl = path_from_utf8(path);
460 extl = strrchr(sl, '.');
464 extl++; /* the first char after . */
465 ext_len = strlen(extl);
467 for (i = 0; i < (1 << ext_len); i++)
470 gboolean skip = FALSE;
471 for (j = 0; j < ext_len; j++)
473 if (i & (1 << (ext_len - 1 - j)))
475 extl[j] = g_ascii_tolower(extl[j]);
476 /* make sure the result does not contain duplicates */
477 if (extl[j] == g_ascii_toupper(extl[j]))
479 /* no change, probably a number, we have already tested this combination */
485 extl[j] = g_ascii_toupper(extl[j]);
489 if (stat(sl, &st) == 0)
491 list = g_list_prepend(list, file_data_new_local(sl, &st, FALSE, FALSE));
500 static void file_data_check_sidecars(FileData *fd, GHashTable *basename_hash)
504 FileData *parent_fd = NULL;
506 const GList *basename_list = NULL;
507 GList *group_list = NULL;
508 if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
511 base_len = fd->extension - fd->path;
512 fname = g_string_new_len(fd->path, base_len);
516 basename_list = g_hash_table_lookup(basename_hash, fname->str);
520 /* check for possible sidecar files;
521 the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
522 they have fd->ref set to 0 and file_data unref must chack and free them all together
523 (using fd->ref would cause loops and leaks)
526 /* find all possible sidecar files and order them according to sidecar_ext_get_list,
527 for case-only differences put lowercase first,
528 put the result to group_list
530 work = sidecar_ext_get_list();
533 gchar *ext = work->data;
539 g_string_truncate(fname, base_len);
540 g_string_append(fname, ext);
541 new_list = check_case_insensitive_ext(fname->str);
542 group_list = g_list_concat(group_list, new_list);
546 const GList *work2 = basename_list;
550 FileData *sfd = work2->data;
552 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
554 group_list = g_list_append(group_list, file_data_ref(sfd));
560 g_string_free(fname, TRUE);
562 /* process the group list - the first one is the parent file, others are sidecars */
566 FileData *new_fd = work->data;
569 if (new_fd->disable_grouping)
571 file_data_unref(new_fd);
575 new_fd->ref--; /* do not use ref here */
578 parent_fd = new_fd; /* parent is the one with the highest prio, found first */
580 file_data_merge_sidecar_files(parent_fd, new_fd);
582 g_list_free(group_list);
586 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars, GHashTable *basename_hash)
588 gchar *path_utf8 = path_to_utf8(path);
589 FileData *ret = file_data_new(path_utf8, st, check_sidecars, basename_hash);
595 FileData *file_data_new_simple(const gchar *path_utf8)
599 if (!stat_utf8(path_utf8, &st))
605 return file_data_new(path_utf8, &st, TRUE, NULL);
608 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
610 sfd->parent = target;
611 if (!g_list_find(target->sidecar_files, sfd))
612 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
613 file_data_increment_version(sfd); /* increments both sfd and target */
618 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
622 file_data_add_sidecar_file(target, source);
624 work = source->sidecar_files;
627 FileData *sfd = work->data;
628 file_data_add_sidecar_file(target, sfd);
632 g_list_free(source->sidecar_files);
633 source->sidecar_files = NULL;
635 target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
640 #ifdef DEBUG_FILEDATA
641 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
643 FileData *file_data_ref(FileData *fd)
646 if (fd == NULL) return NULL;
647 #ifdef DEBUG_FILEDATA
648 if (fd->magick != 0x12345678)
649 DEBUG_0("fd magick mismatch at %s:%d", file, line);
651 g_assert(fd->magick == 0x12345678);
654 #ifdef DEBUG_FILEDATA
655 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
657 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
662 static void file_data_free(FileData *fd)
664 g_assert(fd->magick == 0x12345678);
665 g_assert(fd->ref == 0);
667 g_hash_table_remove(file_data_pool, fd->original_path);
670 g_free(fd->original_path);
671 g_free(fd->collate_key_name);
672 g_free(fd->collate_key_name_nocase);
673 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
674 histmap_free(fd->histmap);
676 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
678 file_data_change_info_free(NULL, fd);
682 #ifdef DEBUG_FILEDATA
683 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
685 void file_data_unref(FileData *fd)
688 if (fd == NULL) return;
689 #ifdef DEBUG_FILEDATA
690 if (fd->magick != 0x12345678)
691 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
693 g_assert(fd->magick == 0x12345678);
696 #ifdef DEBUG_FILEDATA
697 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
699 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
704 FileData *parent = fd->parent ? fd->parent : fd;
706 if (parent->ref > 0) return;
708 work = parent->sidecar_files;
711 FileData *sfd = work->data;
712 if (sfd->ref > 0) return;
716 /* none of parent/children is referenced, we can free everything */
718 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
720 work = parent->sidecar_files;
723 FileData *sfd = work->data;
728 g_list_free(parent->sidecar_files);
729 parent->sidecar_files = NULL;
731 file_data_free(parent);
735 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
737 sfd->parent = target;
738 g_assert(g_list_find(target->sidecar_files, sfd));
740 file_data_increment_version(sfd); /* increments both sfd and target */
742 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
754 /* disables / enables grouping for particular file, sends UPDATE notification */
755 void file_data_disable_grouping(FileData *fd, gboolean disable)
757 if (!fd->disable_grouping == !disable) return;
759 fd->disable_grouping = !!disable;
765 FileData *parent = file_data_ref(fd->parent);
766 file_data_disconnect_sidecar_file(parent, fd);
767 file_data_send_notification(parent, NOTIFY_GROUPING);
768 file_data_unref(parent);
770 else if (fd->sidecar_files)
772 GList *sidecar_files = filelist_copy(fd->sidecar_files);
773 GList *work = sidecar_files;
776 FileData *sfd = work->data;
778 file_data_disconnect_sidecar_file(fd, sfd);
779 file_data_send_notification(sfd, NOTIFY_GROUPING);
781 file_data_check_sidecars((FileData *)sidecar_files->data, FALSE); /* this will group the sidecars back together */
782 filelist_free(sidecar_files);
786 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
791 file_data_increment_version(fd);
792 file_data_check_sidecars(fd, FALSE);
794 file_data_send_notification(fd, NOTIFY_GROUPING);
797 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
804 FileData *fd = work->data;
806 file_data_disable_grouping(fd, disable);
812 /* compare name without extension */
813 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
815 size_t len1 = fd1->extension - fd1->name;
816 size_t len2 = fd2->extension - fd2->name;
818 if (len1 < len2) return -1;
819 if (len1 > len2) return 1;
821 return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
824 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
826 if (!fdci && fd) fdci = fd->change;
830 g_free(fdci->source);
835 if (fd) fd->change = NULL;
838 static gboolean file_data_can_write_directly(FileData *fd)
840 return filter_name_is_writable(fd->extension);
843 static gboolean file_data_can_write_sidecar(FileData *fd)
845 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
848 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
850 gchar *sidecar_path = NULL;
853 if (!file_data_can_write_sidecar(fd)) return NULL;
855 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
858 FileData *sfd = work->data;
860 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
862 sidecar_path = g_strdup(sfd->path);
867 if (!existing_only && !sidecar_path)
869 gchar *base = remove_extension_from_path(fd->path);
870 sidecar_path = g_strconcat(base, ".xmp", NULL);
879 *-----------------------------------------------------------------------------
880 * sidecar file info struct
881 *-----------------------------------------------------------------------------
886 static gint sidecar_file_priority(const gchar *path)
888 const gchar *extension = extension_from_path(path);
892 if (extension == NULL)
895 work = sidecar_ext_get_list();
898 gchar *ext = work->data;
901 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
909 *-----------------------------------------------------------------------------
911 *-----------------------------------------------------------------------------
914 static SortType filelist_sort_method = SORT_NONE;
915 static gboolean filelist_sort_ascend = TRUE;
918 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
921 if (!filelist_sort_ascend)
928 switch (filelist_sort_method)
933 if (fa->size < fb->size) return -1;
934 if (fa->size > fb->size) return 1;
935 /* fall back to name */
938 if (fa->date < fb->date) return -1;
939 if (fa->date > fb->date) return 1;
940 /* fall back to name */
942 #ifdef HAVE_STRVERSCMP
944 ret = strverscmp(fa->name, fb->name);
945 if (ret != 0) return ret;
952 if (options->file_sort.case_sensitive)
953 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
955 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
957 if (ret != 0) return ret;
959 /* do not return 0 unless the files are really the same
960 file_data_pool ensures that original_path is unique
962 return strcmp(fa->original_path, fb->original_path);
965 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
967 filelist_sort_method = method;
968 filelist_sort_ascend = ascend;
969 return filelist_sort_compare_filedata(fa, fb);
972 static gint filelist_sort_file_cb(gpointer a, gpointer b)
974 return filelist_sort_compare_filedata(a, b);
977 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
979 filelist_sort_method = method;
980 filelist_sort_ascend = ascend;
981 return g_list_sort(list, cb);
984 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
986 filelist_sort_method = method;
987 filelist_sort_ascend = ascend;
988 return g_list_insert_sorted(list, data, cb);
991 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
993 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
996 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
998 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1002 static GList *filelist_filter_out_sidecars(GList *flist)
1004 GList *work = flist;
1005 GList *flist_filtered = NULL;
1009 FileData *fd = work->data;
1012 if (fd->parent) /* remove fd's that are children */
1013 file_data_unref(fd);
1015 flist_filtered = g_list_prepend(flist_filtered, fd);
1019 return flist_filtered;
1022 static gboolean is_hidden_file(const gchar *name)
1024 if (name[0] != '.') return FALSE;
1025 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1029 static gboolean filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gboolean follow_symlinks)
1034 GList *dlist = NULL;
1035 GList *flist = NULL;
1036 gint (*stat_func)(const gchar *path, struct stat *buf);
1037 GHashTable *basename_hash = NULL;
1039 g_assert(files || dirs);
1041 if (files) *files = NULL;
1042 if (dirs) *dirs = NULL;
1044 pathl = path_from_utf8(dir_fd->path);
1045 if (!pathl) return FALSE;
1047 dp = opendir(pathl);
1054 if (files) basename_hash = file_data_basename_hash_new();
1056 if (follow_symlinks)
1061 while ((dir = readdir(dp)) != NULL)
1063 struct stat ent_sbuf;
1064 const gchar *name = dir->d_name;
1067 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1070 filepath = g_build_filename(pathl, name, NULL);
1071 if (stat_func(filepath, &ent_sbuf) >= 0)
1073 if (S_ISDIR(ent_sbuf.st_mode))
1075 /* we ignore the .thumbnails dir for cleanliness */
1077 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1078 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1079 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1080 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1082 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE, NULL));
1087 if (files && filter_name_exists(name))
1089 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE, basename_hash));
1095 if (errno == EOVERFLOW)
1097 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1106 if (basename_hash) file_data_basename_hash_free(basename_hash);
1108 if (dirs) *dirs = dlist;
1109 if (files) *files = filelist_filter_out_sidecars(flist);
1114 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1116 return filelist_read_real(dir_fd, files, dirs, TRUE);
1119 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1121 return filelist_read_real(dir_fd, files, dirs, FALSE);
1124 void filelist_free(GList *list)
1131 file_data_unref((FileData *)work->data);
1139 GList *filelist_copy(GList *list)
1141 GList *new_list = NULL;
1152 new_list = g_list_prepend(new_list, file_data_ref(fd));
1155 return g_list_reverse(new_list);
1158 GList *filelist_from_path_list(GList *list)
1160 GList *new_list = NULL;
1171 new_list = g_list_prepend(new_list, file_data_new_simple(path));
1174 return g_list_reverse(new_list);
1177 GList *filelist_to_path_list(GList *list)
1179 GList *new_list = NULL;
1190 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1193 return g_list_reverse(new_list);
1196 GList *filelist_filter(GList *list, gboolean is_dir_list)
1200 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1205 FileData *fd = (FileData *)(work->data);
1206 const gchar *name = fd->name;
1208 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1209 (!is_dir_list && !filter_name_exists(name)) ||
1210 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1211 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1215 list = g_list_remove_link(list, link);
1216 file_data_unref(fd);
1227 *-----------------------------------------------------------------------------
1228 * filelist recursive
1229 *-----------------------------------------------------------------------------
1232 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1234 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1237 GList *filelist_sort_path(GList *list)
1239 return g_list_sort(list, filelist_sort_path_cb);
1242 static void filelist_recursive_append(GList **list, GList *dirs)
1249 FileData *fd = (FileData *)(work->data);
1253 if (filelist_read(fd, &f, &d))
1255 f = filelist_filter(f, FALSE);
1256 f = filelist_sort_path(f);
1257 *list = g_list_concat(*list, f);
1259 d = filelist_filter(d, TRUE);
1260 d = filelist_sort_path(d);
1261 filelist_recursive_append(list, d);
1269 GList *filelist_recursive(FileData *dir_fd)
1274 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1275 list = filelist_filter(list, FALSE);
1276 list = filelist_sort_path(list);
1278 d = filelist_filter(d, TRUE);
1279 d = filelist_sort_path(d);
1280 filelist_recursive_append(&list, d);
1288 * marks and orientation
1291 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1292 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1293 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1294 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1296 gboolean file_data_get_mark(FileData *fd, gint n)
1298 gboolean valid = (fd->valid_marks & (1 << n));
1300 if (file_data_get_mark_func[n] && !valid)
1302 guint old = fd->marks;
1303 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1305 if (!value != !(fd->marks & (1 << n)))
1307 fd->marks = fd->marks ^ (1 << n);
1310 fd->valid_marks |= (1 << n);
1311 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1313 file_data_unref(fd);
1315 else if (!old && fd->marks)
1321 return !!(fd->marks & (1 << n));
1324 guint file_data_get_marks(FileData *fd)
1327 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1331 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1334 if (!value == !file_data_get_mark(fd, n)) return;
1336 if (file_data_set_mark_func[n])
1338 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1343 fd->marks = fd->marks ^ (1 << n);
1345 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1347 file_data_unref(fd);
1349 else if (!old && fd->marks)
1354 file_data_increment_version(fd);
1355 file_data_send_notification(fd, NOTIFY_MARKS);
1358 gboolean file_data_filter_marks(FileData *fd, guint filter)
1361 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1362 return ((fd->marks & filter) == filter);
1365 GList *file_data_filter_marks_list(GList *list, guint filter)
1372 FileData *fd = work->data;
1376 if (!file_data_filter_marks(fd, filter))
1378 list = g_list_remove_link(list, link);
1379 file_data_unref(fd);
1387 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1389 FileData *fd = value;
1390 file_data_increment_version(fd);
1391 file_data_send_notification(fd, NOTIFY_MARKS);
1394 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1396 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1398 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1400 file_data_get_mark_func[n] = get_mark_func;
1401 file_data_set_mark_func[n] = set_mark_func;
1402 file_data_mark_func_data[n] = data;
1403 file_data_destroy_mark_func[n] = notify;
1407 /* this effectively changes all known files */
1408 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1414 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1416 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1417 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1418 if (data) *data = file_data_mark_func_data[n];
1421 gint file_data_get_user_orientation(FileData *fd)
1423 return fd->user_orientation;
1426 void file_data_set_user_orientation(FileData *fd, gint value)
1428 if (fd->user_orientation == value) return;
1430 fd->user_orientation = value;
1431 file_data_increment_version(fd);
1432 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1437 * file_data - operates on the given fd
1438 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1442 /* return list of sidecar file extensions in a string */
1443 gchar *file_data_sc_list_to_string(FileData *fd)
1446 GString *result = g_string_new("");
1448 work = fd->sidecar_files;
1451 FileData *sfd = work->data;
1453 result = g_string_append(result, "+ ");
1454 result = g_string_append(result, sfd->extension);
1456 if (work) result = g_string_append_c(result, ' ');
1459 return g_string_free(result, FALSE);
1465 * add FileDataChangeInfo (see typedefs.h) for the given operation
1466 * uses file_data_add_change_info
1468 * fails if the fd->change already exists - change operations can't run in parallel
1469 * fd->change_info works as a lock
1471 * dest can be NULL - in this case the current name is used for now, it will
1476 FileDataChangeInfo types:
1478 MOVE - path is changed, name may be changed too
1479 RENAME - path remains unchanged, name is changed
1480 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1481 sidecar names are changed too, extensions are not changed
1483 UPDATE - file size, date or grouping has been changed
1486 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1488 FileDataChangeInfo *fdci;
1490 if (fd->change) return FALSE;
1492 fdci = g_new0(FileDataChangeInfo, 1);
1497 fdci->source = g_strdup(src);
1499 fdci->source = g_strdup(fd->path);
1502 fdci->dest = g_strdup(dest);
1509 static void file_data_planned_change_remove(FileData *fd)
1511 if (file_data_planned_change_hash &&
1512 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1514 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1516 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1517 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1518 file_data_unref(fd);
1519 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1521 g_hash_table_destroy(file_data_planned_change_hash);
1522 file_data_planned_change_hash = NULL;
1523 DEBUG_1("planned change: empty");
1530 void file_data_free_ci(FileData *fd)
1532 FileDataChangeInfo *fdci = fd->change;
1536 file_data_planned_change_remove(fd);
1538 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1540 g_free(fdci->source);
1548 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1550 FileDataChangeInfo *fdci = fd->change;
1552 fdci->regroup_when_finished = enable;
1555 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1559 if (fd->parent) fd = fd->parent;
1561 if (fd->change) return FALSE;
1563 work = fd->sidecar_files;
1566 FileData *sfd = work->data;
1568 if (sfd->change) return FALSE;
1572 file_data_add_ci(fd, type, NULL, NULL);
1574 work = fd->sidecar_files;
1577 FileData *sfd = work->data;
1579 file_data_add_ci(sfd, type, NULL, NULL);
1586 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1590 if (fd->parent) fd = fd->parent;
1592 if (!fd->change || fd->change->type != type) return FALSE;
1594 work = fd->sidecar_files;
1597 FileData *sfd = work->data;
1599 if (!sfd->change || sfd->change->type != type) return FALSE;
1607 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1609 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1610 file_data_sc_update_ci_copy(fd, dest_path);
1614 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1616 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1617 file_data_sc_update_ci_move(fd, dest_path);
1621 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1623 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1624 file_data_sc_update_ci_rename(fd, dest_path);
1628 gboolean file_data_sc_add_ci_delete(FileData *fd)
1630 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1633 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1635 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1636 file_data_sc_update_ci_unspecified(fd, dest_path);
1640 gboolean file_data_add_ci_write_metadata(FileData *fd)
1642 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1645 void file_data_sc_free_ci(FileData *fd)
1649 if (fd->parent) fd = fd->parent;
1651 file_data_free_ci(fd);
1653 work = fd->sidecar_files;
1656 FileData *sfd = work->data;
1658 file_data_free_ci(sfd);
1663 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1666 gboolean ret = TRUE;
1671 FileData *fd = work->data;
1673 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1680 static void file_data_sc_revert_ci_list(GList *fd_list)
1687 FileData *fd = work->data;
1689 file_data_sc_free_ci(fd);
1694 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1701 FileData *fd = work->data;
1703 if (!func(fd, dest))
1705 file_data_sc_revert_ci_list(work->prev);
1714 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1716 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1719 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1721 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1724 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1726 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1729 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1731 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1734 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1737 gboolean ret = TRUE;
1742 FileData *fd = work->data;
1744 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1751 void file_data_free_ci_list(GList *fd_list)
1758 FileData *fd = work->data;
1760 file_data_free_ci(fd);
1765 void file_data_sc_free_ci_list(GList *fd_list)
1772 FileData *fd = work->data;
1774 file_data_sc_free_ci(fd);
1780 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1781 * fails if fd->change does not exist or the change type does not match
1784 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1786 FileDataChangeType type = fd->change->type;
1788 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1792 if (!file_data_planned_change_hash)
1793 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1795 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1797 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1798 g_hash_table_remove(file_data_planned_change_hash, old_path);
1799 file_data_unref(fd);
1802 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1807 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1808 g_hash_table_remove(file_data_planned_change_hash, new_path);
1809 file_data_unref(ofd);
1812 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1814 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1819 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1821 gchar *old_path = fd->change->dest;
1823 fd->change->dest = g_strdup(dest_path);
1824 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1828 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1830 const gchar *extension = extension_from_path(fd->change->source);
1831 gchar *base = remove_extension_from_path(dest_path);
1832 gchar *old_path = fd->change->dest;
1834 fd->change->dest = g_strconcat(base, extension, NULL);
1835 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1841 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1844 gchar *dest_path_full = NULL;
1846 if (fd->parent) fd = fd->parent;
1850 dest_path = fd->path;
1852 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1854 gchar *dir = remove_level_from_path(fd->path);
1856 dest_path_full = g_build_filename(dir, dest_path, NULL);
1858 dest_path = dest_path_full;
1860 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1862 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1863 dest_path = dest_path_full;
1866 file_data_update_ci_dest(fd, dest_path);
1868 work = fd->sidecar_files;
1871 FileData *sfd = work->data;
1873 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1877 g_free(dest_path_full);
1880 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1882 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1883 file_data_sc_update_ci(fd, dest_path);
1887 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1889 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1892 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1894 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1897 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1899 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1902 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1904 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1907 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1909 gboolean (*func)(FileData *, const gchar *))
1912 gboolean ret = TRUE;
1917 FileData *fd = work->data;
1919 if (!func(fd, dest)) ret = FALSE;
1926 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1928 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1931 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1933 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1936 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1938 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1943 * verify source and dest paths - dest image exists, etc.
1944 * it should detect all possible problems with the planned operation
1947 gint file_data_verify_ci(FileData *fd)
1949 gint ret = CHANGE_OK;
1954 DEBUG_1("Change checked: no change info: %s", fd->path);
1958 if (!isname(fd->path))
1960 /* this probably should not happen */
1961 ret |= CHANGE_NO_SRC;
1962 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1966 dir = remove_level_from_path(fd->path);
1968 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1969 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
1970 fd->change->type != FILEDATA_CHANGE_RENAME &&
1971 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1974 ret |= CHANGE_WARN_UNSAVED_META;
1975 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
1978 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1979 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1980 !access_file(fd->path, R_OK))
1982 ret |= CHANGE_NO_READ_PERM;
1983 DEBUG_1("Change checked: no read permission: %s", fd->path);
1985 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1986 !access_file(dir, W_OK))
1988 ret |= CHANGE_NO_WRITE_PERM_DIR;
1989 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1991 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1992 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1993 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1994 !access_file(fd->path, W_OK))
1996 ret |= CHANGE_WARN_NO_WRITE_PERM;
1997 DEBUG_1("Change checked: no write permission: %s", fd->path);
1999 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2000 - that means that there are no hard errors and warnings can be disabled
2001 - the destination is determined during the check
2003 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2005 /* determine destination file */
2006 gboolean have_dest = FALSE;
2007 gchar *dest_dir = NULL;
2009 if (options->metadata.save_in_image_file)
2011 if (file_data_can_write_directly(fd))
2013 /* we can write the file directly */
2014 if (access_file(fd->path, W_OK))
2020 if (options->metadata.warn_on_write_problems)
2022 ret |= CHANGE_WARN_NO_WRITE_PERM;
2023 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2027 else if (file_data_can_write_sidecar(fd))
2029 /* we can write sidecar */
2030 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2031 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2033 file_data_update_ci_dest(fd, sidecar);
2038 if (options->metadata.warn_on_write_problems)
2040 ret |= CHANGE_WARN_NO_WRITE_PERM;
2041 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2050 /* write private metadata file under ~/.geeqie */
2052 /* If an existing metadata file exists, we will try writing to
2053 * it's location regardless of the user's preference.
2055 gchar *metadata_path = NULL;
2057 /* but ignore XMP if we are not able to write it */
2058 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2060 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2062 if (metadata_path && !access_file(metadata_path, W_OK))
2064 g_free(metadata_path);
2065 metadata_path = NULL;
2072 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2073 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2075 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2077 metadata_path = g_build_filename(dest_dir, filename, NULL);
2081 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2083 file_data_update_ci_dest(fd, metadata_path);
2088 ret |= CHANGE_NO_WRITE_PERM_DEST;
2089 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2091 g_free(metadata_path);
2096 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2101 same = (strcmp(fd->path, fd->change->dest) == 0);
2105 const gchar *dest_ext = extension_from_path(fd->change->dest);
2106 if (!dest_ext) dest_ext = "";
2108 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2110 ret |= CHANGE_WARN_CHANGED_EXT;
2111 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2116 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2118 ret |= CHANGE_WARN_SAME;
2119 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2123 dest_dir = remove_level_from_path(fd->change->dest);
2125 if (!isdir(dest_dir))
2127 ret |= CHANGE_NO_DEST_DIR;
2128 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2130 else if (!access_file(dest_dir, W_OK))
2132 ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
2133 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2137 if (isfile(fd->change->dest))
2139 if (!access_file(fd->change->dest, W_OK))
2141 ret |= CHANGE_NO_WRITE_PERM_DEST;
2142 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2146 ret |= CHANGE_WARN_DEST_EXISTS;
2147 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2150 else if (isdir(fd->change->dest))
2152 ret |= CHANGE_DEST_EXISTS;
2153 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2160 fd->change->error = ret;
2161 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2168 gint file_data_sc_verify_ci(FileData *fd)
2173 ret = file_data_verify_ci(fd);
2175 work = fd->sidecar_files;
2178 FileData *sfd = work->data;
2180 ret |= file_data_verify_ci(sfd);
2187 gchar *file_data_get_error_string(gint error)
2189 GString *result = g_string_new("");
2191 if (error & CHANGE_NO_SRC)
2193 if (result->len > 0) g_string_append(result, ", ");
2194 g_string_append(result, _("file or directory does not exist"));
2197 if (error & CHANGE_DEST_EXISTS)
2199 if (result->len > 0) g_string_append(result, ", ");
2200 g_string_append(result, _("destination already exists"));
2203 if (error & CHANGE_NO_WRITE_PERM_DEST)
2205 if (result->len > 0) g_string_append(result, ", ");
2206 g_string_append(result, _("destination can't be overwritten"));
2209 if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
2211 if (result->len > 0) g_string_append(result, ", ");
2212 g_string_append(result, _("destination directory is not writable"));
2215 if (error & CHANGE_NO_DEST_DIR)
2217 if (result->len > 0) g_string_append(result, ", ");
2218 g_string_append(result, _("destination directory does not exist"));
2221 if (error & CHANGE_NO_WRITE_PERM_DIR)
2223 if (result->len > 0) g_string_append(result, ", ");
2224 g_string_append(result, _("source directory is not writable"));
2227 if (error & CHANGE_NO_READ_PERM)
2229 if (result->len > 0) g_string_append(result, ", ");
2230 g_string_append(result, _("no read permission"));
2233 if (error & CHANGE_WARN_NO_WRITE_PERM)
2235 if (result->len > 0) g_string_append(result, ", ");
2236 g_string_append(result, _("file is readonly"));
2239 if (error & CHANGE_WARN_DEST_EXISTS)
2241 if (result->len > 0) g_string_append(result, ", ");
2242 g_string_append(result, _("destination already exists and will be overwritten"));
2245 if (error & CHANGE_WARN_SAME)
2247 if (result->len > 0) g_string_append(result, ", ");
2248 g_string_append(result, _("source and destination are the same"));
2251 if (error & CHANGE_WARN_CHANGED_EXT)
2253 if (result->len > 0) g_string_append(result, ", ");
2254 g_string_append(result, _("source and destination have different extension"));
2257 if (error & CHANGE_WARN_UNSAVED_META)
2259 if (result->len > 0) g_string_append(result, ", ");
2260 g_string_append(result, _("there are unsaved metadata changes for the file"));
2263 return g_string_free(result, FALSE);
2266 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2269 gint all_errors = 0;
2270 gint common_errors = ~0;
2275 if (!list) return 0;
2277 num = g_list_length(list);
2278 errors = g_new(int, num);
2289 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2290 all_errors |= error;
2291 common_errors &= error;
2298 if (desc && all_errors)
2301 GString *result = g_string_new("");
2305 gchar *str = file_data_get_error_string(common_errors);
2306 g_string_append(result, str);
2307 g_string_append(result, "\n");
2321 error = errors[i] & ~common_errors;
2325 gchar *str = file_data_get_error_string(error);
2326 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2331 *desc = g_string_free(result, FALSE);
2340 * perform the change described by FileFataChangeInfo
2341 * it is used for internal operations,
2342 * this function actually operates with files on the filesystem
2343 * it should implement safe delete
2346 static gboolean file_data_perform_move(FileData *fd)
2348 g_assert(!strcmp(fd->change->source, fd->path));
2349 return move_file(fd->change->source, fd->change->dest);
2352 static gboolean file_data_perform_copy(FileData *fd)
2354 g_assert(!strcmp(fd->change->source, fd->path));
2355 return copy_file(fd->change->source, fd->change->dest);
2358 static gboolean file_data_perform_delete(FileData *fd)
2360 if (isdir(fd->path) && !islink(fd->path))
2361 return rmdir_utf8(fd->path);
2363 if (options->file_ops.safe_delete_enable)
2364 return file_util_safe_unlink(fd->path);
2366 return unlink_file(fd->path);
2369 gboolean file_data_perform_ci(FileData *fd)
2371 FileDataChangeType type = fd->change->type;
2375 case FILEDATA_CHANGE_MOVE:
2376 return file_data_perform_move(fd);
2377 case FILEDATA_CHANGE_COPY:
2378 return file_data_perform_copy(fd);
2379 case FILEDATA_CHANGE_RENAME:
2380 return file_data_perform_move(fd); /* the same as move */
2381 case FILEDATA_CHANGE_DELETE:
2382 return file_data_perform_delete(fd);
2383 case FILEDATA_CHANGE_WRITE_METADATA:
2384 return metadata_write_perform(fd);
2385 case FILEDATA_CHANGE_UNSPECIFIED:
2386 /* nothing to do here */
2394 gboolean file_data_sc_perform_ci(FileData *fd)
2397 gboolean ret = TRUE;
2398 FileDataChangeType type = fd->change->type;
2400 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2402 work = fd->sidecar_files;
2405 FileData *sfd = work->data;
2407 if (!file_data_perform_ci(sfd)) ret = FALSE;
2411 if (!file_data_perform_ci(fd)) ret = FALSE;
2417 * updates FileData structure according to FileDataChangeInfo
2420 gboolean file_data_apply_ci(FileData *fd)
2422 FileDataChangeType type = fd->change->type;
2425 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2427 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2428 file_data_planned_change_remove(fd);
2430 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2432 /* this change overwrites another file which is already known to other modules
2433 renaming fd would create duplicate FileData structure
2434 the best thing we can do is nothing
2435 FIXME: maybe we could copy stuff like marks
2437 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2441 file_data_set_path(fd, fd->change->dest);
2444 file_data_increment_version(fd);
2445 file_data_send_notification(fd, NOTIFY_CHANGE);
2450 gboolean file_data_sc_apply_ci(FileData *fd)
2453 FileDataChangeType type = fd->change->type;
2455 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2457 work = fd->sidecar_files;
2460 FileData *sfd = work->data;
2462 file_data_apply_ci(sfd);
2466 file_data_apply_ci(fd);
2471 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2474 if (fd->parent) fd = fd->parent;
2475 if (!g_list_find(list, fd)) return FALSE;
2477 work = fd->sidecar_files;
2480 if (!g_list_find(list, work->data)) return FALSE;
2487 static gboolean file_data_list_dump(GList *list)
2489 GList *work, *work2;
2494 FileData *fd = work->data;
2495 printf("%s\n", fd->name);
2496 work2 = fd->sidecar_files;
2499 FileData *fd = work2->data;
2500 printf(" %s\n", fd->name);
2501 work2 = work2->next;
2509 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2514 /* change partial groups to independent files */
2519 FileData *fd = work->data;
2522 if (!file_data_list_contains_whole_group(list, fd))
2524 file_data_disable_grouping(fd, TRUE);
2527 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2533 /* remove sidecars from the list,
2534 they can be still acessed via main_fd->sidecar_files */
2538 FileData *fd = work->data;
2542 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2544 out = g_list_prepend(out, file_data_ref(fd));
2548 filelist_free(list);
2549 out = g_list_reverse(out);
2559 * notify other modules about the change described by FileDataChangeInfo
2562 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2563 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2564 implementation in view_file_list.c */
2569 typedef struct _NotifyData NotifyData;
2571 struct _NotifyData {
2572 FileDataNotifyFunc func;
2574 NotifyPriority priority;
2577 static GList *notify_func_list = NULL;
2579 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2581 NotifyData *nda = (NotifyData *)a;
2582 NotifyData *ndb = (NotifyData *)b;
2584 if (nda->priority < ndb->priority) return -1;
2585 if (nda->priority > ndb->priority) return 1;
2589 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2592 GList *work = notify_func_list;
2596 NotifyData *nd = (NotifyData *)work->data;
2598 if (nd->func == func && nd->data == data)
2600 g_warning("Notify func already registered");
2606 nd = g_new(NotifyData, 1);
2609 nd->priority = priority;
2611 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2612 DEBUG_2("Notify func registered: %p", nd);
2617 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2619 GList *work = notify_func_list;
2623 NotifyData *nd = (NotifyData *)work->data;
2625 if (nd->func == func && nd->data == data)
2627 notify_func_list = g_list_delete_link(notify_func_list, work);
2629 DEBUG_2("Notify func unregistered: %p", nd);
2635 g_warning("Notify func not found");
2640 void file_data_send_notification(FileData *fd, NotifyType type)
2642 GList *work = notify_func_list;
2646 NotifyData *nd = (NotifyData *)work->data;
2648 nd->func(fd, type, nd->data);
2653 static GHashTable *file_data_monitor_pool = NULL;
2654 static guint realtime_monitor_id = 0; /* event source id */
2656 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2660 file_data_check_changed_files(fd);
2662 DEBUG_1("monitor %s", fd->path);
2665 static gboolean realtime_monitor_cb(gpointer data)
2667 if (!options->update_on_time_change) return TRUE;
2668 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2672 gboolean file_data_register_real_time_monitor(FileData *fd)
2678 if (!file_data_monitor_pool)
2679 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2681 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2683 DEBUG_1("Register realtime %d %s", count, fd->path);
2686 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2688 if (!realtime_monitor_id)
2690 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2696 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2700 g_assert(file_data_monitor_pool);
2702 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2704 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2706 g_assert(count > 0);
2711 g_hash_table_remove(file_data_monitor_pool, fd);
2713 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2715 file_data_unref(fd);
2717 if (g_hash_table_size(file_data_monitor_pool) == 0)
2719 g_source_remove(realtime_monitor_id);
2720 realtime_monitor_id = 0;
2726 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */