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 disable_sidecars, GHashTable *basename_hash)
390 DEBUG_2("file_data_new: '%s' %d %d", path_utf8, disable_sidecars, !!basename_hash);
392 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
395 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
397 fd = g_hash_table_lookup(file_data_pool, path_utf8);
403 if (!fd && file_data_planned_change_hash)
405 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
408 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
410 file_data_apply_ci(fd);
418 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
422 file_data_basename_hash_insert(basename_hash, fd);
423 if (!disable_sidecars)
424 file_data_check_sidecars(fd, basename_hash);
428 changed = file_data_check_changed_files(fd);
430 changed = file_data_check_changed_files_recursive(fd, st);
431 if (changed && !disable_sidecars && sidecar_file_priority(fd->extension))
432 file_data_check_sidecars(fd, basename_hash);
433 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
438 fd = g_new0(FileData, 1);
440 fd->size = st->st_size;
441 fd->date = st->st_mtime;
442 fd->mode = st->st_mode;
444 fd->magick = 0x12345678;
446 if (disable_sidecars) fd->disable_grouping = TRUE;
448 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
449 if (basename_hash) file_data_basename_hash_insert(basename_hash, fd);
451 if (!disable_sidecars)
453 g_assert(basename_hash);
454 file_data_check_sidecars(fd, basename_hash);
461 static void file_data_check_sidecars(FileData *fd, GHashTable *basename_hash)
465 FileData *parent_fd = NULL;
467 const GList *basename_list = NULL;
468 GList *group_list = NULL;
469 if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
472 base_len = fd->extension - fd->path;
473 fname = g_string_new_len(fd->path, base_len);
475 basename_list = g_hash_table_lookup(basename_hash, fname->str);
478 /* check for possible sidecar files;
479 the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
480 they have fd->ref set to 0 and file_data unref must chack and free them all together
481 (using fd->ref would cause loops and leaks)
484 /* find all possible sidecar files and order them according to sidecar_ext_get_list,
485 for case-only differences put lowercase first,
486 put the result to group_list
488 work = sidecar_ext_get_list();
491 gchar *ext = work->data;
494 const GList *work2 = basename_list;
498 FileData *sfd = work2->data;
500 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
502 group_list = g_list_append(group_list, file_data_ref(sfd));
507 g_string_free(fname, TRUE);
509 /* process the group list - the first one is the parent file, others are sidecars */
513 FileData *new_fd = work->data;
516 if (new_fd->disable_grouping)
518 file_data_unref(new_fd);
522 new_fd->ref--; /* do not use ref here */
525 parent_fd = new_fd; /* parent is the one with the highest prio, found first */
527 file_data_merge_sidecar_files(parent_fd, new_fd);
529 g_list_free(group_list);
533 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars, GHashTable *basename_hash)
535 gchar *path_utf8 = path_to_utf8(path);
536 FileData *ret = file_data_new(path_utf8, st, disable_sidecars, basename_hash);
542 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
544 sfd->parent = target;
545 if (!g_list_find(target->sidecar_files, sfd))
546 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
547 file_data_increment_version(sfd); /* increments both sfd and target */
552 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
556 file_data_add_sidecar_file(target, source);
558 work = source->sidecar_files;
561 FileData *sfd = work->data;
562 file_data_add_sidecar_file(target, sfd);
566 g_list_free(source->sidecar_files);
567 source->sidecar_files = NULL;
569 target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
574 #ifdef DEBUG_FILEDATA
575 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
577 FileData *file_data_ref(FileData *fd)
580 if (fd == NULL) return NULL;
581 #ifdef DEBUG_FILEDATA
582 if (fd->magick != 0x12345678)
583 DEBUG_0("fd magick mismatch at %s:%d", file, line);
585 g_assert(fd->magick == 0x12345678);
588 #ifdef DEBUG_FILEDATA
589 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
591 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
596 static void file_data_free(FileData *fd)
598 g_assert(fd->magick == 0x12345678);
599 g_assert(fd->ref == 0);
601 metadata_cache_free(fd);
602 g_hash_table_remove(file_data_pool, fd->original_path);
605 g_free(fd->original_path);
606 g_free(fd->collate_key_name);
607 g_free(fd->collate_key_name_nocase);
608 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
609 histmap_free(fd->histmap);
611 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
613 file_data_change_info_free(NULL, fd);
617 #ifdef DEBUG_FILEDATA
618 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
620 void file_data_unref(FileData *fd)
623 if (fd == NULL) return;
624 #ifdef DEBUG_FILEDATA
625 if (fd->magick != 0x12345678)
626 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
628 g_assert(fd->magick == 0x12345678);
631 #ifdef DEBUG_FILEDATA
632 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
634 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
639 FileData *parent = fd->parent ? fd->parent : fd;
641 if (parent->ref > 0) return;
643 work = parent->sidecar_files;
646 FileData *sfd = work->data;
647 if (sfd->ref > 0) return;
651 /* none of parent/children is referenced, we can free everything */
653 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
655 work = parent->sidecar_files;
658 FileData *sfd = work->data;
663 g_list_free(parent->sidecar_files);
664 parent->sidecar_files = NULL;
666 file_data_free(parent);
670 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
672 sfd->parent = target;
673 g_assert(g_list_find(target->sidecar_files, sfd));
675 file_data_increment_version(sfd); /* increments both sfd and target */
677 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
689 /* disables / enables grouping for particular file, sends UPDATE notification */
690 void file_data_disable_grouping(FileData *fd, gboolean disable)
692 if (!fd->disable_grouping == !disable) return;
694 fd->disable_grouping = !!disable;
700 FileData *parent = file_data_ref(fd->parent);
701 file_data_disconnect_sidecar_file(parent, fd);
702 file_data_send_notification(parent, NOTIFY_GROUPING);
703 file_data_unref(parent);
705 else if (fd->sidecar_files)
707 GList *sidecar_files = filelist_copy(fd->sidecar_files);
708 GList *work = sidecar_files;
711 FileData *sfd = work->data;
713 file_data_disconnect_sidecar_file(fd, sfd);
714 file_data_send_notification(sfd, NOTIFY_GROUPING);
716 file_data_check_sidecars((FileData *)sidecar_files->data, FALSE); /* this will group the sidecars back together */
717 filelist_free(sidecar_files);
721 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
726 file_data_increment_version(fd);
727 file_data_check_sidecars(fd, FALSE);
729 file_data_send_notification(fd, NOTIFY_GROUPING);
732 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
739 FileData *fd = work->data;
741 file_data_disable_grouping(fd, disable);
747 /* compare name without extension */
748 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
750 size_t len1 = fd1->extension - fd1->name;
751 size_t len2 = fd2->extension - fd2->name;
753 if (len1 < len2) return -1;
754 if (len1 > len2) return 1;
756 return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
759 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
761 if (!fdci && fd) fdci = fd->change;
765 g_free(fdci->source);
770 if (fd) fd->change = NULL;
773 static gboolean file_data_can_write_directly(FileData *fd)
775 return filter_name_is_writable(fd->extension);
778 static gboolean file_data_can_write_sidecar(FileData *fd)
780 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
783 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
785 gchar *sidecar_path = NULL;
788 if (!file_data_can_write_sidecar(fd)) return NULL;
790 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
793 FileData *sfd = work->data;
795 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
797 sidecar_path = g_strdup(sfd->path);
802 if (!existing_only && !sidecar_path)
804 gchar *base = remove_extension_from_path(fd->path);
805 sidecar_path = g_strconcat(base, ".xmp", NULL);
814 *-----------------------------------------------------------------------------
815 * sidecar file info struct
816 *-----------------------------------------------------------------------------
821 static gint sidecar_file_priority(const gchar *path)
823 const gchar *extension = extension_from_path(path);
827 if (extension == NULL)
830 work = sidecar_ext_get_list();
833 gchar *ext = work->data;
836 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
844 *-----------------------------------------------------------------------------
846 *-----------------------------------------------------------------------------
849 static SortType filelist_sort_method = SORT_NONE;
850 static gboolean filelist_sort_ascend = TRUE;
853 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
856 if (!filelist_sort_ascend)
863 switch (filelist_sort_method)
868 if (fa->size < fb->size) return -1;
869 if (fa->size > fb->size) return 1;
870 /* fall back to name */
873 if (fa->date < fb->date) return -1;
874 if (fa->date > fb->date) return 1;
875 /* fall back to name */
877 #ifdef HAVE_STRVERSCMP
879 ret = strverscmp(fa->name, fb->name);
880 if (ret != 0) return ret;
887 if (options->file_sort.case_sensitive)
888 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
890 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
892 if (ret != 0) return ret;
894 /* do not return 0 unless the files are really the same
895 file_data_pool ensures that original_path is unique
897 return strcmp(fa->original_path, fb->original_path);
900 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
902 filelist_sort_method = method;
903 filelist_sort_ascend = ascend;
904 return filelist_sort_compare_filedata(fa, fb);
907 static gint filelist_sort_file_cb(gpointer a, gpointer b)
909 return filelist_sort_compare_filedata(a, b);
912 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
914 filelist_sort_method = method;
915 filelist_sort_ascend = ascend;
916 return g_list_sort(list, cb);
919 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
921 filelist_sort_method = method;
922 filelist_sort_ascend = ascend;
923 return g_list_insert_sorted(list, data, cb);
926 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
928 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
931 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
933 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
937 static GList *filelist_filter_out_sidecars(GList *flist)
940 GList *flist_filtered = NULL;
944 FileData *fd = work->data;
947 if (fd->parent) /* remove fd's that are children */
950 flist_filtered = g_list_prepend(flist_filtered, fd);
954 return flist_filtered;
957 static gboolean is_hidden_file(const gchar *name)
959 if (name[0] != '.') return FALSE;
960 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
964 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
971 gint (*stat_func)(const gchar *path, struct stat *buf);
972 GHashTable *basename_hash = NULL;
974 g_assert(files || dirs);
976 if (files) *files = NULL;
977 if (dirs) *dirs = NULL;
979 pathl = path_from_utf8(dir_path);
980 if (!pathl) return FALSE;
989 if (files) basename_hash = file_data_basename_hash_new();
996 while ((dir = readdir(dp)) != NULL)
998 struct stat ent_sbuf;
999 const gchar *name = dir->d_name;
1002 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1005 filepath = g_build_filename(pathl, name, NULL);
1006 if (stat_func(filepath, &ent_sbuf) >= 0)
1008 if (S_ISDIR(ent_sbuf.st_mode))
1010 /* we ignore the .thumbnails dir for cleanliness */
1012 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1013 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1014 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1015 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1017 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE, NULL));
1022 if (files && filter_name_exists(name))
1024 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, FALSE, basename_hash));
1030 if (errno == EOVERFLOW)
1032 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1041 if (basename_hash) file_data_basename_hash_free(basename_hash);
1043 if (dirs) *dirs = dlist;
1044 if (files) *files = filelist_filter_out_sidecars(flist);
1049 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1051 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1054 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1056 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1059 FileData *file_data_new_simple(const gchar *path_utf8)
1066 if (!stat_utf8(path_utf8, &st))
1072 if (S_ISDIR(st.st_mode))
1073 return file_data_new(path_utf8, &st, TRUE, NULL);
1075 dir = remove_level_from_path(path_utf8);
1077 filelist_read_real(dir, &files, NULL, TRUE);
1079 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1083 filelist_free(files);
1088 FileData *file_data_new_no_grouping(const gchar *path_utf8)
1092 if (!stat_utf8(path_utf8, &st))
1098 return file_data_new(path_utf8, &st, TRUE, NULL);
1101 FileData *file_data_new_dir(const gchar *path_utf8)
1105 if (!stat_utf8(path_utf8, &st))
1111 g_assert(S_ISDIR(st.st_mode));
1112 return file_data_new(path_utf8, &st, TRUE, NULL);
1115 void filelist_free(GList *list)
1122 file_data_unref((FileData *)work->data);
1130 GList *filelist_copy(GList *list)
1132 GList *new_list = NULL;
1143 new_list = g_list_prepend(new_list, file_data_ref(fd));
1146 return g_list_reverse(new_list);
1149 GList *filelist_from_path_list(GList *list)
1151 GList *new_list = NULL;
1162 new_list = g_list_prepend(new_list, file_data_new_simple(path));
1165 return g_list_reverse(new_list);
1168 GList *filelist_to_path_list(GList *list)
1170 GList *new_list = NULL;
1181 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1184 return g_list_reverse(new_list);
1187 GList *filelist_filter(GList *list, gboolean is_dir_list)
1191 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1196 FileData *fd = (FileData *)(work->data);
1197 const gchar *name = fd->name;
1199 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1200 (!is_dir_list && !filter_name_exists(name)) ||
1201 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1202 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1206 list = g_list_remove_link(list, link);
1207 file_data_unref(fd);
1218 *-----------------------------------------------------------------------------
1219 * filelist recursive
1220 *-----------------------------------------------------------------------------
1223 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1225 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1228 GList *filelist_sort_path(GList *list)
1230 return g_list_sort(list, filelist_sort_path_cb);
1233 static void filelist_recursive_append(GList **list, GList *dirs)
1240 FileData *fd = (FileData *)(work->data);
1244 if (filelist_read(fd, &f, &d))
1246 f = filelist_filter(f, FALSE);
1247 f = filelist_sort_path(f);
1248 *list = g_list_concat(*list, f);
1250 d = filelist_filter(d, TRUE);
1251 d = filelist_sort_path(d);
1252 filelist_recursive_append(list, d);
1260 GList *filelist_recursive(FileData *dir_fd)
1265 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1266 list = filelist_filter(list, FALSE);
1267 list = filelist_sort_path(list);
1269 d = filelist_filter(d, TRUE);
1270 d = filelist_sort_path(d);
1271 filelist_recursive_append(&list, d);
1279 * marks and orientation
1282 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1283 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1284 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1285 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1287 gboolean file_data_get_mark(FileData *fd, gint n)
1289 gboolean valid = (fd->valid_marks & (1 << n));
1291 if (file_data_get_mark_func[n] && !valid)
1293 guint old = fd->marks;
1294 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1296 if (!value != !(fd->marks & (1 << n)))
1298 fd->marks = fd->marks ^ (1 << n);
1301 fd->valid_marks |= (1 << n);
1302 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1304 file_data_unref(fd);
1306 else if (!old && fd->marks)
1312 return !!(fd->marks & (1 << n));
1315 guint file_data_get_marks(FileData *fd)
1318 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1322 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1325 if (!value == !file_data_get_mark(fd, n)) return;
1327 if (file_data_set_mark_func[n])
1329 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1334 fd->marks = fd->marks ^ (1 << n);
1336 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1338 file_data_unref(fd);
1340 else if (!old && fd->marks)
1345 file_data_increment_version(fd);
1346 file_data_send_notification(fd, NOTIFY_MARKS);
1349 gboolean file_data_filter_marks(FileData *fd, guint filter)
1352 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1353 return ((fd->marks & filter) == filter);
1356 GList *file_data_filter_marks_list(GList *list, guint filter)
1363 FileData *fd = work->data;
1367 if (!file_data_filter_marks(fd, filter))
1369 list = g_list_remove_link(list, link);
1370 file_data_unref(fd);
1378 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1380 FileData *fd = value;
1381 file_data_increment_version(fd);
1382 file_data_send_notification(fd, NOTIFY_MARKS);
1385 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1387 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1389 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1391 file_data_get_mark_func[n] = get_mark_func;
1392 file_data_set_mark_func[n] = set_mark_func;
1393 file_data_mark_func_data[n] = data;
1394 file_data_destroy_mark_func[n] = notify;
1398 /* this effectively changes all known files */
1399 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1405 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1407 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1408 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1409 if (data) *data = file_data_mark_func_data[n];
1412 gint file_data_get_user_orientation(FileData *fd)
1414 return fd->user_orientation;
1417 void file_data_set_user_orientation(FileData *fd, gint value)
1419 if (fd->user_orientation == value) return;
1421 fd->user_orientation = value;
1422 file_data_increment_version(fd);
1423 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1428 * file_data - operates on the given fd
1429 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1433 /* return list of sidecar file extensions in a string */
1434 gchar *file_data_sc_list_to_string(FileData *fd)
1437 GString *result = g_string_new("");
1439 work = fd->sidecar_files;
1442 FileData *sfd = work->data;
1444 result = g_string_append(result, "+ ");
1445 result = g_string_append(result, sfd->extension);
1447 if (work) result = g_string_append_c(result, ' ');
1450 return g_string_free(result, FALSE);
1456 * add FileDataChangeInfo (see typedefs.h) for the given operation
1457 * uses file_data_add_change_info
1459 * fails if the fd->change already exists - change operations can't run in parallel
1460 * fd->change_info works as a lock
1462 * dest can be NULL - in this case the current name is used for now, it will
1467 FileDataChangeInfo types:
1469 MOVE - path is changed, name may be changed too
1470 RENAME - path remains unchanged, name is changed
1471 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1472 sidecar names are changed too, extensions are not changed
1474 UPDATE - file size, date or grouping has been changed
1477 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1479 FileDataChangeInfo *fdci;
1481 if (fd->change) return FALSE;
1483 fdci = g_new0(FileDataChangeInfo, 1);
1488 fdci->source = g_strdup(src);
1490 fdci->source = g_strdup(fd->path);
1493 fdci->dest = g_strdup(dest);
1500 static void file_data_planned_change_remove(FileData *fd)
1502 if (file_data_planned_change_hash &&
1503 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1505 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1507 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1508 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1509 file_data_unref(fd);
1510 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1512 g_hash_table_destroy(file_data_planned_change_hash);
1513 file_data_planned_change_hash = NULL;
1514 DEBUG_1("planned change: empty");
1521 void file_data_free_ci(FileData *fd)
1523 FileDataChangeInfo *fdci = fd->change;
1527 file_data_planned_change_remove(fd);
1529 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1531 g_free(fdci->source);
1539 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1541 FileDataChangeInfo *fdci = fd->change;
1543 fdci->regroup_when_finished = enable;
1546 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1550 if (fd->parent) fd = fd->parent;
1552 if (fd->change) return FALSE;
1554 work = fd->sidecar_files;
1557 FileData *sfd = work->data;
1559 if (sfd->change) return FALSE;
1563 file_data_add_ci(fd, type, NULL, NULL);
1565 work = fd->sidecar_files;
1568 FileData *sfd = work->data;
1570 file_data_add_ci(sfd, type, NULL, NULL);
1577 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1581 if (fd->parent) fd = fd->parent;
1583 if (!fd->change || fd->change->type != type) return FALSE;
1585 work = fd->sidecar_files;
1588 FileData *sfd = work->data;
1590 if (!sfd->change || sfd->change->type != type) return FALSE;
1598 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1600 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1601 file_data_sc_update_ci_copy(fd, dest_path);
1605 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1607 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1608 file_data_sc_update_ci_move(fd, dest_path);
1612 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1614 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1615 file_data_sc_update_ci_rename(fd, dest_path);
1619 gboolean file_data_sc_add_ci_delete(FileData *fd)
1621 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1624 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1626 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1627 file_data_sc_update_ci_unspecified(fd, dest_path);
1631 gboolean file_data_add_ci_write_metadata(FileData *fd)
1633 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1636 void file_data_sc_free_ci(FileData *fd)
1640 if (fd->parent) fd = fd->parent;
1642 file_data_free_ci(fd);
1644 work = fd->sidecar_files;
1647 FileData *sfd = work->data;
1649 file_data_free_ci(sfd);
1654 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1657 gboolean ret = TRUE;
1662 FileData *fd = work->data;
1664 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1671 static void file_data_sc_revert_ci_list(GList *fd_list)
1678 FileData *fd = work->data;
1680 file_data_sc_free_ci(fd);
1685 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1692 FileData *fd = work->data;
1694 if (!func(fd, dest))
1696 file_data_sc_revert_ci_list(work->prev);
1705 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1707 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1710 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1712 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1715 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1717 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1720 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1722 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1725 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1728 gboolean ret = TRUE;
1733 FileData *fd = work->data;
1735 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1742 void file_data_free_ci_list(GList *fd_list)
1749 FileData *fd = work->data;
1751 file_data_free_ci(fd);
1756 void file_data_sc_free_ci_list(GList *fd_list)
1763 FileData *fd = work->data;
1765 file_data_sc_free_ci(fd);
1771 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1772 * fails if fd->change does not exist or the change type does not match
1775 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1777 FileDataChangeType type = fd->change->type;
1779 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1783 if (!file_data_planned_change_hash)
1784 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1786 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1788 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1789 g_hash_table_remove(file_data_planned_change_hash, old_path);
1790 file_data_unref(fd);
1793 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1798 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1799 g_hash_table_remove(file_data_planned_change_hash, new_path);
1800 file_data_unref(ofd);
1803 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1805 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1810 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1812 gchar *old_path = fd->change->dest;
1814 fd->change->dest = g_strdup(dest_path);
1815 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1819 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1821 const gchar *extension = extension_from_path(fd->change->source);
1822 gchar *base = remove_extension_from_path(dest_path);
1823 gchar *old_path = fd->change->dest;
1825 fd->change->dest = g_strconcat(base, extension, NULL);
1826 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1832 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1835 gchar *dest_path_full = NULL;
1837 if (fd->parent) fd = fd->parent;
1841 dest_path = fd->path;
1843 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1845 gchar *dir = remove_level_from_path(fd->path);
1847 dest_path_full = g_build_filename(dir, dest_path, NULL);
1849 dest_path = dest_path_full;
1851 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1853 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1854 dest_path = dest_path_full;
1857 file_data_update_ci_dest(fd, dest_path);
1859 work = fd->sidecar_files;
1862 FileData *sfd = work->data;
1864 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1868 g_free(dest_path_full);
1871 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1873 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1874 file_data_sc_update_ci(fd, dest_path);
1878 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1880 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1883 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1885 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1888 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1890 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1893 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1895 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1898 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1900 gboolean (*func)(FileData *, const gchar *))
1903 gboolean ret = TRUE;
1908 FileData *fd = work->data;
1910 if (!func(fd, dest)) ret = FALSE;
1917 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1919 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1922 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1924 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1927 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1929 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1934 * verify source and dest paths - dest image exists, etc.
1935 * it should detect all possible problems with the planned operation
1938 gint file_data_verify_ci(FileData *fd)
1940 gint ret = CHANGE_OK;
1945 DEBUG_1("Change checked: no change info: %s", fd->path);
1949 if (!isname(fd->path))
1951 /* this probably should not happen */
1952 ret |= CHANGE_NO_SRC;
1953 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1957 dir = remove_level_from_path(fd->path);
1959 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1960 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
1961 fd->change->type != FILEDATA_CHANGE_RENAME &&
1962 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1965 ret |= CHANGE_WARN_UNSAVED_META;
1966 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
1969 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1970 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1971 !access_file(fd->path, R_OK))
1973 ret |= CHANGE_NO_READ_PERM;
1974 DEBUG_1("Change checked: no read permission: %s", fd->path);
1976 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1977 !access_file(dir, W_OK))
1979 ret |= CHANGE_NO_WRITE_PERM_DIR;
1980 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1982 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1983 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1984 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1985 !access_file(fd->path, W_OK))
1987 ret |= CHANGE_WARN_NO_WRITE_PERM;
1988 DEBUG_1("Change checked: no write permission: %s", fd->path);
1990 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1991 - that means that there are no hard errors and warnings can be disabled
1992 - the destination is determined during the check
1994 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1996 /* determine destination file */
1997 gboolean have_dest = FALSE;
1998 gchar *dest_dir = NULL;
2000 if (options->metadata.save_in_image_file)
2002 if (file_data_can_write_directly(fd))
2004 /* we can write the file directly */
2005 if (access_file(fd->path, W_OK))
2011 if (options->metadata.warn_on_write_problems)
2013 ret |= CHANGE_WARN_NO_WRITE_PERM;
2014 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2018 else if (file_data_can_write_sidecar(fd))
2020 /* we can write sidecar */
2021 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2022 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2024 file_data_update_ci_dest(fd, sidecar);
2029 if (options->metadata.warn_on_write_problems)
2031 ret |= CHANGE_WARN_NO_WRITE_PERM;
2032 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2041 /* write private metadata file under ~/.geeqie */
2043 /* If an existing metadata file exists, we will try writing to
2044 * it's location regardless of the user's preference.
2046 gchar *metadata_path = NULL;
2048 /* but ignore XMP if we are not able to write it */
2049 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2051 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2053 if (metadata_path && !access_file(metadata_path, W_OK))
2055 g_free(metadata_path);
2056 metadata_path = NULL;
2063 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2064 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2066 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2068 metadata_path = g_build_filename(dest_dir, filename, NULL);
2072 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2074 file_data_update_ci_dest(fd, metadata_path);
2079 ret |= CHANGE_NO_WRITE_PERM_DEST;
2080 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2082 g_free(metadata_path);
2087 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2092 same = (strcmp(fd->path, fd->change->dest) == 0);
2096 const gchar *dest_ext = extension_from_path(fd->change->dest);
2097 if (!dest_ext) dest_ext = "";
2099 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2101 ret |= CHANGE_WARN_CHANGED_EXT;
2102 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2107 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2109 ret |= CHANGE_WARN_SAME;
2110 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2114 dest_dir = remove_level_from_path(fd->change->dest);
2116 if (!isdir(dest_dir))
2118 ret |= CHANGE_NO_DEST_DIR;
2119 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2121 else if (!access_file(dest_dir, W_OK))
2123 ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
2124 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2128 if (isfile(fd->change->dest))
2130 if (!access_file(fd->change->dest, W_OK))
2132 ret |= CHANGE_NO_WRITE_PERM_DEST;
2133 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2137 ret |= CHANGE_WARN_DEST_EXISTS;
2138 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2141 else if (isdir(fd->change->dest))
2143 ret |= CHANGE_DEST_EXISTS;
2144 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2151 fd->change->error = ret;
2152 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2159 gint file_data_sc_verify_ci(FileData *fd)
2164 ret = file_data_verify_ci(fd);
2166 work = fd->sidecar_files;
2169 FileData *sfd = work->data;
2171 ret |= file_data_verify_ci(sfd);
2178 gchar *file_data_get_error_string(gint error)
2180 GString *result = g_string_new("");
2182 if (error & CHANGE_NO_SRC)
2184 if (result->len > 0) g_string_append(result, ", ");
2185 g_string_append(result, _("file or directory does not exist"));
2188 if (error & CHANGE_DEST_EXISTS)
2190 if (result->len > 0) g_string_append(result, ", ");
2191 g_string_append(result, _("destination already exists"));
2194 if (error & CHANGE_NO_WRITE_PERM_DEST)
2196 if (result->len > 0) g_string_append(result, ", ");
2197 g_string_append(result, _("destination can't be overwritten"));
2200 if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
2202 if (result->len > 0) g_string_append(result, ", ");
2203 g_string_append(result, _("destination directory is not writable"));
2206 if (error & CHANGE_NO_DEST_DIR)
2208 if (result->len > 0) g_string_append(result, ", ");
2209 g_string_append(result, _("destination directory does not exist"));
2212 if (error & CHANGE_NO_WRITE_PERM_DIR)
2214 if (result->len > 0) g_string_append(result, ", ");
2215 g_string_append(result, _("source directory is not writable"));
2218 if (error & CHANGE_NO_READ_PERM)
2220 if (result->len > 0) g_string_append(result, ", ");
2221 g_string_append(result, _("no read permission"));
2224 if (error & CHANGE_WARN_NO_WRITE_PERM)
2226 if (result->len > 0) g_string_append(result, ", ");
2227 g_string_append(result, _("file is readonly"));
2230 if (error & CHANGE_WARN_DEST_EXISTS)
2232 if (result->len > 0) g_string_append(result, ", ");
2233 g_string_append(result, _("destination already exists and will be overwritten"));
2236 if (error & CHANGE_WARN_SAME)
2238 if (result->len > 0) g_string_append(result, ", ");
2239 g_string_append(result, _("source and destination are the same"));
2242 if (error & CHANGE_WARN_CHANGED_EXT)
2244 if (result->len > 0) g_string_append(result, ", ");
2245 g_string_append(result, _("source and destination have different extension"));
2248 if (error & CHANGE_WARN_UNSAVED_META)
2250 if (result->len > 0) g_string_append(result, ", ");
2251 g_string_append(result, _("there are unsaved metadata changes for the file"));
2254 return g_string_free(result, FALSE);
2257 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2260 gint all_errors = 0;
2261 gint common_errors = ~0;
2266 if (!list) return 0;
2268 num = g_list_length(list);
2269 errors = g_new(int, num);
2280 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2281 all_errors |= error;
2282 common_errors &= error;
2289 if (desc && all_errors)
2292 GString *result = g_string_new("");
2296 gchar *str = file_data_get_error_string(common_errors);
2297 g_string_append(result, str);
2298 g_string_append(result, "\n");
2312 error = errors[i] & ~common_errors;
2316 gchar *str = file_data_get_error_string(error);
2317 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2322 *desc = g_string_free(result, FALSE);
2331 * perform the change described by FileFataChangeInfo
2332 * it is used for internal operations,
2333 * this function actually operates with files on the filesystem
2334 * it should implement safe delete
2337 static gboolean file_data_perform_move(FileData *fd)
2339 g_assert(!strcmp(fd->change->source, fd->path));
2340 return move_file(fd->change->source, fd->change->dest);
2343 static gboolean file_data_perform_copy(FileData *fd)
2345 g_assert(!strcmp(fd->change->source, fd->path));
2346 return copy_file(fd->change->source, fd->change->dest);
2349 static gboolean file_data_perform_delete(FileData *fd)
2351 if (isdir(fd->path) && !islink(fd->path))
2352 return rmdir_utf8(fd->path);
2354 if (options->file_ops.safe_delete_enable)
2355 return file_util_safe_unlink(fd->path);
2357 return unlink_file(fd->path);
2360 gboolean file_data_perform_ci(FileData *fd)
2362 FileDataChangeType type = fd->change->type;
2366 case FILEDATA_CHANGE_MOVE:
2367 return file_data_perform_move(fd);
2368 case FILEDATA_CHANGE_COPY:
2369 return file_data_perform_copy(fd);
2370 case FILEDATA_CHANGE_RENAME:
2371 return file_data_perform_move(fd); /* the same as move */
2372 case FILEDATA_CHANGE_DELETE:
2373 return file_data_perform_delete(fd);
2374 case FILEDATA_CHANGE_WRITE_METADATA:
2375 return metadata_write_perform(fd);
2376 case FILEDATA_CHANGE_UNSPECIFIED:
2377 /* nothing to do here */
2385 gboolean file_data_sc_perform_ci(FileData *fd)
2388 gboolean ret = TRUE;
2389 FileDataChangeType type = fd->change->type;
2391 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2393 work = fd->sidecar_files;
2396 FileData *sfd = work->data;
2398 if (!file_data_perform_ci(sfd)) ret = FALSE;
2402 if (!file_data_perform_ci(fd)) ret = FALSE;
2408 * updates FileData structure according to FileDataChangeInfo
2411 gboolean file_data_apply_ci(FileData *fd)
2413 FileDataChangeType type = fd->change->type;
2416 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2418 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2419 file_data_planned_change_remove(fd);
2421 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2423 /* this change overwrites another file which is already known to other modules
2424 renaming fd would create duplicate FileData structure
2425 the best thing we can do is nothing
2426 FIXME: maybe we could copy stuff like marks
2428 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2432 file_data_set_path(fd, fd->change->dest);
2435 file_data_increment_version(fd);
2436 file_data_send_notification(fd, NOTIFY_CHANGE);
2441 gboolean file_data_sc_apply_ci(FileData *fd)
2444 FileDataChangeType type = fd->change->type;
2446 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2448 work = fd->sidecar_files;
2451 FileData *sfd = work->data;
2453 file_data_apply_ci(sfd);
2457 file_data_apply_ci(fd);
2462 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2465 if (fd->parent) fd = fd->parent;
2466 if (!g_list_find(list, fd)) return FALSE;
2468 work = fd->sidecar_files;
2471 if (!g_list_find(list, work->data)) return FALSE;
2478 static gboolean file_data_list_dump(GList *list)
2480 GList *work, *work2;
2485 FileData *fd = work->data;
2486 printf("%s\n", fd->name);
2487 work2 = fd->sidecar_files;
2490 FileData *fd = work2->data;
2491 printf(" %s\n", fd->name);
2492 work2 = work2->next;
2500 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2505 /* change partial groups to independent files */
2510 FileData *fd = work->data;
2513 if (!file_data_list_contains_whole_group(list, fd))
2515 file_data_disable_grouping(fd, TRUE);
2518 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2524 /* remove sidecars from the list,
2525 they can be still acessed via main_fd->sidecar_files */
2529 FileData *fd = work->data;
2533 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2535 out = g_list_prepend(out, file_data_ref(fd));
2539 filelist_free(list);
2540 out = g_list_reverse(out);
2550 * notify other modules about the change described by FileDataChangeInfo
2553 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2554 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2555 implementation in view_file_list.c */
2560 typedef struct _NotifyData NotifyData;
2562 struct _NotifyData {
2563 FileDataNotifyFunc func;
2565 NotifyPriority priority;
2568 static GList *notify_func_list = NULL;
2570 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2572 NotifyData *nda = (NotifyData *)a;
2573 NotifyData *ndb = (NotifyData *)b;
2575 if (nda->priority < ndb->priority) return -1;
2576 if (nda->priority > ndb->priority) return 1;
2580 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2583 GList *work = notify_func_list;
2587 NotifyData *nd = (NotifyData *)work->data;
2589 if (nd->func == func && nd->data == data)
2591 g_warning("Notify func already registered");
2597 nd = g_new(NotifyData, 1);
2600 nd->priority = priority;
2602 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2603 DEBUG_2("Notify func registered: %p", nd);
2608 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2610 GList *work = notify_func_list;
2614 NotifyData *nd = (NotifyData *)work->data;
2616 if (nd->func == func && nd->data == data)
2618 notify_func_list = g_list_delete_link(notify_func_list, work);
2620 DEBUG_2("Notify func unregistered: %p", nd);
2626 g_warning("Notify func not found");
2631 void file_data_send_notification(FileData *fd, NotifyType type)
2633 GList *work = notify_func_list;
2637 NotifyData *nd = (NotifyData *)work->data;
2639 nd->func(fd, type, nd->data);
2644 static GHashTable *file_data_monitor_pool = NULL;
2645 static guint realtime_monitor_id = 0; /* event source id */
2647 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2651 file_data_check_changed_files(fd);
2653 DEBUG_1("monitor %s", fd->path);
2656 static gboolean realtime_monitor_cb(gpointer data)
2658 if (!options->update_on_time_change) return TRUE;
2659 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2663 gboolean file_data_register_real_time_monitor(FileData *fd)
2669 if (!file_data_monitor_pool)
2670 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2672 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2674 DEBUG_1("Register realtime %d %s", count, fd->path);
2677 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2679 if (!realtime_monitor_id)
2681 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2687 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2691 g_assert(file_data_monitor_pool);
2693 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2695 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2697 g_assert(count > 0);
2702 g_hash_table_remove(file_data_monitor_pool, fd);
2704 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2706 file_data_unref(fd);
2708 if (g_hash_table_size(file_data_monitor_pool) == 0)
2710 g_source_remove(realtime_monitor_id);
2711 realtime_monitor_id = 0;
2717 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */