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"
30 gint global_file_data_count = 0;
33 static GHashTable *file_data_pool = NULL;
34 static GHashTable *file_data_planned_change_hash = NULL;
36 static gint sidecar_file_priority(const gchar *extension);
37 static void file_data_check_sidecars(const GList *basename_list);
38 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
41 static SortType filelist_sort_method = SORT_NONE;
42 static gboolean filelist_sort_ascend = TRUE;
45 *-----------------------------------------------------------------------------
46 * text conversion utils
47 *-----------------------------------------------------------------------------
50 gchar *text_from_size(gint64 size)
56 /* what I would like to use is printf("%'d", size)
57 * BUT: not supported on every libc :(
61 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
62 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
66 a = g_strdup_printf("%d", (guint)size);
72 b = g_new(gchar, l + n + 1);
97 gchar *text_from_size_abrev(gint64 size)
99 if (size < (gint64)1024)
101 return g_strdup_printf(_("%d bytes"), (gint)size);
103 if (size < (gint64)1048576)
105 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
107 if (size < (gint64)1073741824)
109 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
112 /* to avoid overflowing the gdouble, do division in two steps */
114 return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
117 /* note: returned string is valid until next call to text_from_time() */
118 const gchar *text_from_time(time_t t)
120 static gchar *ret = NULL;
124 GError *error = NULL;
126 btime = localtime(&t);
128 /* the %x warning about 2 digit years is not an error */
129 buflen = strftime(buf, sizeof(buf), "%x %X", btime);
130 if (buflen < 1) return "";
133 ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
136 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
145 *-----------------------------------------------------------------------------
146 * changed files detection and notification
147 *-----------------------------------------------------------------------------
150 void file_data_increment_version(FileData *fd)
156 fd->parent->version++;
157 fd->parent->valid_marks = 0;
161 static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st)
163 if (fd->size != st->st_size ||
164 fd->date != st->st_mtime)
166 fd->size = st->st_size;
167 fd->date = st->st_mtime;
168 fd->mode = st->st_mode;
169 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
170 fd->thumb_pixbuf = NULL;
171 file_data_increment_version(fd);
172 file_data_send_notification(fd, NOTIFY_REREAD);
178 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
180 gboolean ret = FALSE;
183 ret = file_data_check_changed_single_file(fd, st);
185 work = fd->sidecar_files;
188 FileData *sfd = work->data;
192 if (!stat_utf8(sfd->path, &st))
197 file_data_disconnect_sidecar_file(fd, sfd);
199 file_data_increment_version(sfd);
200 file_data_send_notification(sfd, NOTIFY_REREAD);
201 file_data_unref(sfd);
205 ret |= file_data_check_changed_files_recursive(sfd, &st);
211 gboolean file_data_check_changed_files(FileData *fd)
213 gboolean ret = FALSE;
216 if (fd->parent) fd = fd->parent;
218 if (!stat_utf8(fd->path, &st))
222 FileData *sfd = NULL;
224 /* parent is missing, we have to rebuild whole group */
229 /* file_data_disconnect_sidecar_file might delete the file,
230 we have to keep the reference to prevent this */
231 sidecars = filelist_copy(fd->sidecar_files);
239 file_data_disconnect_sidecar_file(fd, sfd);
241 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
242 /* now we can release the sidecars */
243 filelist_free(sidecars);
244 file_data_increment_version(fd);
245 file_data_send_notification(fd, NOTIFY_REREAD);
250 ret |= file_data_check_changed_files_recursive(fd, &st);
257 *-----------------------------------------------------------------------------
258 * file name, extension, sorting, ...
259 *-----------------------------------------------------------------------------
262 static void file_data_set_collate_keys(FileData *fd)
264 gchar *caseless_name;
267 valid_name = g_filename_display_name(fd->name);
268 caseless_name = g_utf8_casefold(valid_name, -1);
270 g_free(fd->collate_key_name);
271 g_free(fd->collate_key_name_nocase);
273 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
274 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
277 g_free(caseless_name);
280 static void file_data_set_path(FileData *fd, const gchar *path)
282 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
283 g_assert(file_data_pool);
287 if (fd->original_path)
289 g_hash_table_remove(file_data_pool, fd->original_path);
290 g_free(fd->original_path);
293 g_assert(!g_hash_table_lookup(file_data_pool, path));
295 fd->original_path = g_strdup(path);
296 g_hash_table_insert(file_data_pool, fd->original_path, fd);
298 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
300 fd->path = g_strdup(path);
302 fd->extension = fd->name + 1;
303 file_data_set_collate_keys(fd);
307 fd->path = g_strdup(path);
308 fd->name = filename_from_path(fd->path);
310 if (strcmp(fd->name, "..") == 0)
312 gchar *dir = remove_level_from_path(path);
314 fd->path = remove_level_from_path(dir);
317 fd->extension = fd->name + 2;
318 file_data_set_collate_keys(fd);
321 else if (strcmp(fd->name, ".") == 0)
324 fd->path = remove_level_from_path(path);
326 fd->extension = fd->name + 1;
327 file_data_set_collate_keys(fd);
331 fd->extension = registered_extension_from_path(fd->path);
332 if (fd->extension == NULL)
334 fd->extension = fd->name + strlen(fd->name);
337 fd->sidecar_priority = sidecar_file_priority(fd->extension);
338 file_data_set_collate_keys(fd);
342 *-----------------------------------------------------------------------------
343 * create or reuse Filedata
344 *-----------------------------------------------------------------------------
347 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
351 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
353 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
356 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
358 fd = g_hash_table_lookup(file_data_pool, path_utf8);
364 if (!fd && file_data_planned_change_hash)
366 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
369 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
371 file_data_apply_ci(fd);
379 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
382 changed = file_data_check_changed_single_file(fd, st);
384 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
389 fd = g_new0(FileData, 1);
390 #ifdef DEBUG_FILEDATA
391 global_file_data_count++;
392 DEBUG_2("file data count++: %d", global_file_data_count);
395 fd->size = st->st_size;
396 fd->date = st->st_mtime;
397 fd->mode = st->st_mode;
399 fd->magick = FD_MAGICK;
401 if (disable_sidecars) fd->disable_grouping = TRUE;
403 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
408 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
410 gchar *path_utf8 = path_to_utf8(path);
411 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
417 FileData *file_data_new_simple(const gchar *path_utf8)
422 if (!stat_utf8(path_utf8, &st))
428 fd = g_hash_table_lookup(file_data_pool, path_utf8);
429 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
438 void init_exif_time_data(GList *files)
441 DEBUG_1("%s init_exif_time_data: ...", get_exec_time());
453 void read_exif_time_data(FileData *file)
455 if (file->exifdate > 0)
457 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
461 file->exif = exif_read_fd(file);
465 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
466 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), file, file->path);
471 uint year, month, day, hour, min, sec;
473 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
474 time_str.tm_year = year - 1900;
475 time_str.tm_mon = month - 1;
476 time_str.tm_mday = day;
477 time_str.tm_hour = hour;
478 time_str.tm_min = min;
479 time_str.tm_sec = sec;
480 time_str.tm_isdst = 0;
482 file->exifdate = mktime(&time_str);
488 void set_exif_time_data(GList *files)
490 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
494 FileData *file = files->data;
496 read_exif_time_data(file);
501 FileData *file_data_new_no_grouping(const gchar *path_utf8)
505 if (!stat_utf8(path_utf8, &st))
511 return file_data_new(path_utf8, &st, TRUE);
514 FileData *file_data_new_dir(const gchar *path_utf8)
518 if (!stat_utf8(path_utf8, &st))
524 /* dir or non-existing yet */
525 g_assert(S_ISDIR(st.st_mode));
527 return file_data_new(path_utf8, &st, TRUE);
531 *-----------------------------------------------------------------------------
533 *-----------------------------------------------------------------------------
536 #ifdef DEBUG_FILEDATA
537 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
539 FileData *file_data_ref(FileData *fd)
542 if (fd == NULL) return NULL;
543 if (fd->magick != FD_MAGICK)
544 #ifdef DEBUG_FILEDATA
545 DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
547 DEBUG_0("fd magick mismatch fd=%p", fd);
549 g_assert(fd->magick == FD_MAGICK);
552 #ifdef DEBUG_FILEDATA
553 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", fd, fd->ref, fd->path, file, line);
555 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
560 static void file_data_free(FileData *fd)
562 g_assert(fd->magick == FD_MAGICK);
563 g_assert(fd->ref == 0);
564 g_assert(!fd->locked);
566 #ifdef DEBUG_FILEDATA
567 global_file_data_count--;
568 DEBUG_2("file data count--: %d", global_file_data_count);
571 metadata_cache_free(fd);
572 g_hash_table_remove(file_data_pool, fd->original_path);
575 g_free(fd->original_path);
576 g_free(fd->collate_key_name);
577 g_free(fd->collate_key_name_nocase);
578 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
579 histmap_free(fd->histmap);
581 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
583 file_data_change_info_free(NULL, fd);
588 * \brief Checks if the FileData is referenced
590 * Checks the refcount and whether the FileData is locked.
592 static gboolean file_data_check_has_ref(FileData *fd)
594 return fd->ref > 0 || fd->locked;
598 * \brief Consider freeing a FileData.
600 * This function will free a FileData and its children provided that neither its parent nor it has
601 * a positive refcount, and provided that neither is locked.
603 static void file_data_consider_free(FileData *fd)
606 FileData *parent = fd->parent ? fd->parent : fd;
608 g_assert(fd->magick == FD_MAGICK);
609 if (file_data_check_has_ref(fd)) return;
610 if (file_data_check_has_ref(parent)) return;
612 work = parent->sidecar_files;
615 FileData *sfd = work->data;
616 if (file_data_check_has_ref(sfd)) return;
620 /* Neither the parent nor the siblings are referenced, so we can free everything */
621 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
622 fd->path, fd->parent ? parent->path : "-");
624 work = parent->sidecar_files;
627 FileData *sfd = work->data;
632 g_list_free(parent->sidecar_files);
633 parent->sidecar_files = NULL;
635 file_data_free(parent);
638 #ifdef DEBUG_FILEDATA
639 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
641 void file_data_unref(FileData *fd)
644 if (fd == NULL) return;
645 if (fd->magick != FD_MAGICK)
646 #ifdef DEBUG_FILEDATA
647 DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
649 DEBUG_0("fd magick mismatch fd=%p", fd);
651 g_assert(fd->magick == FD_MAGICK);
654 #ifdef DEBUG_FILEDATA
655 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", fd, fd->ref, fd->locked, fd->path,
658 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
661 // Free FileData if it's no longer ref'd
662 file_data_consider_free(fd);
666 * \brief Lock the FileData in memory.
668 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
669 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
670 * even if the code would continue to function properly even if the FileData were freed. Code that
671 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
673 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
674 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
676 void file_data_lock(FileData *fd)
678 if (fd == NULL) return;
679 if (fd->magick != FD_MAGICK) DEBUG_0("fd magick mismatch fd=%p", fd);
681 g_assert(fd->magick == FD_MAGICK);
684 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
688 * \brief Reset the maintain-FileData-in-memory lock
690 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
691 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
692 * keeping it from being freed.
694 void file_data_unlock(FileData *fd)
696 if (fd == NULL) return;
697 if (fd->magick != FD_MAGICK) DEBUG_0("fd magick mismatch fd=%p", fd);
699 g_assert(fd->magick == FD_MAGICK);
702 // Free FileData if it's no longer ref'd
703 file_data_consider_free(fd);
707 * \brief Lock all of the FileDatas in the provided list
709 * \see file_data_lock(FileData)
711 void file_data_lock_list(GList *list)
718 FileData *fd = work->data;
725 * \brief Unlock all of the FileDatas in the provided list
727 * \see file_data_unlock(FileData)
729 void file_data_unlock_list(GList *list)
736 FileData *fd = work->data;
738 file_data_unlock(fd);
743 *-----------------------------------------------------------------------------
744 * sidecar file info struct
745 *-----------------------------------------------------------------------------
748 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
750 const FileData *fda = a;
751 const FileData *fdb = b;
753 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
754 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
756 return strcmp(fdb->extension, fda->extension);
760 static gint sidecar_file_priority(const gchar *extension)
765 if (extension == NULL)
768 work = sidecar_ext_get_list();
771 gchar *ext = work->data;
774 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
780 static void file_data_check_sidecars(const GList *basename_list)
782 /* basename_list contains the new group - first is the parent, then sorted sidecars */
783 /* all files in the list have ref count > 0 */
786 GList *s_work, *new_sidecars;
789 if (!basename_list) return;
792 DEBUG_2("basename start");
793 work = basename_list;
796 FileData *fd = work->data;
798 g_assert(fd->magick == FD_MAGICK);
799 DEBUG_2("basename: %p %s", fd, fd->name);
802 g_assert(fd->parent->magick == FD_MAGICK);
803 DEBUG_2(" parent: %p", fd->parent);
805 s_work = fd->sidecar_files;
808 FileData *sfd = s_work->data;
809 s_work = s_work->next;
810 g_assert(sfd->magick == FD_MAGICK);
811 DEBUG_2(" sidecar: %p %s", sfd, sfd->name);
814 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
817 parent_fd = basename_list->data;
819 /* check if the second and next entries of basename_list are already connected
820 as sidecars of the first entry (parent_fd) */
821 work = basename_list->next;
822 s_work = parent_fd->sidecar_files;
824 while (work && s_work)
826 if (work->data != s_work->data) break;
828 s_work = s_work->next;
831 if (!work && !s_work)
833 DEBUG_2("basename no change");
834 return; /* no change in grouping */
837 /* we have to regroup it */
839 /* first, disconnect everything and send notification*/
841 work = basename_list;
844 FileData *fd = work->data;
846 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
850 FileData *old_parent = fd->parent;
851 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
852 file_data_ref(old_parent);
853 file_data_disconnect_sidecar_file(old_parent, fd);
854 file_data_send_notification(old_parent, NOTIFY_REREAD);
855 file_data_unref(old_parent);
858 while (fd->sidecar_files)
860 FileData *sfd = fd->sidecar_files->data;
861 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
863 file_data_disconnect_sidecar_file(fd, sfd);
864 file_data_send_notification(sfd, NOTIFY_REREAD);
865 file_data_unref(sfd);
867 file_data_send_notification(fd, NOTIFY_GROUPING);
869 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
872 /* now we can form the new group */
873 work = basename_list->next;
877 FileData *sfd = work->data;
878 g_assert(sfd->magick == FD_MAGICK);
879 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
880 sfd->parent = parent_fd;
881 new_sidecars = g_list_prepend(new_sidecars, sfd);
884 g_assert(parent_fd->sidecar_files == NULL);
885 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
886 DEBUG_1("basename group changed for %s", parent_fd->path);
890 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
892 g_assert(target->magick == FD_MAGICK);
893 g_assert(sfd->magick == FD_MAGICK);
894 g_assert(g_list_find(target->sidecar_files, sfd));
896 file_data_ref(target);
899 g_assert(sfd->parent == target);
901 file_data_increment_version(sfd); /* increments both sfd and target */
903 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
906 file_data_unref(target);
907 file_data_unref(sfd);
910 /* disables / enables grouping for particular file, sends UPDATE notification */
911 void file_data_disable_grouping(FileData *fd, gboolean disable)
913 if (!fd->disable_grouping == !disable) return;
915 fd->disable_grouping = !!disable;
921 FileData *parent = file_data_ref(fd->parent);
922 file_data_disconnect_sidecar_file(parent, fd);
923 file_data_send_notification(parent, NOTIFY_GROUPING);
924 file_data_unref(parent);
926 else if (fd->sidecar_files)
928 GList *sidecar_files = filelist_copy(fd->sidecar_files);
929 GList *work = sidecar_files;
932 FileData *sfd = work->data;
934 file_data_disconnect_sidecar_file(fd, sfd);
935 file_data_send_notification(sfd, NOTIFY_GROUPING);
937 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
938 filelist_free(sidecar_files);
942 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
947 file_data_increment_version(fd);
948 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
950 file_data_send_notification(fd, NOTIFY_GROUPING);
953 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
960 FileData *fd = work->data;
962 file_data_disable_grouping(fd, disable);
970 *-----------------------------------------------------------------------------
972 *-----------------------------------------------------------------------------
976 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
979 if (!filelist_sort_ascend)
986 switch (filelist_sort_method)
991 if (fa->size < fb->size) return -1;
992 if (fa->size > fb->size) return 1;
993 /* fall back to name */
996 if (fa->date < fb->date) return -1;
997 if (fa->date > fb->date) return 1;
998 /* fall back to name */
1001 if (fa->exifdate < fb->exifdate) return -1;
1002 if (fa->exifdate > fb->exifdate) return 1;
1003 /* fall back to name */
1005 #ifdef HAVE_STRVERSCMP
1007 ret = strverscmp(fa->name, fb->name);
1008 if (ret != 0) return ret;
1015 if (options->file_sort.case_sensitive)
1016 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1018 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1020 if (ret != 0) return ret;
1022 /* do not return 0 unless the files are really the same
1023 file_data_pool ensures that original_path is unique
1025 return strcmp(fa->original_path, fb->original_path);
1028 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1030 filelist_sort_method = method;
1031 filelist_sort_ascend = ascend;
1032 return filelist_sort_compare_filedata(fa, fb);
1035 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1037 return filelist_sort_compare_filedata(a, b);
1040 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1042 filelist_sort_method = method;
1043 filelist_sort_ascend = ascend;
1044 return g_list_sort(list, cb);
1047 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1049 filelist_sort_method = method;
1050 filelist_sort_ascend = ascend;
1051 return g_list_insert_sorted(list, data, cb);
1054 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1056 if (method == SORT_EXIFTIME)
1058 set_exif_time_data(list);
1060 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1063 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
1065 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1069 *-----------------------------------------------------------------------------
1070 * basename hash - grouping of sidecars in filelist
1071 *-----------------------------------------------------------------------------
1075 static GHashTable *file_data_basename_hash_new(void)
1077 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1080 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1083 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1085 list = g_hash_table_lookup(basename_hash, basename);
1087 if (!g_list_find(list, fd))
1089 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1090 g_hash_table_insert(basename_hash, basename, list);
1099 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
1101 filelist_free((GList *)value);
1104 static void file_data_basename_hash_free(GHashTable *basename_hash)
1106 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1107 g_hash_table_destroy(basename_hash);
1111 *-----------------------------------------------------------------------------
1112 * handling sidecars in filelist
1113 *-----------------------------------------------------------------------------
1116 static GList *filelist_filter_out_sidecars(GList *flist)
1118 GList *work = flist;
1119 GList *flist_filtered = NULL;
1123 FileData *fd = work->data;
1126 if (fd->parent) /* remove fd's that are children */
1127 file_data_unref(fd);
1129 flist_filtered = g_list_prepend(flist_filtered, fd);
1133 return flist_filtered;
1136 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
1138 GList *basename_list = (GList *)value;
1139 file_data_check_sidecars(basename_list);
1143 static gboolean is_hidden_file(const gchar *name)
1145 if (name[0] != '.') return FALSE;
1146 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1151 *-----------------------------------------------------------------------------
1152 * the main filelist function
1153 *-----------------------------------------------------------------------------
1156 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1161 GList *dlist = NULL;
1162 GList *flist = NULL;
1163 gint (*stat_func)(const gchar *path, struct stat *buf);
1164 GHashTable *basename_hash = NULL;
1166 g_assert(files || dirs);
1168 if (files) *files = NULL;
1169 if (dirs) *dirs = NULL;
1171 pathl = path_from_utf8(dir_path);
1172 if (!pathl) return FALSE;
1174 dp = opendir(pathl);
1181 if (files) basename_hash = file_data_basename_hash_new();
1183 if (follow_symlinks)
1188 while ((dir = readdir(dp)) != NULL)
1190 struct stat ent_sbuf;
1191 const gchar *name = dir->d_name;
1194 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1197 filepath = g_build_filename(pathl, name, NULL);
1198 if (stat_func(filepath, &ent_sbuf) >= 0)
1200 if (S_ISDIR(ent_sbuf.st_mode))
1202 /* we ignore the .thumbnails dir for cleanliness */
1204 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1205 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1206 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1207 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1209 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1214 if (files && filter_name_exists(name))
1216 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1217 flist = g_list_prepend(flist, fd);
1218 if (fd->sidecar_priority && !fd->disable_grouping)
1220 file_data_basename_hash_insert(basename_hash, fd);
1227 if (errno == EOVERFLOW)
1229 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1239 if (dirs) *dirs = dlist;
1243 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1245 *files = filelist_filter_out_sidecars(flist);
1247 if (basename_hash) file_data_basename_hash_free(basename_hash);
1249 // Call a separate function to initialize the exif datestamps for the found files..
1250 if (files) init_exif_time_data(*files);
1255 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1257 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1260 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1262 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1265 FileData *file_data_new_group(const gchar *path_utf8)
1272 if (!stat_utf8(path_utf8, &st))
1278 if (S_ISDIR(st.st_mode))
1279 return file_data_new(path_utf8, &st, TRUE);
1281 dir = remove_level_from_path(path_utf8);
1283 filelist_read_real(dir, &files, NULL, TRUE);
1285 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1286 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1292 filelist_free(files);
1298 void filelist_free(GList *list)
1305 file_data_unref((FileData *)work->data);
1313 GList *filelist_copy(GList *list)
1315 GList *new_list = NULL;
1326 new_list = g_list_prepend(new_list, file_data_ref(fd));
1329 return g_list_reverse(new_list);
1332 GList *filelist_from_path_list(GList *list)
1334 GList *new_list = NULL;
1345 new_list = g_list_prepend(new_list, file_data_new_group(path));
1348 return g_list_reverse(new_list);
1351 GList *filelist_to_path_list(GList *list)
1353 GList *new_list = NULL;
1364 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1367 return g_list_reverse(new_list);
1370 GList *filelist_filter(GList *list, gboolean is_dir_list)
1374 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1379 FileData *fd = (FileData *)(work->data);
1380 const gchar *name = fd->name;
1382 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1383 (!is_dir_list && !filter_name_exists(name)) ||
1384 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1385 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1389 list = g_list_remove_link(list, link);
1390 file_data_unref(fd);
1401 *-----------------------------------------------------------------------------
1402 * filelist recursive
1403 *-----------------------------------------------------------------------------
1406 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1408 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1411 GList *filelist_sort_path(GList *list)
1413 return g_list_sort(list, filelist_sort_path_cb);
1416 static void filelist_recursive_append(GList **list, GList *dirs)
1423 FileData *fd = (FileData *)(work->data);
1427 if (filelist_read(fd, &f, &d))
1429 f = filelist_filter(f, FALSE);
1430 f = filelist_sort_path(f);
1431 *list = g_list_concat(*list, f);
1433 d = filelist_filter(d, TRUE);
1434 d = filelist_sort_path(d);
1435 filelist_recursive_append(list, d);
1443 GList *filelist_recursive(FileData *dir_fd)
1448 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1449 list = filelist_filter(list, FALSE);
1450 list = filelist_sort_path(list);
1452 d = filelist_filter(d, TRUE);
1453 d = filelist_sort_path(d);
1454 filelist_recursive_append(&list, d);
1461 *-----------------------------------------------------------------------------
1462 * file modification support
1463 *-----------------------------------------------------------------------------
1467 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1469 if (!fdci && fd) fdci = fd->change;
1473 g_free(fdci->source);
1478 if (fd) fd->change = NULL;
1481 static gboolean file_data_can_write_directly(FileData *fd)
1483 return filter_name_is_writable(fd->extension);
1486 static gboolean file_data_can_write_sidecar(FileData *fd)
1488 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1491 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1493 gchar *sidecar_path = NULL;
1496 if (!file_data_can_write_sidecar(fd)) return NULL;
1498 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1501 FileData *sfd = work->data;
1503 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
1505 sidecar_path = g_strdup(sfd->path);
1510 if (!existing_only && !sidecar_path)
1512 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1513 sidecar_path = g_strconcat(base, ".xmp", NULL);
1517 return sidecar_path;
1521 * marks and orientation
1524 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1525 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1526 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1527 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1529 gboolean file_data_get_mark(FileData *fd, gint n)
1531 gboolean valid = (fd->valid_marks & (1 << n));
1533 if (file_data_get_mark_func[n] && !valid)
1535 guint old = fd->marks;
1536 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1538 if (!value != !(fd->marks & (1 << n)))
1540 fd->marks = fd->marks ^ (1 << n);
1543 fd->valid_marks |= (1 << n);
1544 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1546 file_data_unref(fd);
1548 else if (!old && fd->marks)
1554 return !!(fd->marks & (1 << n));
1557 guint file_data_get_marks(FileData *fd)
1560 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1564 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1567 if (!value == !file_data_get_mark(fd, n)) return;
1569 if (file_data_set_mark_func[n])
1571 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1576 fd->marks = fd->marks ^ (1 << n);
1578 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1580 file_data_unref(fd);
1582 else if (!old && fd->marks)
1587 file_data_increment_version(fd);
1588 file_data_send_notification(fd, NOTIFY_MARKS);
1591 gboolean file_data_filter_marks(FileData *fd, guint filter)
1594 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1595 return ((fd->marks & filter) == filter);
1598 GList *file_data_filter_marks_list(GList *list, guint filter)
1605 FileData *fd = work->data;
1609 if (!file_data_filter_marks(fd, filter))
1611 list = g_list_remove_link(list, link);
1612 file_data_unref(fd);
1620 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1622 FileData *fd = value;
1623 file_data_increment_version(fd);
1624 file_data_send_notification(fd, NOTIFY_MARKS);
1627 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1629 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1631 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1633 file_data_get_mark_func[n] = get_mark_func;
1634 file_data_set_mark_func[n] = set_mark_func;
1635 file_data_mark_func_data[n] = data;
1636 file_data_destroy_mark_func[n] = notify;
1640 /* this effectively changes all known files */
1641 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1647 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1649 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1650 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1651 if (data) *data = file_data_mark_func_data[n];
1654 gint file_data_get_user_orientation(FileData *fd)
1656 return fd->user_orientation;
1659 void file_data_set_user_orientation(FileData *fd, gint value)
1661 if (fd->user_orientation == value) return;
1663 fd->user_orientation = value;
1664 file_data_increment_version(fd);
1665 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1670 * file_data - operates on the given fd
1671 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1675 /* return list of sidecar file extensions in a string */
1676 gchar *file_data_sc_list_to_string(FileData *fd)
1679 GString *result = g_string_new("");
1681 work = fd->sidecar_files;
1684 FileData *sfd = work->data;
1686 result = g_string_append(result, "+ ");
1687 result = g_string_append(result, sfd->extension);
1689 if (work) result = g_string_append_c(result, ' ');
1692 return g_string_free(result, FALSE);
1698 * add FileDataChangeInfo (see typedefs.h) for the given operation
1699 * uses file_data_add_change_info
1701 * fails if the fd->change already exists - change operations can't run in parallel
1702 * fd->change_info works as a lock
1704 * dest can be NULL - in this case the current name is used for now, it will
1709 FileDataChangeInfo types:
1711 MOVE - path is changed, name may be changed too
1712 RENAME - path remains unchanged, name is changed
1713 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1714 sidecar names are changed too, extensions are not changed
1716 UPDATE - file size, date or grouping has been changed
1719 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1721 FileDataChangeInfo *fdci;
1723 if (fd->change) return FALSE;
1725 fdci = g_new0(FileDataChangeInfo, 1);
1730 fdci->source = g_strdup(src);
1732 fdci->source = g_strdup(fd->path);
1735 fdci->dest = g_strdup(dest);
1742 static void file_data_planned_change_remove(FileData *fd)
1744 if (file_data_planned_change_hash &&
1745 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1747 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1749 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1750 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1751 file_data_unref(fd);
1752 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1754 g_hash_table_destroy(file_data_planned_change_hash);
1755 file_data_planned_change_hash = NULL;
1756 DEBUG_1("planned change: empty");
1763 void file_data_free_ci(FileData *fd)
1765 FileDataChangeInfo *fdci = fd->change;
1769 file_data_planned_change_remove(fd);
1771 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1773 g_free(fdci->source);
1781 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1783 FileDataChangeInfo *fdci = fd->change;
1785 fdci->regroup_when_finished = enable;
1788 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1792 if (fd->parent) fd = fd->parent;
1794 if (fd->change) return FALSE;
1796 work = fd->sidecar_files;
1799 FileData *sfd = work->data;
1801 if (sfd->change) return FALSE;
1805 file_data_add_ci(fd, type, NULL, NULL);
1807 work = fd->sidecar_files;
1810 FileData *sfd = work->data;
1812 file_data_add_ci(sfd, type, NULL, NULL);
1819 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1823 if (fd->parent) fd = fd->parent;
1825 if (!fd->change || fd->change->type != type) return FALSE;
1827 work = fd->sidecar_files;
1830 FileData *sfd = work->data;
1832 if (!sfd->change || sfd->change->type != type) return FALSE;
1840 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1842 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1843 file_data_sc_update_ci_copy(fd, dest_path);
1847 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1849 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1850 file_data_sc_update_ci_move(fd, dest_path);
1854 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1856 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1857 file_data_sc_update_ci_rename(fd, dest_path);
1861 gboolean file_data_sc_add_ci_delete(FileData *fd)
1863 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1866 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1868 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1869 file_data_sc_update_ci_unspecified(fd, dest_path);
1873 gboolean file_data_add_ci_write_metadata(FileData *fd)
1875 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1878 void file_data_sc_free_ci(FileData *fd)
1882 if (fd->parent) fd = fd->parent;
1884 file_data_free_ci(fd);
1886 work = fd->sidecar_files;
1889 FileData *sfd = work->data;
1891 file_data_free_ci(sfd);
1896 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1899 gboolean ret = TRUE;
1904 FileData *fd = work->data;
1906 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1913 static void file_data_sc_revert_ci_list(GList *fd_list)
1920 FileData *fd = work->data;
1922 file_data_sc_free_ci(fd);
1927 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1934 FileData *fd = work->data;
1936 if (!func(fd, dest))
1938 file_data_sc_revert_ci_list(work->prev);
1947 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1949 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1952 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1954 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1957 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1959 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1962 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1964 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1967 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1970 gboolean ret = TRUE;
1975 FileData *fd = work->data;
1977 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1984 void file_data_free_ci_list(GList *fd_list)
1991 FileData *fd = work->data;
1993 file_data_free_ci(fd);
1998 void file_data_sc_free_ci_list(GList *fd_list)
2005 FileData *fd = work->data;
2007 file_data_sc_free_ci(fd);
2013 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2014 * fails if fd->change does not exist or the change type does not match
2017 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2019 FileDataChangeType type = fd->change->type;
2021 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2025 if (!file_data_planned_change_hash)
2026 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2028 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2030 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2031 g_hash_table_remove(file_data_planned_change_hash, old_path);
2032 file_data_unref(fd);
2035 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
2040 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2041 g_hash_table_remove(file_data_planned_change_hash, new_path);
2042 file_data_unref(ofd);
2045 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2047 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2052 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2054 gchar *old_path = fd->change->dest;
2056 fd->change->dest = g_strdup(dest_path);
2057 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2061 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2063 const gchar *extension = extension_from_path(fd->change->source);
2064 gchar *base = remove_extension_from_path(dest_path);
2065 gchar *old_path = fd->change->dest;
2067 fd->change->dest = g_strconcat(base, extension, NULL);
2068 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2074 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2077 gchar *dest_path_full = NULL;
2079 if (fd->parent) fd = fd->parent;
2083 dest_path = fd->path;
2085 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2087 gchar *dir = remove_level_from_path(fd->path);
2089 dest_path_full = g_build_filename(dir, dest_path, NULL);
2091 dest_path = dest_path_full;
2093 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2095 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2096 dest_path = dest_path_full;
2099 file_data_update_ci_dest(fd, dest_path);
2101 work = fd->sidecar_files;
2104 FileData *sfd = work->data;
2106 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2110 g_free(dest_path_full);
2113 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2115 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2116 file_data_sc_update_ci(fd, dest_path);
2120 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2122 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2125 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2127 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2130 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2132 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2135 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2137 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2140 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2142 gboolean (*func)(FileData *, const gchar *))
2145 gboolean ret = TRUE;
2150 FileData *fd = work->data;
2152 if (!func(fd, dest)) ret = FALSE;
2159 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2161 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2164 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2166 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2169 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2171 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2176 * verify source and dest paths - dest image exists, etc.
2177 * it should detect all possible problems with the planned operation
2180 gint file_data_verify_ci(FileData *fd)
2182 gint ret = CHANGE_OK;
2187 DEBUG_1("Change checked: no change info: %s", fd->path);
2191 if (!isname(fd->path))
2193 /* this probably should not happen */
2194 ret |= CHANGE_NO_SRC;
2195 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2199 dir = remove_level_from_path(fd->path);
2201 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2202 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2203 fd->change->type != FILEDATA_CHANGE_RENAME &&
2204 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2207 ret |= CHANGE_WARN_UNSAVED_META;
2208 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2211 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2212 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2213 !access_file(fd->path, R_OK))
2215 ret |= CHANGE_NO_READ_PERM;
2216 DEBUG_1("Change checked: no read permission: %s", fd->path);
2218 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2219 !access_file(dir, W_OK))
2221 ret |= CHANGE_NO_WRITE_PERM_DIR;
2222 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2224 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2225 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2226 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2227 !access_file(fd->path, W_OK))
2229 ret |= CHANGE_WARN_NO_WRITE_PERM;
2230 DEBUG_1("Change checked: no write permission: %s", fd->path);
2232 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2233 - that means that there are no hard errors and warnings can be disabled
2234 - the destination is determined during the check
2236 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2238 /* determine destination file */
2239 gboolean have_dest = FALSE;
2240 gchar *dest_dir = NULL;
2242 if (options->metadata.save_in_image_file)
2244 if (file_data_can_write_directly(fd))
2246 /* we can write the file directly */
2247 if (access_file(fd->path, W_OK))
2253 if (options->metadata.warn_on_write_problems)
2255 ret |= CHANGE_WARN_NO_WRITE_PERM;
2256 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2260 else if (file_data_can_write_sidecar(fd))
2262 /* we can write sidecar */
2263 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2264 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2266 file_data_update_ci_dest(fd, sidecar);
2271 if (options->metadata.warn_on_write_problems)
2273 ret |= CHANGE_WARN_NO_WRITE_PERM;
2274 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2283 /* write private metadata file under ~/.geeqie */
2285 /* If an existing metadata file exists, we will try writing to
2286 * it's location regardless of the user's preference.
2288 gchar *metadata_path = NULL;
2290 /* but ignore XMP if we are not able to write it */
2291 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2293 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2295 if (metadata_path && !access_file(metadata_path, W_OK))
2297 g_free(metadata_path);
2298 metadata_path = NULL;
2305 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2306 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2308 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2310 metadata_path = g_build_filename(dest_dir, filename, NULL);
2314 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2316 file_data_update_ci_dest(fd, metadata_path);
2321 ret |= CHANGE_NO_WRITE_PERM_DEST;
2322 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2324 g_free(metadata_path);
2329 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2334 same = (strcmp(fd->path, fd->change->dest) == 0);
2338 const gchar *dest_ext = extension_from_path(fd->change->dest);
2339 if (!dest_ext) dest_ext = "";
2341 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2343 ret |= CHANGE_WARN_CHANGED_EXT;
2344 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2349 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2351 ret |= CHANGE_WARN_SAME;
2352 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2356 dest_dir = remove_level_from_path(fd->change->dest);
2358 if (!isdir(dest_dir))
2360 ret |= CHANGE_NO_DEST_DIR;
2361 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2363 else if (!access_file(dest_dir, W_OK))
2365 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2366 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2370 if (isfile(fd->change->dest))
2372 if (!access_file(fd->change->dest, W_OK))
2374 ret |= CHANGE_NO_WRITE_PERM_DEST;
2375 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2379 ret |= CHANGE_WARN_DEST_EXISTS;
2380 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2383 else if (isdir(fd->change->dest))
2385 ret |= CHANGE_DEST_EXISTS;
2386 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2393 fd->change->error = ret;
2394 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2401 gint file_data_sc_verify_ci(FileData *fd)
2406 ret = file_data_verify_ci(fd);
2408 work = fd->sidecar_files;
2411 FileData *sfd = work->data;
2413 ret |= file_data_verify_ci(sfd);
2420 gchar *file_data_get_error_string(gint error)
2422 GString *result = g_string_new("");
2424 if (error & CHANGE_NO_SRC)
2426 if (result->len > 0) g_string_append(result, ", ");
2427 g_string_append(result, _("file or directory does not exist"));
2430 if (error & CHANGE_DEST_EXISTS)
2432 if (result->len > 0) g_string_append(result, ", ");
2433 g_string_append(result, _("destination already exists"));
2436 if (error & CHANGE_NO_WRITE_PERM_DEST)
2438 if (result->len > 0) g_string_append(result, ", ");
2439 g_string_append(result, _("destination can't be overwritten"));
2442 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2444 if (result->len > 0) g_string_append(result, ", ");
2445 g_string_append(result, _("destination directory is not writable"));
2448 if (error & CHANGE_NO_DEST_DIR)
2450 if (result->len > 0) g_string_append(result, ", ");
2451 g_string_append(result, _("destination directory does not exist"));
2454 if (error & CHANGE_NO_WRITE_PERM_DIR)
2456 if (result->len > 0) g_string_append(result, ", ");
2457 g_string_append(result, _("source directory is not writable"));
2460 if (error & CHANGE_NO_READ_PERM)
2462 if (result->len > 0) g_string_append(result, ", ");
2463 g_string_append(result, _("no read permission"));
2466 if (error & CHANGE_WARN_NO_WRITE_PERM)
2468 if (result->len > 0) g_string_append(result, ", ");
2469 g_string_append(result, _("file is readonly"));
2472 if (error & CHANGE_WARN_DEST_EXISTS)
2474 if (result->len > 0) g_string_append(result, ", ");
2475 g_string_append(result, _("destination already exists and will be overwritten"));
2478 if (error & CHANGE_WARN_SAME)
2480 if (result->len > 0) g_string_append(result, ", ");
2481 g_string_append(result, _("source and destination are the same"));
2484 if (error & CHANGE_WARN_CHANGED_EXT)
2486 if (result->len > 0) g_string_append(result, ", ");
2487 g_string_append(result, _("source and destination have different extension"));
2490 if (error & CHANGE_WARN_UNSAVED_META)
2492 if (result->len > 0) g_string_append(result, ", ");
2493 g_string_append(result, _("there are unsaved metadata changes for the file"));
2496 return g_string_free(result, FALSE);
2499 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2502 gint all_errors = 0;
2503 gint common_errors = ~0;
2508 if (!list) return 0;
2510 num = g_list_length(list);
2511 errors = g_new(int, num);
2522 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2523 all_errors |= error;
2524 common_errors &= error;
2531 if (desc && all_errors)
2534 GString *result = g_string_new("");
2538 gchar *str = file_data_get_error_string(common_errors);
2539 g_string_append(result, str);
2540 g_string_append(result, "\n");
2554 error = errors[i] & ~common_errors;
2558 gchar *str = file_data_get_error_string(error);
2559 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2564 *desc = g_string_free(result, FALSE);
2573 * perform the change described by FileFataChangeInfo
2574 * it is used for internal operations,
2575 * this function actually operates with files on the filesystem
2576 * it should implement safe delete
2579 static gboolean file_data_perform_move(FileData *fd)
2581 g_assert(!strcmp(fd->change->source, fd->path));
2582 return move_file(fd->change->source, fd->change->dest);
2585 static gboolean file_data_perform_copy(FileData *fd)
2587 g_assert(!strcmp(fd->change->source, fd->path));
2588 return copy_file(fd->change->source, fd->change->dest);
2591 static gboolean file_data_perform_delete(FileData *fd)
2593 if (isdir(fd->path) && !islink(fd->path))
2594 return rmdir_utf8(fd->path);
2596 if (options->file_ops.safe_delete_enable)
2597 return file_util_safe_unlink(fd->path);
2599 return unlink_file(fd->path);
2602 gboolean file_data_perform_ci(FileData *fd)
2604 FileDataChangeType type = fd->change->type;
2608 case FILEDATA_CHANGE_MOVE:
2609 return file_data_perform_move(fd);
2610 case FILEDATA_CHANGE_COPY:
2611 return file_data_perform_copy(fd);
2612 case FILEDATA_CHANGE_RENAME:
2613 return file_data_perform_move(fd); /* the same as move */
2614 case FILEDATA_CHANGE_DELETE:
2615 return file_data_perform_delete(fd);
2616 case FILEDATA_CHANGE_WRITE_METADATA:
2617 return metadata_write_perform(fd);
2618 case FILEDATA_CHANGE_UNSPECIFIED:
2619 /* nothing to do here */
2627 gboolean file_data_sc_perform_ci(FileData *fd)
2630 gboolean ret = TRUE;
2631 FileDataChangeType type = fd->change->type;
2633 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2635 work = fd->sidecar_files;
2638 FileData *sfd = work->data;
2640 if (!file_data_perform_ci(sfd)) ret = FALSE;
2644 if (!file_data_perform_ci(fd)) ret = FALSE;
2650 * updates FileData structure according to FileDataChangeInfo
2653 gboolean file_data_apply_ci(FileData *fd)
2655 FileDataChangeType type = fd->change->type;
2658 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2660 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2661 file_data_planned_change_remove(fd);
2663 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2665 /* this change overwrites another file which is already known to other modules
2666 renaming fd would create duplicate FileData structure
2667 the best thing we can do is nothing
2668 FIXME: maybe we could copy stuff like marks
2670 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2674 file_data_set_path(fd, fd->change->dest);
2677 file_data_increment_version(fd);
2678 file_data_send_notification(fd, NOTIFY_CHANGE);
2683 gboolean file_data_sc_apply_ci(FileData *fd)
2686 FileDataChangeType type = fd->change->type;
2688 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2690 work = fd->sidecar_files;
2693 FileData *sfd = work->data;
2695 file_data_apply_ci(sfd);
2699 file_data_apply_ci(fd);
2704 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2707 if (fd->parent) fd = fd->parent;
2708 if (!g_list_find(list, fd)) return FALSE;
2710 work = fd->sidecar_files;
2713 if (!g_list_find(list, work->data)) return FALSE;
2719 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2724 /* change partial groups to independent files */
2729 FileData *fd = work->data;
2732 if (!file_data_list_contains_whole_group(list, fd))
2734 file_data_disable_grouping(fd, TRUE);
2737 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2743 /* remove sidecars from the list,
2744 they can be still acessed via main_fd->sidecar_files */
2748 FileData *fd = work->data;
2752 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2754 out = g_list_prepend(out, file_data_ref(fd));
2758 filelist_free(list);
2759 out = g_list_reverse(out);
2769 * notify other modules about the change described by FileDataChangeInfo
2772 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2773 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2774 implementation in view_file_list.c */
2777 typedef struct _NotifyIdleData NotifyIdleData;
2779 struct _NotifyIdleData {
2785 typedef struct _NotifyData NotifyData;
2787 struct _NotifyData {
2788 FileDataNotifyFunc func;
2790 NotifyPriority priority;
2793 static GList *notify_func_list = NULL;
2795 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2797 NotifyData *nda = (NotifyData *)a;
2798 NotifyData *ndb = (NotifyData *)b;
2800 if (nda->priority < ndb->priority) return -1;
2801 if (nda->priority > ndb->priority) return 1;
2805 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2808 GList *work = notify_func_list;
2812 NotifyData *nd = (NotifyData *)work->data;
2814 if (nd->func == func && nd->data == data)
2816 g_warning("Notify func already registered");
2822 nd = g_new(NotifyData, 1);
2825 nd->priority = priority;
2827 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2828 DEBUG_2("Notify func registered: %p", nd);
2833 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2835 GList *work = notify_func_list;
2839 NotifyData *nd = (NotifyData *)work->data;
2841 if (nd->func == func && nd->data == data)
2843 notify_func_list = g_list_delete_link(notify_func_list, work);
2845 DEBUG_2("Notify func unregistered: %p", nd);
2851 g_warning("Notify func not found");
2856 gboolean file_data_send_notification_idle_cb(gpointer data)
2858 NotifyIdleData *nid = (NotifyIdleData *)data;
2859 GList *work = notify_func_list;
2863 NotifyData *nd = (NotifyData *)work->data;
2865 nd->func(nid->fd, nid->type, nd->data);
2868 file_data_unref(nid->fd);
2873 void file_data_send_notification(FileData *fd, NotifyType type)
2875 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
2876 nid->fd = file_data_ref(fd);
2878 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
2881 static GHashTable *file_data_monitor_pool = NULL;
2882 static guint realtime_monitor_id = 0; /* event source id */
2884 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2888 file_data_check_changed_files(fd);
2890 DEBUG_1("monitor %s", fd->path);
2893 static gboolean realtime_monitor_cb(gpointer data)
2895 if (!options->update_on_time_change) return TRUE;
2896 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2900 gboolean file_data_register_real_time_monitor(FileData *fd)
2906 if (!file_data_monitor_pool)
2907 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2909 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2911 DEBUG_1("Register realtime %d %s", count, fd->path);
2914 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2916 if (!realtime_monitor_id)
2918 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2924 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2928 g_assert(file_data_monitor_pool);
2930 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2932 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2934 g_assert(count > 0);
2939 g_hash_table_remove(file_data_monitor_pool, fd);
2941 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2943 file_data_unref(fd);
2945 if (g_hash_table_size(file_data_monitor_pool) == 0)
2947 g_source_remove(realtime_monitor_id);
2948 realtime_monitor_id = 0;
2954 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */