4 * Copyright (C) 2008 - 2012 The Geeqie Team
8 * This software is released under the GNU General Public License (GNU GPL).
9 * Please read the included file COPYING for more information.
10 * This software comes with no warranty of any kind, use at your own risk!
17 #include "filefilter.h"
19 #include "thumb_standard.h"
20 #include "ui_fileops.h"
23 #include "histogram.h"
29 static GHashTable *file_data_pool = NULL;
30 static GHashTable *file_data_planned_change_hash = NULL;
32 static gint sidecar_file_priority(const gchar *extension);
33 static void file_data_check_sidecars(const GList *basename_list);
34 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
37 static SortType filelist_sort_method = SORT_NONE;
38 static gboolean filelist_sort_ascend = TRUE;
41 *-----------------------------------------------------------------------------
42 * text conversion utils
43 *-----------------------------------------------------------------------------
46 gchar *text_from_size(gint64 size)
52 /* what I would like to use is printf("%'d", size)
53 * BUT: not supported on every libc :(
57 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
58 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
62 a = g_strdup_printf("%d", (guint)size);
68 b = g_new(gchar, l + n + 1);
93 gchar *text_from_size_abrev(gint64 size)
95 if (size < (gint64)1024)
97 return g_strdup_printf(_("%d bytes"), (gint)size);
99 if (size < (gint64)1048576)
101 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
103 if (size < (gint64)1073741824)
105 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
108 /* to avoid overflowing the gdouble, do division in two steps */
110 return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
113 /* note: returned string is valid until next call to text_from_time() */
114 const gchar *text_from_time(time_t t)
116 static gchar *ret = NULL;
120 GError *error = NULL;
122 btime = localtime(&t);
124 /* the %x warning about 2 digit years is not an error */
125 buflen = strftime(buf, sizeof(buf), "%x %X", btime);
126 if (buflen < 1) return "";
129 ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
132 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
141 *-----------------------------------------------------------------------------
142 * changed files detection and notification
143 *-----------------------------------------------------------------------------
146 void file_data_increment_version(FileData *fd)
152 fd->parent->version++;
153 fd->parent->valid_marks = 0;
157 static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st)
159 if (fd->size != st->st_size ||
160 fd->date != st->st_mtime)
162 fd->size = st->st_size;
163 fd->date = st->st_mtime;
164 fd->mode = st->st_mode;
165 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
166 fd->thumb_pixbuf = NULL;
167 file_data_increment_version(fd);
168 file_data_send_notification(fd, NOTIFY_REREAD);
174 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
176 gboolean ret = FALSE;
179 ret = file_data_check_changed_single_file(fd, st);
181 work = fd->sidecar_files;
184 FileData *sfd = work->data;
188 if (!stat_utf8(sfd->path, &st))
193 file_data_disconnect_sidecar_file(fd, sfd);
195 file_data_increment_version(sfd);
196 file_data_send_notification(sfd, NOTIFY_REREAD);
197 file_data_unref(sfd);
201 ret |= file_data_check_changed_files_recursive(sfd, &st);
207 gboolean file_data_check_changed_files(FileData *fd)
209 gboolean ret = FALSE;
212 if (fd->parent) fd = fd->parent;
214 if (!stat_utf8(fd->path, &st))
218 FileData *sfd = NULL;
220 /* parent is missing, we have to rebuild whole group */
225 /* file_data_disconnect_sidecar_file might delete the file,
226 we have to keep the reference to prevent this */
227 sidecars = filelist_copy(fd->sidecar_files);
235 file_data_disconnect_sidecar_file(fd, sfd);
237 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
238 /* now we can release the sidecars */
239 filelist_free(sidecars);
240 file_data_increment_version(fd);
241 file_data_send_notification(fd, NOTIFY_REREAD);
246 ret |= file_data_check_changed_files_recursive(fd, &st);
253 *-----------------------------------------------------------------------------
254 * file name, extension, sorting, ...
255 *-----------------------------------------------------------------------------
258 static void file_data_set_collate_keys(FileData *fd)
260 gchar *caseless_name;
263 valid_name = g_filename_display_name(fd->name);
264 caseless_name = g_utf8_casefold(valid_name, -1);
266 g_free(fd->collate_key_name);
267 g_free(fd->collate_key_name_nocase);
269 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
270 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
273 g_free(caseless_name);
276 static void file_data_set_path(FileData *fd, const gchar *path)
278 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
279 g_assert(file_data_pool);
283 if (fd->original_path)
285 g_hash_table_remove(file_data_pool, fd->original_path);
286 g_free(fd->original_path);
289 g_assert(!g_hash_table_lookup(file_data_pool, path));
291 fd->original_path = g_strdup(path);
292 g_hash_table_insert(file_data_pool, fd->original_path, fd);
294 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
296 fd->path = g_strdup(path);
298 fd->extension = fd->name + 1;
299 file_data_set_collate_keys(fd);
303 fd->path = g_strdup(path);
304 fd->name = filename_from_path(fd->path);
306 if (strcmp(fd->name, "..") == 0)
308 gchar *dir = remove_level_from_path(path);
310 fd->path = remove_level_from_path(dir);
313 fd->extension = fd->name + 2;
314 file_data_set_collate_keys(fd);
317 else if (strcmp(fd->name, ".") == 0)
320 fd->path = remove_level_from_path(path);
322 fd->extension = fd->name + 1;
323 file_data_set_collate_keys(fd);
327 fd->extension = registered_extension_from_path(fd->path);
328 if (fd->extension == NULL)
330 fd->extension = fd->name + strlen(fd->name);
333 fd->sidecar_priority = sidecar_file_priority(fd->extension);
334 file_data_set_collate_keys(fd);
338 *-----------------------------------------------------------------------------
339 * create or reuse Filedata
340 *-----------------------------------------------------------------------------
343 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
347 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
349 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
352 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
354 fd = g_hash_table_lookup(file_data_pool, path_utf8);
360 if (!fd && file_data_planned_change_hash)
362 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
365 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
367 file_data_apply_ci(fd);
375 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
378 changed = file_data_check_changed_single_file(fd, st);
380 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
385 fd = g_new0(FileData, 1);
387 fd->size = st->st_size;
388 fd->date = st->st_mtime;
389 fd->mode = st->st_mode;
391 fd->magick = FD_MAGICK;
393 if (disable_sidecars) fd->disable_grouping = TRUE;
395 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
400 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
402 gchar *path_utf8 = path_to_utf8(path);
403 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
409 void init_exif_time_data(GList *files)
412 DEBUG_1("%s init_exif_time_data: ...", get_exec_time());
424 void read_exif_time_data(FileData *file)
426 if (file->exifdate > 0)
428 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
432 file->exif = exif_read_fd(file);
436 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
437 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), file, file->path);
442 uint year, month, day, hour, min, sec;
444 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
445 time_str.tm_year = year - 1900;
446 time_str.tm_mon = month - 1;
447 time_str.tm_mday = day;
448 time_str.tm_hour = hour;
449 time_str.tm_min = min;
450 time_str.tm_sec = sec;
451 time_str.tm_isdst = 0;
453 file->exifdate = mktime(&time_str);
459 void set_exif_time_data(GList *files)
461 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
465 FileData *file = files->data;
467 read_exif_time_data(file);
472 FileData *file_data_new_no_grouping(const gchar *path_utf8)
476 if (!stat_utf8(path_utf8, &st))
482 return file_data_new(path_utf8, &st, TRUE);
485 FileData *file_data_new_dir(const gchar *path_utf8)
489 if (!stat_utf8(path_utf8, &st))
495 /* dir or non-existing yet */
496 g_assert(S_ISDIR(st.st_mode));
498 return file_data_new(path_utf8, &st, TRUE);
502 *-----------------------------------------------------------------------------
504 *-----------------------------------------------------------------------------
507 #ifdef DEBUG_FILEDATA
508 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
510 FileData *file_data_ref(FileData *fd)
513 if (fd == NULL) return NULL;
514 if (fd->magick != FD_MAGICK)
515 #ifdef DEBUG_FILEDATA
516 DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
518 DEBUG_0("fd magick mismatch fd=%p", fd);
520 g_assert(fd->magick == FD_MAGICK);
523 #ifdef DEBUG_FILEDATA
524 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", fd, fd->ref, fd->path, file, line);
526 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
531 static void file_data_free(FileData *fd)
533 g_assert(fd->magick == FD_MAGICK);
534 g_assert(fd->ref == 0);
535 g_assert(!fd->locked);
537 metadata_cache_free(fd);
538 g_hash_table_remove(file_data_pool, fd->original_path);
541 g_free(fd->original_path);
542 g_free(fd->collate_key_name);
543 g_free(fd->collate_key_name_nocase);
544 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
545 histmap_free(fd->histmap);
547 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
549 file_data_change_info_free(NULL, fd);
554 * \brief Checks if the FileData is referenced
556 * Checks the refcount and whether the FileData is locked.
558 static gboolean file_data_check_has_ref(FileData *fd)
560 return fd->ref > 0 || fd->locked;
564 * \brief Consider freeing a FileData.
566 * This function will free a FileData and its children provided that neither its parent nor it has
567 * a positive refcount, and provided that neither is locked.
569 static void file_data_consider_free(FileData *fd)
572 FileData *parent = fd->parent ? fd->parent : fd;
574 g_assert(fd->magick == FD_MAGICK);
575 if (file_data_check_has_ref(fd)) return;
576 if (file_data_check_has_ref(parent)) return;
578 work = parent->sidecar_files;
581 FileData *sfd = work->data;
582 if (file_data_check_has_ref(sfd)) return;
586 /* Neither the parent nor the siblings are referenced, so we can free everything */
587 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
588 fd->path, fd->parent ? parent->path : "-");
590 work = parent->sidecar_files;
593 FileData *sfd = work->data;
598 g_list_free(parent->sidecar_files);
599 parent->sidecar_files = NULL;
601 file_data_free(parent);
604 #ifdef DEBUG_FILEDATA
605 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
607 void file_data_unref(FileData *fd)
610 if (fd == NULL) return;
611 if (fd->magick != FD_MAGICK)
612 #ifdef DEBUG_FILEDATA
613 DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
615 DEBUG_0("fd magick mismatch fd=%p", fd);
617 g_assert(fd->magick == FD_MAGICK);
620 #ifdef DEBUG_FILEDATA
621 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", fd, fd->ref, fd->locked, fd->path,
624 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
627 // Free FileData if it's no longer ref'd
628 file_data_consider_free(fd);
632 * \brief Lock the FileData in memory.
634 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
635 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
636 * even if the code would continue to function properly even if the FileData were freed. Code that
637 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
639 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
640 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
642 void file_data_lock(FileData *fd)
644 if (fd == NULL) return;
645 if (fd->magick != FD_MAGICK) DEBUG_0("fd magick mismatch fd=%p", fd);
647 g_assert(fd->magick == FD_MAGICK);
650 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
654 * \brief Reset the maintain-FileData-in-memory lock
656 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
657 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
658 * keeping it from being freed.
660 void file_data_unlock(FileData *fd)
662 if (fd == NULL) return;
663 if (fd->magick != FD_MAGICK) DEBUG_0("fd magick mismatch fd=%p", fd);
665 g_assert(fd->magick == FD_MAGICK);
668 // Free FileData if it's no longer ref'd
669 file_data_consider_free(fd);
673 * \brief Lock all of the FileDatas in the provided list
675 * \see file_data_lock(FileData)
677 void file_data_lock_list(GList *list)
684 FileData *fd = work->data;
691 * \brief Unlock all of the FileDatas in the provided list
693 * \see file_data_unlock(FileData)
695 void file_data_unlock_list(GList *list)
702 FileData *fd = work->data;
704 file_data_unlock(fd);
709 *-----------------------------------------------------------------------------
710 * sidecar file info struct
711 *-----------------------------------------------------------------------------
714 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
716 const FileData *fda = a;
717 const FileData *fdb = b;
719 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
720 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
722 return strcmp(fdb->extension, fda->extension);
726 static gint sidecar_file_priority(const gchar *extension)
731 if (extension == NULL)
734 work = sidecar_ext_get_list();
737 gchar *ext = work->data;
740 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
746 static void file_data_check_sidecars(const GList *basename_list)
748 /* basename_list contains the new group - first is the parent, then sorted sidecars */
749 /* all files in the list have ref count > 0 */
752 GList *s_work, *new_sidecars;
755 if (!basename_list) return;
758 DEBUG_2("basename start");
759 work = basename_list;
762 FileData *fd = work->data;
764 g_assert(fd->magick == FD_MAGICK);
765 DEBUG_2("basename: %p %s", fd, fd->name);
768 g_assert(fd->parent->magick == FD_MAGICK);
769 DEBUG_2(" parent: %p", fd->parent);
771 s_work = fd->sidecar_files;
774 FileData *sfd = s_work->data;
775 s_work = s_work->next;
776 g_assert(sfd->magick == FD_MAGICK);
777 DEBUG_2(" sidecar: %p %s", sfd, sfd->name);
780 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
783 parent_fd = basename_list->data;
785 /* check if the second and next entries of basename_list are already connected
786 as sidecars of the first entry (parent_fd) */
787 work = basename_list->next;
788 s_work = parent_fd->sidecar_files;
790 while (work && s_work)
792 if (work->data != s_work->data) break;
794 s_work = s_work->next;
797 if (!work && !s_work)
799 DEBUG_2("basename no change");
800 return; /* no change in grouping */
803 /* we have to regroup it */
805 /* first, disconnect everything and send notification*/
807 work = basename_list;
810 FileData *fd = work->data;
812 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
816 FileData *old_parent = fd->parent;
817 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
818 file_data_ref(old_parent);
819 file_data_disconnect_sidecar_file(old_parent, fd);
820 file_data_send_notification(old_parent, NOTIFY_REREAD);
821 file_data_unref(old_parent);
824 while (fd->sidecar_files)
826 FileData *sfd = fd->sidecar_files->data;
827 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
829 file_data_disconnect_sidecar_file(fd, sfd);
830 file_data_send_notification(sfd, NOTIFY_REREAD);
831 file_data_unref(sfd);
833 file_data_send_notification(fd, NOTIFY_GROUPING);
835 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
838 /* now we can form the new group */
839 work = basename_list->next;
843 FileData *sfd = work->data;
844 g_assert(sfd->magick == FD_MAGICK);
845 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
846 sfd->parent = parent_fd;
847 new_sidecars = g_list_prepend(new_sidecars, sfd);
850 g_assert(parent_fd->sidecar_files == NULL);
851 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
852 DEBUG_1("basename group changed for %s", parent_fd->path);
856 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
858 g_assert(target->magick == FD_MAGICK);
859 g_assert(sfd->magick == FD_MAGICK);
860 g_assert(g_list_find(target->sidecar_files, sfd));
862 file_data_ref(target);
865 g_assert(sfd->parent == target);
867 file_data_increment_version(sfd); /* increments both sfd and target */
869 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
872 file_data_unref(target);
873 file_data_unref(sfd);
876 /* disables / enables grouping for particular file, sends UPDATE notification */
877 void file_data_disable_grouping(FileData *fd, gboolean disable)
879 if (!fd->disable_grouping == !disable) return;
881 fd->disable_grouping = !!disable;
887 FileData *parent = file_data_ref(fd->parent);
888 file_data_disconnect_sidecar_file(parent, fd);
889 file_data_send_notification(parent, NOTIFY_GROUPING);
890 file_data_unref(parent);
892 else if (fd->sidecar_files)
894 GList *sidecar_files = filelist_copy(fd->sidecar_files);
895 GList *work = sidecar_files;
898 FileData *sfd = work->data;
900 file_data_disconnect_sidecar_file(fd, sfd);
901 file_data_send_notification(sfd, NOTIFY_GROUPING);
903 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
904 filelist_free(sidecar_files);
908 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
913 file_data_increment_version(fd);
914 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
916 file_data_send_notification(fd, NOTIFY_GROUPING);
919 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
926 FileData *fd = work->data;
928 file_data_disable_grouping(fd, disable);
936 *-----------------------------------------------------------------------------
938 *-----------------------------------------------------------------------------
942 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
945 if (!filelist_sort_ascend)
952 switch (filelist_sort_method)
957 if (fa->size < fb->size) return -1;
958 if (fa->size > fb->size) return 1;
959 /* fall back to name */
962 if (fa->date < fb->date) return -1;
963 if (fa->date > fb->date) return 1;
964 /* fall back to name */
967 if (fa->exifdate < fb->exifdate) return -1;
968 if (fa->exifdate > fb->exifdate) return 1;
969 /* fall back to name */
971 #ifdef HAVE_STRVERSCMP
973 ret = strverscmp(fa->name, fb->name);
974 if (ret != 0) return ret;
981 if (options->file_sort.case_sensitive)
982 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
984 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
986 if (ret != 0) return ret;
988 /* do not return 0 unless the files are really the same
989 file_data_pool ensures that original_path is unique
991 return strcmp(fa->original_path, fb->original_path);
994 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
996 filelist_sort_method = method;
997 filelist_sort_ascend = ascend;
998 return filelist_sort_compare_filedata(fa, fb);
1001 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1003 return filelist_sort_compare_filedata(a, b);
1006 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1008 filelist_sort_method = method;
1009 filelist_sort_ascend = ascend;
1010 return g_list_sort(list, cb);
1013 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1015 filelist_sort_method = method;
1016 filelist_sort_ascend = ascend;
1017 return g_list_insert_sorted(list, data, cb);
1020 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1022 if (method == SORT_EXIFTIME)
1024 set_exif_time_data(list);
1026 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1029 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
1031 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1035 *-----------------------------------------------------------------------------
1036 * basename hash - grouping of sidecars in filelist
1037 *-----------------------------------------------------------------------------
1041 static GHashTable *file_data_basename_hash_new(void)
1043 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1046 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1049 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1051 list = g_hash_table_lookup(basename_hash, basename);
1053 if (!g_list_find(list, fd))
1055 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1056 g_hash_table_insert(basename_hash, basename, list);
1065 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
1067 filelist_free((GList *)value);
1070 static void file_data_basename_hash_free(GHashTable *basename_hash)
1072 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1073 g_hash_table_destroy(basename_hash);
1077 *-----------------------------------------------------------------------------
1078 * handling sidecars in filelist
1079 *-----------------------------------------------------------------------------
1082 static GList *filelist_filter_out_sidecars(GList *flist)
1084 GList *work = flist;
1085 GList *flist_filtered = NULL;
1089 FileData *fd = work->data;
1092 if (fd->parent) /* remove fd's that are children */
1093 file_data_unref(fd);
1095 flist_filtered = g_list_prepend(flist_filtered, fd);
1099 return flist_filtered;
1102 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
1104 GList *basename_list = (GList *)value;
1105 file_data_check_sidecars(basename_list);
1109 static gboolean is_hidden_file(const gchar *name)
1111 if (name[0] != '.') return FALSE;
1112 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1117 *-----------------------------------------------------------------------------
1118 * the main filelist function
1119 *-----------------------------------------------------------------------------
1122 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1127 GList *dlist = NULL;
1128 GList *flist = NULL;
1129 gint (*stat_func)(const gchar *path, struct stat *buf);
1130 GHashTable *basename_hash = NULL;
1132 g_assert(files || dirs);
1134 if (files) *files = NULL;
1135 if (dirs) *dirs = NULL;
1137 pathl = path_from_utf8(dir_path);
1138 if (!pathl) return FALSE;
1140 dp = opendir(pathl);
1147 if (files) basename_hash = file_data_basename_hash_new();
1149 if (follow_symlinks)
1154 while ((dir = readdir(dp)) != NULL)
1156 struct stat ent_sbuf;
1157 const gchar *name = dir->d_name;
1160 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1163 filepath = g_build_filename(pathl, name, NULL);
1164 if (stat_func(filepath, &ent_sbuf) >= 0)
1166 if (S_ISDIR(ent_sbuf.st_mode))
1168 /* we ignore the .thumbnails dir for cleanliness */
1170 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1171 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1172 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1173 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1175 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1180 if (files && filter_name_exists(name))
1182 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1183 flist = g_list_prepend(flist, fd);
1184 if (fd->sidecar_priority && !fd->disable_grouping)
1186 file_data_basename_hash_insert(basename_hash, fd);
1193 if (errno == EOVERFLOW)
1195 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1205 if (dirs) *dirs = dlist;
1209 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1211 *files = filelist_filter_out_sidecars(flist);
1213 if (basename_hash) file_data_basename_hash_free(basename_hash);
1215 // Call a separate function to initialize the exif datestamps for the found files..
1216 if (files) init_exif_time_data(*files);
1221 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1223 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1226 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1228 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1231 FileData *file_data_new_group(const gchar *path_utf8)
1238 if (!stat_utf8(path_utf8, &st))
1244 if (S_ISDIR(st.st_mode))
1245 return file_data_new(path_utf8, &st, TRUE);
1247 dir = remove_level_from_path(path_utf8);
1249 filelist_read_real(dir, &files, NULL, TRUE);
1251 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1255 filelist_free(files);
1261 void filelist_free(GList *list)
1268 file_data_unref((FileData *)work->data);
1276 GList *filelist_copy(GList *list)
1278 GList *new_list = NULL;
1289 new_list = g_list_prepend(new_list, file_data_ref(fd));
1292 return g_list_reverse(new_list);
1295 GList *filelist_from_path_list(GList *list)
1297 GList *new_list = NULL;
1308 new_list = g_list_prepend(new_list, file_data_new_group(path));
1311 return g_list_reverse(new_list);
1314 GList *filelist_to_path_list(GList *list)
1316 GList *new_list = NULL;
1327 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1330 return g_list_reverse(new_list);
1333 GList *filelist_filter(GList *list, gboolean is_dir_list)
1337 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1342 FileData *fd = (FileData *)(work->data);
1343 const gchar *name = fd->name;
1345 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1346 (!is_dir_list && !filter_name_exists(name)) ||
1347 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1348 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1352 list = g_list_remove_link(list, link);
1353 file_data_unref(fd);
1364 *-----------------------------------------------------------------------------
1365 * filelist recursive
1366 *-----------------------------------------------------------------------------
1369 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1371 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1374 GList *filelist_sort_path(GList *list)
1376 return g_list_sort(list, filelist_sort_path_cb);
1379 static void filelist_recursive_append(GList **list, GList *dirs)
1386 FileData *fd = (FileData *)(work->data);
1390 if (filelist_read(fd, &f, &d))
1392 f = filelist_filter(f, FALSE);
1393 f = filelist_sort_path(f);
1394 *list = g_list_concat(*list, f);
1396 d = filelist_filter(d, TRUE);
1397 d = filelist_sort_path(d);
1398 filelist_recursive_append(list, d);
1406 GList *filelist_recursive(FileData *dir_fd)
1411 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1412 list = filelist_filter(list, FALSE);
1413 list = filelist_sort_path(list);
1415 d = filelist_filter(d, TRUE);
1416 d = filelist_sort_path(d);
1417 filelist_recursive_append(&list, d);
1424 *-----------------------------------------------------------------------------
1425 * file modification support
1426 *-----------------------------------------------------------------------------
1430 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1432 if (!fdci && fd) fdci = fd->change;
1436 g_free(fdci->source);
1441 if (fd) fd->change = NULL;
1444 static gboolean file_data_can_write_directly(FileData *fd)
1446 return filter_name_is_writable(fd->extension);
1449 static gboolean file_data_can_write_sidecar(FileData *fd)
1451 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1454 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1456 gchar *sidecar_path = NULL;
1459 if (!file_data_can_write_sidecar(fd)) return NULL;
1461 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1464 FileData *sfd = work->data;
1466 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
1468 sidecar_path = g_strdup(sfd->path);
1473 if (!existing_only && !sidecar_path)
1475 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1476 sidecar_path = g_strconcat(base, ".xmp", NULL);
1480 return sidecar_path;
1484 * marks and orientation
1487 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1488 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1489 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1490 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1492 gboolean file_data_get_mark(FileData *fd, gint n)
1494 gboolean valid = (fd->valid_marks & (1 << n));
1496 if (file_data_get_mark_func[n] && !valid)
1498 guint old = fd->marks;
1499 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1501 if (!value != !(fd->marks & (1 << n)))
1503 fd->marks = fd->marks ^ (1 << n);
1506 fd->valid_marks |= (1 << n);
1507 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1509 file_data_unref(fd);
1511 else if (!old && fd->marks)
1517 return !!(fd->marks & (1 << n));
1520 guint file_data_get_marks(FileData *fd)
1523 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1527 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1530 if (!value == !file_data_get_mark(fd, n)) return;
1532 if (file_data_set_mark_func[n])
1534 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1539 fd->marks = fd->marks ^ (1 << n);
1541 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1543 file_data_unref(fd);
1545 else if (!old && fd->marks)
1550 file_data_increment_version(fd);
1551 file_data_send_notification(fd, NOTIFY_MARKS);
1554 gboolean file_data_filter_marks(FileData *fd, guint filter)
1557 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1558 return ((fd->marks & filter) == filter);
1561 GList *file_data_filter_marks_list(GList *list, guint filter)
1568 FileData *fd = work->data;
1572 if (!file_data_filter_marks(fd, filter))
1574 list = g_list_remove_link(list, link);
1575 file_data_unref(fd);
1583 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1585 FileData *fd = value;
1586 file_data_increment_version(fd);
1587 file_data_send_notification(fd, NOTIFY_MARKS);
1590 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1592 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1594 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1596 file_data_get_mark_func[n] = get_mark_func;
1597 file_data_set_mark_func[n] = set_mark_func;
1598 file_data_mark_func_data[n] = data;
1599 file_data_destroy_mark_func[n] = notify;
1603 /* this effectively changes all known files */
1604 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1610 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1612 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1613 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1614 if (data) *data = file_data_mark_func_data[n];
1617 gint file_data_get_user_orientation(FileData *fd)
1619 return fd->user_orientation;
1622 void file_data_set_user_orientation(FileData *fd, gint value)
1624 if (fd->user_orientation == value) return;
1626 fd->user_orientation = value;
1627 file_data_increment_version(fd);
1628 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1633 * file_data - operates on the given fd
1634 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1638 /* return list of sidecar file extensions in a string */
1639 gchar *file_data_sc_list_to_string(FileData *fd)
1642 GString *result = g_string_new("");
1644 work = fd->sidecar_files;
1647 FileData *sfd = work->data;
1649 result = g_string_append(result, "+ ");
1650 result = g_string_append(result, sfd->extension);
1652 if (work) result = g_string_append_c(result, ' ');
1655 return g_string_free(result, FALSE);
1661 * add FileDataChangeInfo (see typedefs.h) for the given operation
1662 * uses file_data_add_change_info
1664 * fails if the fd->change already exists - change operations can't run in parallel
1665 * fd->change_info works as a lock
1667 * dest can be NULL - in this case the current name is used for now, it will
1672 FileDataChangeInfo types:
1674 MOVE - path is changed, name may be changed too
1675 RENAME - path remains unchanged, name is changed
1676 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1677 sidecar names are changed too, extensions are not changed
1679 UPDATE - file size, date or grouping has been changed
1682 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1684 FileDataChangeInfo *fdci;
1686 if (fd->change) return FALSE;
1688 fdci = g_new0(FileDataChangeInfo, 1);
1693 fdci->source = g_strdup(src);
1695 fdci->source = g_strdup(fd->path);
1698 fdci->dest = g_strdup(dest);
1705 static void file_data_planned_change_remove(FileData *fd)
1707 if (file_data_planned_change_hash &&
1708 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1710 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1712 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1713 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1714 file_data_unref(fd);
1715 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1717 g_hash_table_destroy(file_data_planned_change_hash);
1718 file_data_planned_change_hash = NULL;
1719 DEBUG_1("planned change: empty");
1726 void file_data_free_ci(FileData *fd)
1728 FileDataChangeInfo *fdci = fd->change;
1732 file_data_planned_change_remove(fd);
1734 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1736 g_free(fdci->source);
1744 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1746 FileDataChangeInfo *fdci = fd->change;
1748 fdci->regroup_when_finished = enable;
1751 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1755 if (fd->parent) fd = fd->parent;
1757 if (fd->change) return FALSE;
1759 work = fd->sidecar_files;
1762 FileData *sfd = work->data;
1764 if (sfd->change) return FALSE;
1768 file_data_add_ci(fd, type, NULL, NULL);
1770 work = fd->sidecar_files;
1773 FileData *sfd = work->data;
1775 file_data_add_ci(sfd, type, NULL, NULL);
1782 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1786 if (fd->parent) fd = fd->parent;
1788 if (!fd->change || fd->change->type != type) return FALSE;
1790 work = fd->sidecar_files;
1793 FileData *sfd = work->data;
1795 if (!sfd->change || sfd->change->type != type) return FALSE;
1803 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1805 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1806 file_data_sc_update_ci_copy(fd, dest_path);
1810 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1812 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1813 file_data_sc_update_ci_move(fd, dest_path);
1817 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1819 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1820 file_data_sc_update_ci_rename(fd, dest_path);
1824 gboolean file_data_sc_add_ci_delete(FileData *fd)
1826 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1829 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1831 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1832 file_data_sc_update_ci_unspecified(fd, dest_path);
1836 gboolean file_data_add_ci_write_metadata(FileData *fd)
1838 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1841 void file_data_sc_free_ci(FileData *fd)
1845 if (fd->parent) fd = fd->parent;
1847 file_data_free_ci(fd);
1849 work = fd->sidecar_files;
1852 FileData *sfd = work->data;
1854 file_data_free_ci(sfd);
1859 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1862 gboolean ret = TRUE;
1867 FileData *fd = work->data;
1869 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1876 static void file_data_sc_revert_ci_list(GList *fd_list)
1883 FileData *fd = work->data;
1885 file_data_sc_free_ci(fd);
1890 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1897 FileData *fd = work->data;
1899 if (!func(fd, dest))
1901 file_data_sc_revert_ci_list(work->prev);
1910 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1912 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1915 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1917 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1920 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1922 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1925 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1927 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1930 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1933 gboolean ret = TRUE;
1938 FileData *fd = work->data;
1940 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1947 void file_data_free_ci_list(GList *fd_list)
1954 FileData *fd = work->data;
1956 file_data_free_ci(fd);
1961 void file_data_sc_free_ci_list(GList *fd_list)
1968 FileData *fd = work->data;
1970 file_data_sc_free_ci(fd);
1976 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1977 * fails if fd->change does not exist or the change type does not match
1980 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1982 FileDataChangeType type = fd->change->type;
1984 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1988 if (!file_data_planned_change_hash)
1989 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1991 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1993 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1994 g_hash_table_remove(file_data_planned_change_hash, old_path);
1995 file_data_unref(fd);
1998 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
2003 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2004 g_hash_table_remove(file_data_planned_change_hash, new_path);
2005 file_data_unref(ofd);
2008 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2010 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2015 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2017 gchar *old_path = fd->change->dest;
2019 fd->change->dest = g_strdup(dest_path);
2020 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2024 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2026 const gchar *extension = extension_from_path(fd->change->source);
2027 gchar *base = remove_extension_from_path(dest_path);
2028 gchar *old_path = fd->change->dest;
2030 fd->change->dest = g_strconcat(base, extension, NULL);
2031 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2037 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2040 gchar *dest_path_full = NULL;
2042 if (fd->parent) fd = fd->parent;
2046 dest_path = fd->path;
2048 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2050 gchar *dir = remove_level_from_path(fd->path);
2052 dest_path_full = g_build_filename(dir, dest_path, NULL);
2054 dest_path = dest_path_full;
2056 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2058 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2059 dest_path = dest_path_full;
2062 file_data_update_ci_dest(fd, dest_path);
2064 work = fd->sidecar_files;
2067 FileData *sfd = work->data;
2069 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2073 g_free(dest_path_full);
2076 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2078 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2079 file_data_sc_update_ci(fd, dest_path);
2083 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2085 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2088 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2090 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2093 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2095 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2098 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2100 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2103 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2105 gboolean (*func)(FileData *, const gchar *))
2108 gboolean ret = TRUE;
2113 FileData *fd = work->data;
2115 if (!func(fd, dest)) ret = FALSE;
2122 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2124 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2127 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2129 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2132 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2134 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2139 * verify source and dest paths - dest image exists, etc.
2140 * it should detect all possible problems with the planned operation
2143 gint file_data_verify_ci(FileData *fd)
2145 gint ret = CHANGE_OK;
2150 DEBUG_1("Change checked: no change info: %s", fd->path);
2154 if (!isname(fd->path))
2156 /* this probably should not happen */
2157 ret |= CHANGE_NO_SRC;
2158 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2162 dir = remove_level_from_path(fd->path);
2164 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2165 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2166 fd->change->type != FILEDATA_CHANGE_RENAME &&
2167 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2170 ret |= CHANGE_WARN_UNSAVED_META;
2171 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2174 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2175 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2176 !access_file(fd->path, R_OK))
2178 ret |= CHANGE_NO_READ_PERM;
2179 DEBUG_1("Change checked: no read permission: %s", fd->path);
2181 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2182 !access_file(dir, W_OK))
2184 ret |= CHANGE_NO_WRITE_PERM_DIR;
2185 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2187 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2188 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2189 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2190 !access_file(fd->path, W_OK))
2192 ret |= CHANGE_WARN_NO_WRITE_PERM;
2193 DEBUG_1("Change checked: no write permission: %s", fd->path);
2195 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2196 - that means that there are no hard errors and warnings can be disabled
2197 - the destination is determined during the check
2199 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2201 /* determine destination file */
2202 gboolean have_dest = FALSE;
2203 gchar *dest_dir = NULL;
2205 if (options->metadata.save_in_image_file)
2207 if (file_data_can_write_directly(fd))
2209 /* we can write the file directly */
2210 if (access_file(fd->path, W_OK))
2216 if (options->metadata.warn_on_write_problems)
2218 ret |= CHANGE_WARN_NO_WRITE_PERM;
2219 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2223 else if (file_data_can_write_sidecar(fd))
2225 /* we can write sidecar */
2226 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2227 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2229 file_data_update_ci_dest(fd, sidecar);
2234 if (options->metadata.warn_on_write_problems)
2236 ret |= CHANGE_WARN_NO_WRITE_PERM;
2237 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2246 /* write private metadata file under ~/.geeqie */
2248 /* If an existing metadata file exists, we will try writing to
2249 * it's location regardless of the user's preference.
2251 gchar *metadata_path = NULL;
2253 /* but ignore XMP if we are not able to write it */
2254 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2256 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2258 if (metadata_path && !access_file(metadata_path, W_OK))
2260 g_free(metadata_path);
2261 metadata_path = NULL;
2268 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2269 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2271 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2273 metadata_path = g_build_filename(dest_dir, filename, NULL);
2277 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2279 file_data_update_ci_dest(fd, metadata_path);
2284 ret |= CHANGE_NO_WRITE_PERM_DEST;
2285 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2287 g_free(metadata_path);
2292 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2297 same = (strcmp(fd->path, fd->change->dest) == 0);
2301 const gchar *dest_ext = extension_from_path(fd->change->dest);
2302 if (!dest_ext) dest_ext = "";
2304 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2306 ret |= CHANGE_WARN_CHANGED_EXT;
2307 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2312 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2314 ret |= CHANGE_WARN_SAME;
2315 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2319 dest_dir = remove_level_from_path(fd->change->dest);
2321 if (!isdir(dest_dir))
2323 ret |= CHANGE_NO_DEST_DIR;
2324 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2326 else if (!access_file(dest_dir, W_OK))
2328 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2329 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2333 if (isfile(fd->change->dest))
2335 if (!access_file(fd->change->dest, W_OK))
2337 ret |= CHANGE_NO_WRITE_PERM_DEST;
2338 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2342 ret |= CHANGE_WARN_DEST_EXISTS;
2343 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2346 else if (isdir(fd->change->dest))
2348 ret |= CHANGE_DEST_EXISTS;
2349 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2356 fd->change->error = ret;
2357 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2364 gint file_data_sc_verify_ci(FileData *fd)
2369 ret = file_data_verify_ci(fd);
2371 work = fd->sidecar_files;
2374 FileData *sfd = work->data;
2376 ret |= file_data_verify_ci(sfd);
2383 gchar *file_data_get_error_string(gint error)
2385 GString *result = g_string_new("");
2387 if (error & CHANGE_NO_SRC)
2389 if (result->len > 0) g_string_append(result, ", ");
2390 g_string_append(result, _("file or directory does not exist"));
2393 if (error & CHANGE_DEST_EXISTS)
2395 if (result->len > 0) g_string_append(result, ", ");
2396 g_string_append(result, _("destination already exists"));
2399 if (error & CHANGE_NO_WRITE_PERM_DEST)
2401 if (result->len > 0) g_string_append(result, ", ");
2402 g_string_append(result, _("destination can't be overwritten"));
2405 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2407 if (result->len > 0) g_string_append(result, ", ");
2408 g_string_append(result, _("destination directory is not writable"));
2411 if (error & CHANGE_NO_DEST_DIR)
2413 if (result->len > 0) g_string_append(result, ", ");
2414 g_string_append(result, _("destination directory does not exist"));
2417 if (error & CHANGE_NO_WRITE_PERM_DIR)
2419 if (result->len > 0) g_string_append(result, ", ");
2420 g_string_append(result, _("source directory is not writable"));
2423 if (error & CHANGE_NO_READ_PERM)
2425 if (result->len > 0) g_string_append(result, ", ");
2426 g_string_append(result, _("no read permission"));
2429 if (error & CHANGE_WARN_NO_WRITE_PERM)
2431 if (result->len > 0) g_string_append(result, ", ");
2432 g_string_append(result, _("file is readonly"));
2435 if (error & CHANGE_WARN_DEST_EXISTS)
2437 if (result->len > 0) g_string_append(result, ", ");
2438 g_string_append(result, _("destination already exists and will be overwritten"));
2441 if (error & CHANGE_WARN_SAME)
2443 if (result->len > 0) g_string_append(result, ", ");
2444 g_string_append(result, _("source and destination are the same"));
2447 if (error & CHANGE_WARN_CHANGED_EXT)
2449 if (result->len > 0) g_string_append(result, ", ");
2450 g_string_append(result, _("source and destination have different extension"));
2453 if (error & CHANGE_WARN_UNSAVED_META)
2455 if (result->len > 0) g_string_append(result, ", ");
2456 g_string_append(result, _("there are unsaved metadata changes for the file"));
2459 return g_string_free(result, FALSE);
2462 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2465 gint all_errors = 0;
2466 gint common_errors = ~0;
2471 if (!list) return 0;
2473 num = g_list_length(list);
2474 errors = g_new(int, num);
2485 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2486 all_errors |= error;
2487 common_errors &= error;
2494 if (desc && all_errors)
2497 GString *result = g_string_new("");
2501 gchar *str = file_data_get_error_string(common_errors);
2502 g_string_append(result, str);
2503 g_string_append(result, "\n");
2517 error = errors[i] & ~common_errors;
2521 gchar *str = file_data_get_error_string(error);
2522 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2527 *desc = g_string_free(result, FALSE);
2536 * perform the change described by FileFataChangeInfo
2537 * it is used for internal operations,
2538 * this function actually operates with files on the filesystem
2539 * it should implement safe delete
2542 static gboolean file_data_perform_move(FileData *fd)
2544 g_assert(!strcmp(fd->change->source, fd->path));
2545 return move_file(fd->change->source, fd->change->dest);
2548 static gboolean file_data_perform_copy(FileData *fd)
2550 g_assert(!strcmp(fd->change->source, fd->path));
2551 return copy_file(fd->change->source, fd->change->dest);
2554 static gboolean file_data_perform_delete(FileData *fd)
2556 if (isdir(fd->path) && !islink(fd->path))
2557 return rmdir_utf8(fd->path);
2559 if (options->file_ops.safe_delete_enable)
2560 return file_util_safe_unlink(fd->path);
2562 return unlink_file(fd->path);
2565 gboolean file_data_perform_ci(FileData *fd)
2567 FileDataChangeType type = fd->change->type;
2571 case FILEDATA_CHANGE_MOVE:
2572 return file_data_perform_move(fd);
2573 case FILEDATA_CHANGE_COPY:
2574 return file_data_perform_copy(fd);
2575 case FILEDATA_CHANGE_RENAME:
2576 return file_data_perform_move(fd); /* the same as move */
2577 case FILEDATA_CHANGE_DELETE:
2578 return file_data_perform_delete(fd);
2579 case FILEDATA_CHANGE_WRITE_METADATA:
2580 return metadata_write_perform(fd);
2581 case FILEDATA_CHANGE_UNSPECIFIED:
2582 /* nothing to do here */
2590 gboolean file_data_sc_perform_ci(FileData *fd)
2593 gboolean ret = TRUE;
2594 FileDataChangeType type = fd->change->type;
2596 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2598 work = fd->sidecar_files;
2601 FileData *sfd = work->data;
2603 if (!file_data_perform_ci(sfd)) ret = FALSE;
2607 if (!file_data_perform_ci(fd)) ret = FALSE;
2613 * updates FileData structure according to FileDataChangeInfo
2616 gboolean file_data_apply_ci(FileData *fd)
2618 FileDataChangeType type = fd->change->type;
2621 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2623 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2624 file_data_planned_change_remove(fd);
2626 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2628 /* this change overwrites another file which is already known to other modules
2629 renaming fd would create duplicate FileData structure
2630 the best thing we can do is nothing
2631 FIXME: maybe we could copy stuff like marks
2633 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2637 file_data_set_path(fd, fd->change->dest);
2640 file_data_increment_version(fd);
2641 file_data_send_notification(fd, NOTIFY_CHANGE);
2646 gboolean file_data_sc_apply_ci(FileData *fd)
2649 FileDataChangeType type = fd->change->type;
2651 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2653 work = fd->sidecar_files;
2656 FileData *sfd = work->data;
2658 file_data_apply_ci(sfd);
2662 file_data_apply_ci(fd);
2667 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2670 if (fd->parent) fd = fd->parent;
2671 if (!g_list_find(list, fd)) return FALSE;
2673 work = fd->sidecar_files;
2676 if (!g_list_find(list, work->data)) return FALSE;
2682 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2687 /* change partial groups to independent files */
2692 FileData *fd = work->data;
2695 if (!file_data_list_contains_whole_group(list, fd))
2697 file_data_disable_grouping(fd, TRUE);
2700 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2706 /* remove sidecars from the list,
2707 they can be still acessed via main_fd->sidecar_files */
2711 FileData *fd = work->data;
2715 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2717 out = g_list_prepend(out, file_data_ref(fd));
2721 filelist_free(list);
2722 out = g_list_reverse(out);
2732 * notify other modules about the change described by FileDataChangeInfo
2735 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2736 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2737 implementation in view_file_list.c */
2740 typedef struct _NotifyIdleData NotifyIdleData;
2742 struct _NotifyIdleData {
2748 typedef struct _NotifyData NotifyData;
2750 struct _NotifyData {
2751 FileDataNotifyFunc func;
2753 NotifyPriority priority;
2756 static GList *notify_func_list = NULL;
2758 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2760 NotifyData *nda = (NotifyData *)a;
2761 NotifyData *ndb = (NotifyData *)b;
2763 if (nda->priority < ndb->priority) return -1;
2764 if (nda->priority > ndb->priority) return 1;
2768 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2771 GList *work = notify_func_list;
2775 NotifyData *nd = (NotifyData *)work->data;
2777 if (nd->func == func && nd->data == data)
2779 g_warning("Notify func already registered");
2785 nd = g_new(NotifyData, 1);
2788 nd->priority = priority;
2790 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2791 DEBUG_2("Notify func registered: %p", nd);
2796 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2798 GList *work = notify_func_list;
2802 NotifyData *nd = (NotifyData *)work->data;
2804 if (nd->func == func && nd->data == data)
2806 notify_func_list = g_list_delete_link(notify_func_list, work);
2808 DEBUG_2("Notify func unregistered: %p", nd);
2814 g_warning("Notify func not found");
2819 gboolean file_data_send_notification_idle_cb(gpointer data)
2821 NotifyIdleData *nid = (NotifyIdleData *)data;
2822 GList *work = notify_func_list;
2826 NotifyData *nd = (NotifyData *)work->data;
2828 nd->func(nid->fd, nid->type, nd->data);
2831 file_data_unref(nid->fd);
2836 void file_data_send_notification(FileData *fd, NotifyType type)
2838 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
2839 nid->fd = file_data_ref(fd);
2841 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
2844 static GHashTable *file_data_monitor_pool = NULL;
2845 static guint realtime_monitor_id = 0; /* event source id */
2847 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2851 file_data_check_changed_files(fd);
2853 DEBUG_1("monitor %s", fd->path);
2856 static gboolean realtime_monitor_cb(gpointer data)
2858 if (!options->update_on_time_change) return TRUE;
2859 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2863 gboolean file_data_register_real_time_monitor(FileData *fd)
2869 if (!file_data_monitor_pool)
2870 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2872 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2874 DEBUG_1("Register realtime %d %s", count, fd->path);
2877 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2879 if (!realtime_monitor_id)
2881 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2887 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2891 g_assert(file_data_monitor_pool);
2893 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2895 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2897 g_assert(count > 0);
2902 g_hash_table_remove(file_data_monitor_pool, fd);
2904 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2906 file_data_unref(fd);
2908 if (g_hash_table_size(file_data_monitor_pool) == 0)
2910 g_source_remove(realtime_monitor_id);
2911 realtime_monitor_id = 0;
2917 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */