2 * Copyright (C) 2006 John Ellis
3 * Copyright (C) 2008 - 2016 The Geeqie Team
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 #include "filefilter.h"
27 #include "thumb_standard.h"
28 #include "ui_fileops.h"
31 #include "histogram.h"
38 gint global_file_data_count = 0;
41 static GHashTable *file_data_pool = NULL;
42 static GHashTable *file_data_planned_change_hash = NULL;
44 static gint sidecar_file_priority(const gchar *extension);
45 static void file_data_check_sidecars(const GList *basename_list);
46 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
49 static SortType filelist_sort_method = SORT_NONE;
50 static gboolean filelist_sort_ascend = TRUE;
53 *-----------------------------------------------------------------------------
54 * text conversion utils
55 *-----------------------------------------------------------------------------
58 gchar *text_from_size(gint64 size)
64 /* what I would like to use is printf("%'d", size)
65 * BUT: not supported on every libc :(
69 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
70 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
74 a = g_strdup_printf("%d", (guint)size);
80 b = g_new(gchar, l + n + 1);
105 gchar *text_from_size_abrev(gint64 size)
107 if (size < (gint64)1024)
109 return g_strdup_printf(_("%d bytes"), (gint)size);
111 if (size < (gint64)1048576)
113 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
115 if (size < (gint64)1073741824)
117 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
120 /* to avoid overflowing the gdouble, do division in two steps */
122 return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
125 /* note: returned string is valid until next call to text_from_time() */
126 const gchar *text_from_time(time_t t)
128 static gchar *ret = NULL;
132 GError *error = NULL;
134 btime = localtime(&t);
136 /* the %x warning about 2 digit years is not an error */
137 buflen = strftime(buf, sizeof(buf), "%x %X", btime);
138 if (buflen < 1) return "";
141 ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
144 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
153 *-----------------------------------------------------------------------------
154 * changed files detection and notification
155 *-----------------------------------------------------------------------------
158 void file_data_increment_version(FileData *fd)
164 fd->parent->version++;
165 fd->parent->valid_marks = 0;
169 static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st)
171 if (fd->size != st->st_size ||
172 fd->date != st->st_mtime)
174 fd->size = st->st_size;
175 fd->date = st->st_mtime;
176 fd->cdate = st->st_ctime;
177 fd->mode = st->st_mode;
178 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
179 fd->thumb_pixbuf = NULL;
180 file_data_increment_version(fd);
181 file_data_send_notification(fd, NOTIFY_REREAD);
187 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
189 gboolean ret = FALSE;
192 ret = file_data_check_changed_single_file(fd, st);
194 work = fd->sidecar_files;
197 FileData *sfd = work->data;
201 if (!stat_utf8(sfd->path, &st))
206 file_data_disconnect_sidecar_file(fd, sfd);
208 file_data_increment_version(sfd);
209 file_data_send_notification(sfd, NOTIFY_REREAD);
210 file_data_unref(sfd);
214 ret |= file_data_check_changed_files_recursive(sfd, &st);
220 gboolean file_data_check_changed_files(FileData *fd)
222 gboolean ret = FALSE;
225 if (fd->parent) fd = fd->parent;
227 if (!stat_utf8(fd->path, &st))
231 FileData *sfd = NULL;
233 /* parent is missing, we have to rebuild whole group */
238 /* file_data_disconnect_sidecar_file might delete the file,
239 we have to keep the reference to prevent this */
240 sidecars = filelist_copy(fd->sidecar_files);
248 file_data_disconnect_sidecar_file(fd, sfd);
250 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
251 /* now we can release the sidecars */
252 filelist_free(sidecars);
253 file_data_increment_version(fd);
254 file_data_send_notification(fd, NOTIFY_REREAD);
259 ret |= file_data_check_changed_files_recursive(fd, &st);
266 *-----------------------------------------------------------------------------
267 * file name, extension, sorting, ...
268 *-----------------------------------------------------------------------------
271 static void file_data_set_collate_keys(FileData *fd)
273 gchar *caseless_name;
276 valid_name = g_filename_display_name(fd->name);
277 caseless_name = g_utf8_casefold(valid_name, -1);
279 g_free(fd->collate_key_name);
280 g_free(fd->collate_key_name_nocase);
282 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
283 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
286 g_free(caseless_name);
289 static void file_data_set_path(FileData *fd, const gchar *path)
291 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
292 g_assert(file_data_pool);
296 if (fd->original_path)
298 g_hash_table_remove(file_data_pool, fd->original_path);
299 g_free(fd->original_path);
302 g_assert(!g_hash_table_lookup(file_data_pool, path));
304 fd->original_path = g_strdup(path);
305 g_hash_table_insert(file_data_pool, fd->original_path, fd);
307 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
309 fd->path = g_strdup(path);
311 fd->extension = fd->name + 1;
312 file_data_set_collate_keys(fd);
316 fd->path = g_strdup(path);
317 fd->name = filename_from_path(fd->path);
319 if (strcmp(fd->name, "..") == 0)
321 gchar *dir = remove_level_from_path(path);
323 fd->path = remove_level_from_path(dir);
326 fd->extension = fd->name + 2;
327 file_data_set_collate_keys(fd);
330 else if (strcmp(fd->name, ".") == 0)
333 fd->path = remove_level_from_path(path);
335 fd->extension = fd->name + 1;
336 file_data_set_collate_keys(fd);
340 fd->extension = registered_extension_from_path(fd->path);
341 if (fd->extension == NULL)
343 fd->extension = fd->name + strlen(fd->name);
346 fd->sidecar_priority = sidecar_file_priority(fd->extension);
347 file_data_set_collate_keys(fd);
351 *-----------------------------------------------------------------------------
352 * create or reuse Filedata
353 *-----------------------------------------------------------------------------
356 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
360 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
362 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
365 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
367 fd = g_hash_table_lookup(file_data_pool, path_utf8);
373 if (!fd && file_data_planned_change_hash)
375 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
378 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
379 if (!isfile(fd->path))
382 file_data_apply_ci(fd);
395 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
398 changed = file_data_check_changed_single_file(fd, st);
400 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
405 fd = g_new0(FileData, 1);
406 #ifdef DEBUG_FILEDATA
407 global_file_data_count++;
408 DEBUG_2("file data count++: %d", global_file_data_count);
411 fd->size = st->st_size;
412 fd->date = st->st_mtime;
413 fd->cdate = st->st_ctime;
414 fd->mode = st->st_mode;
416 fd->magick = FD_MAGICK;
418 if (disable_sidecars) fd->disable_grouping = TRUE;
420 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
425 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
427 gchar *path_utf8 = path_to_utf8(path);
428 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
434 FileData *file_data_new_simple(const gchar *path_utf8)
439 if (!stat_utf8(path_utf8, &st))
445 fd = g_hash_table_lookup(file_data_pool, path_utf8);
446 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
455 void init_exif_time_data(GList *files)
458 DEBUG_1("%s init_exif_time_data: ...", get_exec_time());
470 void read_exif_time_data(FileData *file)
472 if (file->exifdate > 0)
474 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
478 file->exif = exif_read_fd(file);
482 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
483 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), file, file->path);
488 uint year, month, day, hour, min, sec;
490 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
491 time_str.tm_year = year - 1900;
492 time_str.tm_mon = month - 1;
493 time_str.tm_mday = day;
494 time_str.tm_hour = hour;
495 time_str.tm_min = min;
496 time_str.tm_sec = sec;
497 time_str.tm_isdst = 0;
499 file->exifdate = mktime(&time_str);
505 void set_exif_time_data(GList *files)
507 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
511 FileData *file = files->data;
513 read_exif_time_data(file);
518 FileData *file_data_new_no_grouping(const gchar *path_utf8)
522 if (!stat_utf8(path_utf8, &st))
528 return file_data_new(path_utf8, &st, TRUE);
531 FileData *file_data_new_dir(const gchar *path_utf8)
535 if (!stat_utf8(path_utf8, &st))
541 /* dir or non-existing yet */
542 g_assert(S_ISDIR(st.st_mode));
544 return file_data_new(path_utf8, &st, TRUE);
548 *-----------------------------------------------------------------------------
550 *-----------------------------------------------------------------------------
553 #ifdef DEBUG_FILEDATA
554 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
556 FileData *file_data_ref(FileData *fd)
559 if (fd == NULL) return NULL;
560 if (fd->magick != FD_MAGICK)
561 #ifdef DEBUG_FILEDATA
562 DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
564 DEBUG_0("fd magick mismatch fd=%p", fd);
566 g_assert(fd->magick == FD_MAGICK);
569 #ifdef DEBUG_FILEDATA
570 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", fd, fd->ref, fd->path, file, line);
572 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
577 static void file_data_free(FileData *fd)
579 g_assert(fd->magick == FD_MAGICK);
580 g_assert(fd->ref == 0);
581 g_assert(!fd->locked);
583 #ifdef DEBUG_FILEDATA
584 global_file_data_count--;
585 DEBUG_2("file data count--: %d", global_file_data_count);
588 metadata_cache_free(fd);
589 g_hash_table_remove(file_data_pool, fd->original_path);
592 g_free(fd->original_path);
593 g_free(fd->collate_key_name);
594 g_free(fd->collate_key_name_nocase);
595 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
596 histmap_free(fd->histmap);
598 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
600 file_data_change_info_free(NULL, fd);
605 * \brief Checks if the FileData is referenced
607 * Checks the refcount and whether the FileData is locked.
609 static gboolean file_data_check_has_ref(FileData *fd)
611 return fd->ref > 0 || fd->locked;
615 * \brief Consider freeing a FileData.
617 * This function will free a FileData and its children provided that neither its parent nor it has
618 * a positive refcount, and provided that neither is locked.
620 static void file_data_consider_free(FileData *fd)
623 FileData *parent = fd->parent ? fd->parent : fd;
625 g_assert(fd->magick == FD_MAGICK);
626 if (file_data_check_has_ref(fd)) return;
627 if (file_data_check_has_ref(parent)) return;
629 work = parent->sidecar_files;
632 FileData *sfd = work->data;
633 if (file_data_check_has_ref(sfd)) return;
637 /* Neither the parent nor the siblings are referenced, so we can free everything */
638 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
639 fd->path, fd->parent ? parent->path : "-");
641 work = parent->sidecar_files;
644 FileData *sfd = work->data;
649 g_list_free(parent->sidecar_files);
650 parent->sidecar_files = NULL;
652 file_data_free(parent);
655 #ifdef DEBUG_FILEDATA
656 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
658 void file_data_unref(FileData *fd)
661 if (fd == NULL) return;
662 if (fd->magick != FD_MAGICK)
663 #ifdef DEBUG_FILEDATA
664 DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
666 DEBUG_0("fd magick mismatch fd=%p", fd);
668 g_assert(fd->magick == FD_MAGICK);
671 #ifdef DEBUG_FILEDATA
672 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", fd, fd->ref, fd->locked, fd->path,
675 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
678 // Free FileData if it's no longer ref'd
679 file_data_consider_free(fd);
683 * \brief Lock the FileData in memory.
685 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
686 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
687 * even if the code would continue to function properly even if the FileData were freed. Code that
688 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
690 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
691 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
693 void file_data_lock(FileData *fd)
695 if (fd == NULL) return;
696 if (fd->magick != FD_MAGICK) DEBUG_0("fd magick mismatch fd=%p", fd);
698 g_assert(fd->magick == FD_MAGICK);
701 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
705 * \brief Reset the maintain-FileData-in-memory lock
707 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
708 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
709 * keeping it from being freed.
711 void file_data_unlock(FileData *fd)
713 if (fd == NULL) return;
714 if (fd->magick != FD_MAGICK) DEBUG_0("fd magick mismatch fd=%p", fd);
716 g_assert(fd->magick == FD_MAGICK);
719 // Free FileData if it's no longer ref'd
720 file_data_consider_free(fd);
724 * \brief Lock all of the FileDatas in the provided list
726 * \see file_data_lock(FileData)
728 void file_data_lock_list(GList *list)
735 FileData *fd = work->data;
742 * \brief Unlock all of the FileDatas in the provided list
744 * \see file_data_unlock(FileData)
746 void file_data_unlock_list(GList *list)
753 FileData *fd = work->data;
755 file_data_unlock(fd);
760 *-----------------------------------------------------------------------------
761 * sidecar file info struct
762 *-----------------------------------------------------------------------------
765 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
767 const FileData *fda = a;
768 const FileData *fdb = b;
770 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
771 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
773 return strcmp(fdb->extension, fda->extension);
777 static gint sidecar_file_priority(const gchar *extension)
782 if (extension == NULL)
785 work = sidecar_ext_get_list();
788 gchar *ext = work->data;
791 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
797 static void file_data_check_sidecars(const GList *basename_list)
799 /* basename_list contains the new group - first is the parent, then sorted sidecars */
800 /* all files in the list have ref count > 0 */
803 GList *s_work, *new_sidecars;
806 if (!basename_list) return;
809 DEBUG_2("basename start");
810 work = basename_list;
813 FileData *fd = work->data;
815 g_assert(fd->magick == FD_MAGICK);
816 DEBUG_2("basename: %p %s", fd, fd->name);
819 g_assert(fd->parent->magick == FD_MAGICK);
820 DEBUG_2(" parent: %p", fd->parent);
822 s_work = fd->sidecar_files;
825 FileData *sfd = s_work->data;
826 s_work = s_work->next;
827 g_assert(sfd->magick == FD_MAGICK);
828 DEBUG_2(" sidecar: %p %s", sfd, sfd->name);
831 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
834 parent_fd = basename_list->data;
836 /* check if the second and next entries of basename_list are already connected
837 as sidecars of the first entry (parent_fd) */
838 work = basename_list->next;
839 s_work = parent_fd->sidecar_files;
841 while (work && s_work)
843 if (work->data != s_work->data) break;
845 s_work = s_work->next;
848 if (!work && !s_work)
850 DEBUG_2("basename no change");
851 return; /* no change in grouping */
854 /* we have to regroup it */
856 /* first, disconnect everything and send notification*/
858 work = basename_list;
861 FileData *fd = work->data;
863 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
867 FileData *old_parent = fd->parent;
868 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
869 file_data_ref(old_parent);
870 file_data_disconnect_sidecar_file(old_parent, fd);
871 file_data_send_notification(old_parent, NOTIFY_REREAD);
872 file_data_unref(old_parent);
875 while (fd->sidecar_files)
877 FileData *sfd = fd->sidecar_files->data;
878 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
880 file_data_disconnect_sidecar_file(fd, sfd);
881 file_data_send_notification(sfd, NOTIFY_REREAD);
882 file_data_unref(sfd);
884 file_data_send_notification(fd, NOTIFY_GROUPING);
886 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
889 /* now we can form the new group */
890 work = basename_list->next;
894 FileData *sfd = work->data;
895 g_assert(sfd->magick == FD_MAGICK);
896 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
897 sfd->parent = parent_fd;
898 new_sidecars = g_list_prepend(new_sidecars, sfd);
901 g_assert(parent_fd->sidecar_files == NULL);
902 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
903 DEBUG_1("basename group changed for %s", parent_fd->path);
907 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
909 g_assert(target->magick == FD_MAGICK);
910 g_assert(sfd->magick == FD_MAGICK);
911 g_assert(g_list_find(target->sidecar_files, sfd));
913 file_data_ref(target);
916 g_assert(sfd->parent == target);
918 file_data_increment_version(sfd); /* increments both sfd and target */
920 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
923 file_data_unref(target);
924 file_data_unref(sfd);
927 /* disables / enables grouping for particular file, sends UPDATE notification */
928 void file_data_disable_grouping(FileData *fd, gboolean disable)
930 if (!fd->disable_grouping == !disable) return;
932 fd->disable_grouping = !!disable;
938 FileData *parent = file_data_ref(fd->parent);
939 file_data_disconnect_sidecar_file(parent, fd);
940 file_data_send_notification(parent, NOTIFY_GROUPING);
941 file_data_unref(parent);
943 else if (fd->sidecar_files)
945 GList *sidecar_files = filelist_copy(fd->sidecar_files);
946 GList *work = sidecar_files;
949 FileData *sfd = work->data;
951 file_data_disconnect_sidecar_file(fd, sfd);
952 file_data_send_notification(sfd, NOTIFY_GROUPING);
954 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
955 filelist_free(sidecar_files);
959 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
964 file_data_increment_version(fd);
965 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
967 file_data_send_notification(fd, NOTIFY_GROUPING);
970 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
977 FileData *fd = work->data;
979 file_data_disable_grouping(fd, disable);
987 *-----------------------------------------------------------------------------
989 *-----------------------------------------------------------------------------
993 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
996 if (!filelist_sort_ascend)
1003 switch (filelist_sort_method)
1008 if (fa->size < fb->size) return -1;
1009 if (fa->size > fb->size) return 1;
1010 /* fall back to name */
1013 if (fa->date < fb->date) return -1;
1014 if (fa->date > fb->date) return 1;
1015 /* fall back to name */
1018 if (fa->cdate < fb->cdate) return -1;
1019 if (fa->cdate > fb->cdate) return 1;
1020 /* fall back to name */
1023 if (fa->exifdate < fb->exifdate) return -1;
1024 if (fa->exifdate > fb->exifdate) return 1;
1025 /* fall back to name */
1027 #ifdef HAVE_STRVERSCMP
1029 ret = strverscmp(fa->name, fb->name);
1030 if (ret != 0) return ret;
1037 if (options->file_sort.case_sensitive)
1038 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1040 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1042 if (ret != 0) return ret;
1044 /* do not return 0 unless the files are really the same
1045 file_data_pool ensures that original_path is unique
1047 return strcmp(fa->original_path, fb->original_path);
1050 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1052 filelist_sort_method = method;
1053 filelist_sort_ascend = ascend;
1054 return filelist_sort_compare_filedata(fa, fb);
1057 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1059 return filelist_sort_compare_filedata(a, b);
1062 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1064 filelist_sort_method = method;
1065 filelist_sort_ascend = ascend;
1066 return g_list_sort(list, cb);
1069 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1071 filelist_sort_method = method;
1072 filelist_sort_ascend = ascend;
1073 return g_list_insert_sorted(list, data, cb);
1076 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1078 if (method == SORT_EXIFTIME)
1080 set_exif_time_data(list);
1082 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1085 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
1087 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1091 *-----------------------------------------------------------------------------
1092 * basename hash - grouping of sidecars in filelist
1093 *-----------------------------------------------------------------------------
1097 static GHashTable *file_data_basename_hash_new(void)
1099 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1102 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1105 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1107 list = g_hash_table_lookup(basename_hash, basename);
1109 if (!g_list_find(list, fd))
1111 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1112 g_hash_table_insert(basename_hash, basename, list);
1121 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
1123 filelist_free((GList *)value);
1126 static void file_data_basename_hash_free(GHashTable *basename_hash)
1128 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1129 g_hash_table_destroy(basename_hash);
1133 *-----------------------------------------------------------------------------
1134 * handling sidecars in filelist
1135 *-----------------------------------------------------------------------------
1138 static GList *filelist_filter_out_sidecars(GList *flist)
1140 GList *work = flist;
1141 GList *flist_filtered = NULL;
1145 FileData *fd = work->data;
1148 if (fd->parent) /* remove fd's that are children */
1149 file_data_unref(fd);
1151 flist_filtered = g_list_prepend(flist_filtered, fd);
1155 return flist_filtered;
1158 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
1160 GList *basename_list = (GList *)value;
1161 file_data_check_sidecars(basename_list);
1165 static gboolean is_hidden_file(const gchar *name)
1167 if (name[0] != '.') return FALSE;
1168 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1173 *-----------------------------------------------------------------------------
1174 * the main filelist function
1175 *-----------------------------------------------------------------------------
1178 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1183 GList *dlist = NULL;
1184 GList *flist = NULL;
1185 gint (*stat_func)(const gchar *path, struct stat *buf);
1186 GHashTable *basename_hash = NULL;
1188 g_assert(files || dirs);
1190 if (files) *files = NULL;
1191 if (dirs) *dirs = NULL;
1193 pathl = path_from_utf8(dir_path);
1194 if (!pathl) return FALSE;
1196 dp = opendir(pathl);
1203 if (files) basename_hash = file_data_basename_hash_new();
1205 if (follow_symlinks)
1210 while ((dir = readdir(dp)) != NULL)
1212 struct stat ent_sbuf;
1213 const gchar *name = dir->d_name;
1216 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1219 filepath = g_build_filename(pathl, name, NULL);
1220 if (stat_func(filepath, &ent_sbuf) >= 0)
1222 if (S_ISDIR(ent_sbuf.st_mode))
1224 /* we ignore the .thumbnails dir for cleanliness */
1226 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1227 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1228 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1229 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1231 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1236 if (files && filter_name_exists(name))
1238 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1239 flist = g_list_prepend(flist, fd);
1240 if (fd->sidecar_priority && !fd->disable_grouping)
1242 file_data_basename_hash_insert(basename_hash, fd);
1249 if (errno == EOVERFLOW)
1251 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1261 if (dirs) *dirs = dlist;
1265 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1267 *files = filelist_filter_out_sidecars(flist);
1269 if (basename_hash) file_data_basename_hash_free(basename_hash);
1271 // Call a separate function to initialize the exif datestamps for the found files..
1272 if (files) init_exif_time_data(*files);
1277 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1279 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1282 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1284 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1287 FileData *file_data_new_group(const gchar *path_utf8)
1294 if (!stat_utf8(path_utf8, &st))
1300 if (S_ISDIR(st.st_mode))
1301 return file_data_new(path_utf8, &st, TRUE);
1303 dir = remove_level_from_path(path_utf8);
1305 filelist_read_real(dir, &files, NULL, TRUE);
1307 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1308 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1314 filelist_free(files);
1320 void filelist_free(GList *list)
1327 file_data_unref((FileData *)work->data);
1335 GList *filelist_copy(GList *list)
1337 GList *new_list = NULL;
1348 new_list = g_list_prepend(new_list, file_data_ref(fd));
1351 return g_list_reverse(new_list);
1354 GList *filelist_from_path_list(GList *list)
1356 GList *new_list = NULL;
1367 new_list = g_list_prepend(new_list, file_data_new_group(path));
1370 return g_list_reverse(new_list);
1373 GList *filelist_to_path_list(GList *list)
1375 GList *new_list = NULL;
1386 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1389 return g_list_reverse(new_list);
1392 GList *filelist_filter(GList *list, gboolean is_dir_list)
1396 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1401 FileData *fd = (FileData *)(work->data);
1402 const gchar *name = fd->name;
1404 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1405 (!is_dir_list && !filter_name_exists(name)) ||
1406 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1407 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1411 list = g_list_remove_link(list, link);
1412 file_data_unref(fd);
1423 *-----------------------------------------------------------------------------
1424 * filelist recursive
1425 *-----------------------------------------------------------------------------
1428 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1430 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1433 GList *filelist_sort_path(GList *list)
1435 return g_list_sort(list, filelist_sort_path_cb);
1438 static void filelist_recursive_append(GList **list, GList *dirs)
1445 FileData *fd = (FileData *)(work->data);
1449 if (filelist_read(fd, &f, &d))
1451 f = filelist_filter(f, FALSE);
1452 f = filelist_sort_path(f);
1453 *list = g_list_concat(*list, f);
1455 d = filelist_filter(d, TRUE);
1456 d = filelist_sort_path(d);
1457 filelist_recursive_append(list, d);
1465 GList *filelist_recursive(FileData *dir_fd)
1470 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1471 list = filelist_filter(list, FALSE);
1472 list = filelist_sort_path(list);
1474 d = filelist_filter(d, TRUE);
1475 d = filelist_sort_path(d);
1476 filelist_recursive_append(&list, d);
1483 *-----------------------------------------------------------------------------
1484 * file modification support
1485 *-----------------------------------------------------------------------------
1489 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1491 if (!fdci && fd) fdci = fd->change;
1495 g_free(fdci->source);
1500 if (fd) fd->change = NULL;
1503 static gboolean file_data_can_write_directly(FileData *fd)
1505 return filter_name_is_writable(fd->extension);
1508 static gboolean file_data_can_write_sidecar(FileData *fd)
1510 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1513 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1515 gchar *sidecar_path = NULL;
1518 if (!file_data_can_write_sidecar(fd)) return NULL;
1520 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1523 FileData *sfd = work->data;
1525 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
1527 sidecar_path = g_strdup(sfd->path);
1532 if (!existing_only && !sidecar_path)
1534 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1535 sidecar_path = g_strconcat(base, ".xmp", NULL);
1539 return sidecar_path;
1543 * marks and orientation
1546 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1547 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1548 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1549 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1551 gboolean file_data_get_mark(FileData *fd, gint n)
1553 gboolean valid = (fd->valid_marks & (1 << n));
1555 if (file_data_get_mark_func[n] && !valid)
1557 guint old = fd->marks;
1558 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1560 if (!value != !(fd->marks & (1 << n)))
1562 fd->marks = fd->marks ^ (1 << n);
1565 fd->valid_marks |= (1 << n);
1566 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1568 file_data_unref(fd);
1570 else if (!old && fd->marks)
1576 return !!(fd->marks & (1 << n));
1579 guint file_data_get_marks(FileData *fd)
1582 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1586 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1589 if (!value == !file_data_get_mark(fd, n)) return;
1591 if (file_data_set_mark_func[n])
1593 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1598 fd->marks = fd->marks ^ (1 << n);
1600 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1602 file_data_unref(fd);
1604 else if (!old && fd->marks)
1609 file_data_increment_version(fd);
1610 file_data_send_notification(fd, NOTIFY_MARKS);
1613 gboolean file_data_filter_marks(FileData *fd, guint filter)
1616 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1617 return ((fd->marks & filter) == filter);
1620 GList *file_data_filter_marks_list(GList *list, guint filter)
1627 FileData *fd = work->data;
1631 if (!file_data_filter_marks(fd, filter))
1633 list = g_list_remove_link(list, link);
1634 file_data_unref(fd);
1642 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1644 FileData *fd = value;
1645 file_data_increment_version(fd);
1646 file_data_send_notification(fd, NOTIFY_MARKS);
1649 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1651 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1653 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1655 file_data_get_mark_func[n] = get_mark_func;
1656 file_data_set_mark_func[n] = set_mark_func;
1657 file_data_mark_func_data[n] = data;
1658 file_data_destroy_mark_func[n] = notify;
1662 /* this effectively changes all known files */
1663 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1669 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1671 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1672 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1673 if (data) *data = file_data_mark_func_data[n];
1676 gint file_data_get_user_orientation(FileData *fd)
1678 return fd->user_orientation;
1681 void file_data_set_user_orientation(FileData *fd, gint value)
1683 if (fd->user_orientation == value) return;
1685 fd->user_orientation = value;
1686 file_data_increment_version(fd);
1687 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1692 * file_data - operates on the given fd
1693 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1697 /* return list of sidecar file extensions in a string */
1698 gchar *file_data_sc_list_to_string(FileData *fd)
1701 GString *result = g_string_new("");
1703 work = fd->sidecar_files;
1706 FileData *sfd = work->data;
1708 result = g_string_append(result, "+ ");
1709 result = g_string_append(result, sfd->extension);
1711 if (work) result = g_string_append_c(result, ' ');
1714 return g_string_free(result, FALSE);
1720 * add FileDataChangeInfo (see typedefs.h) for the given operation
1721 * uses file_data_add_change_info
1723 * fails if the fd->change already exists - change operations can't run in parallel
1724 * fd->change_info works as a lock
1726 * dest can be NULL - in this case the current name is used for now, it will
1731 FileDataChangeInfo types:
1733 MOVE - path is changed, name may be changed too
1734 RENAME - path remains unchanged, name is changed
1735 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1736 sidecar names are changed too, extensions are not changed
1738 UPDATE - file size, date or grouping has been changed
1741 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1743 FileDataChangeInfo *fdci;
1745 if (fd->change) return FALSE;
1747 fdci = g_new0(FileDataChangeInfo, 1);
1752 fdci->source = g_strdup(src);
1754 fdci->source = g_strdup(fd->path);
1757 fdci->dest = g_strdup(dest);
1764 static void file_data_planned_change_remove(FileData *fd)
1766 if (file_data_planned_change_hash &&
1767 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1769 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1771 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1772 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1773 file_data_unref(fd);
1774 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1776 g_hash_table_destroy(file_data_planned_change_hash);
1777 file_data_planned_change_hash = NULL;
1778 DEBUG_1("planned change: empty");
1785 void file_data_free_ci(FileData *fd)
1787 FileDataChangeInfo *fdci = fd->change;
1791 file_data_planned_change_remove(fd);
1793 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1795 g_free(fdci->source);
1803 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1805 FileDataChangeInfo *fdci = fd->change;
1807 fdci->regroup_when_finished = enable;
1810 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1814 if (fd->parent) fd = fd->parent;
1816 if (fd->change) return FALSE;
1818 work = fd->sidecar_files;
1821 FileData *sfd = work->data;
1823 if (sfd->change) return FALSE;
1827 file_data_add_ci(fd, type, NULL, NULL);
1829 work = fd->sidecar_files;
1832 FileData *sfd = work->data;
1834 file_data_add_ci(sfd, type, NULL, NULL);
1841 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1845 if (fd->parent) fd = fd->parent;
1847 if (!fd->change || fd->change->type != type) return FALSE;
1849 work = fd->sidecar_files;
1852 FileData *sfd = work->data;
1854 if (!sfd->change || sfd->change->type != type) return FALSE;
1862 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1864 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1865 file_data_sc_update_ci_copy(fd, dest_path);
1869 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1871 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1872 file_data_sc_update_ci_move(fd, dest_path);
1876 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1878 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1879 file_data_sc_update_ci_rename(fd, dest_path);
1883 gboolean file_data_sc_add_ci_delete(FileData *fd)
1885 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1888 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1890 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1891 file_data_sc_update_ci_unspecified(fd, dest_path);
1895 gboolean file_data_add_ci_write_metadata(FileData *fd)
1897 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1900 void file_data_sc_free_ci(FileData *fd)
1904 if (fd->parent) fd = fd->parent;
1906 file_data_free_ci(fd);
1908 work = fd->sidecar_files;
1911 FileData *sfd = work->data;
1913 file_data_free_ci(sfd);
1918 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1921 gboolean ret = TRUE;
1926 FileData *fd = work->data;
1928 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1935 static void file_data_sc_revert_ci_list(GList *fd_list)
1942 FileData *fd = work->data;
1944 file_data_sc_free_ci(fd);
1949 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1956 FileData *fd = work->data;
1958 if (!func(fd, dest))
1960 file_data_sc_revert_ci_list(work->prev);
1969 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1971 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1974 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1976 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1979 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1981 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1984 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1986 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1989 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1992 gboolean ret = TRUE;
1997 FileData *fd = work->data;
1999 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2006 void file_data_free_ci_list(GList *fd_list)
2013 FileData *fd = work->data;
2015 file_data_free_ci(fd);
2020 void file_data_sc_free_ci_list(GList *fd_list)
2027 FileData *fd = work->data;
2029 file_data_sc_free_ci(fd);
2035 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2036 * fails if fd->change does not exist or the change type does not match
2039 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2041 FileDataChangeType type = fd->change->type;
2043 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2047 if (!file_data_planned_change_hash)
2048 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2050 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2052 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2053 g_hash_table_remove(file_data_planned_change_hash, old_path);
2054 file_data_unref(fd);
2057 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
2062 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2063 g_hash_table_remove(file_data_planned_change_hash, new_path);
2064 file_data_unref(ofd);
2067 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2069 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2074 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2076 gchar *old_path = fd->change->dest;
2078 fd->change->dest = g_strdup(dest_path);
2079 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2083 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2085 const gchar *extension = extension_from_path(fd->change->source);
2086 gchar *base = remove_extension_from_path(dest_path);
2087 gchar *old_path = fd->change->dest;
2089 fd->change->dest = g_strconcat(base, extension, NULL);
2090 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2096 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2099 gchar *dest_path_full = NULL;
2101 if (fd->parent) fd = fd->parent;
2105 dest_path = fd->path;
2107 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2109 gchar *dir = remove_level_from_path(fd->path);
2111 dest_path_full = g_build_filename(dir, dest_path, NULL);
2113 dest_path = dest_path_full;
2115 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2117 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2118 dest_path = dest_path_full;
2121 file_data_update_ci_dest(fd, dest_path);
2123 work = fd->sidecar_files;
2126 FileData *sfd = work->data;
2128 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2132 g_free(dest_path_full);
2135 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2137 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2138 file_data_sc_update_ci(fd, dest_path);
2142 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2144 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2147 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2149 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2152 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2154 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2157 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2159 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2162 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2164 gboolean (*func)(FileData *, const gchar *))
2167 gboolean ret = TRUE;
2172 FileData *fd = work->data;
2174 if (!func(fd, dest)) ret = FALSE;
2181 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2183 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2186 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2188 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2191 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2193 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2198 * verify source and dest paths - dest image exists, etc.
2199 * it should detect all possible problems with the planned operation
2202 gint file_data_verify_ci(FileData *fd, GList *list)
2204 gint ret = CHANGE_OK;
2207 FileData *fd1 = NULL;
2211 DEBUG_1("Change checked: no change info: %s", fd->path);
2215 if (!isname(fd->path))
2217 /* this probably should not happen */
2218 ret |= CHANGE_NO_SRC;
2219 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2223 dir = remove_level_from_path(fd->path);
2225 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2226 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2227 fd->change->type != FILEDATA_CHANGE_RENAME &&
2228 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2231 ret |= CHANGE_WARN_UNSAVED_META;
2232 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2235 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2236 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2237 !access_file(fd->path, R_OK))
2239 ret |= CHANGE_NO_READ_PERM;
2240 DEBUG_1("Change checked: no read permission: %s", fd->path);
2242 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2243 !access_file(dir, W_OK))
2245 ret |= CHANGE_NO_WRITE_PERM_DIR;
2246 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2248 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2249 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2250 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2251 !access_file(fd->path, W_OK))
2253 ret |= CHANGE_WARN_NO_WRITE_PERM;
2254 DEBUG_1("Change checked: no write permission: %s", fd->path);
2256 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2257 - that means that there are no hard errors and warnings can be disabled
2258 - the destination is determined during the check
2260 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2262 /* determine destination file */
2263 gboolean have_dest = FALSE;
2264 gchar *dest_dir = NULL;
2266 if (options->metadata.save_in_image_file)
2268 if (file_data_can_write_directly(fd))
2270 /* we can write the file directly */
2271 if (access_file(fd->path, W_OK))
2277 if (options->metadata.warn_on_write_problems)
2279 ret |= CHANGE_WARN_NO_WRITE_PERM;
2280 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2284 else if (file_data_can_write_sidecar(fd))
2286 /* we can write sidecar */
2287 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2288 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2290 file_data_update_ci_dest(fd, sidecar);
2295 if (options->metadata.warn_on_write_problems)
2297 ret |= CHANGE_WARN_NO_WRITE_PERM;
2298 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2307 /* write private metadata file under ~/.geeqie */
2309 /* If an existing metadata file exists, we will try writing to
2310 * it's location regardless of the user's preference.
2312 gchar *metadata_path = NULL;
2314 /* but ignore XMP if we are not able to write it */
2315 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2317 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2319 if (metadata_path && !access_file(metadata_path, W_OK))
2321 g_free(metadata_path);
2322 metadata_path = NULL;
2329 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2330 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2332 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2334 metadata_path = g_build_filename(dest_dir, filename, NULL);
2338 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2340 file_data_update_ci_dest(fd, metadata_path);
2345 ret |= CHANGE_NO_WRITE_PERM_DEST;
2346 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2348 g_free(metadata_path);
2353 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2358 same = (strcmp(fd->path, fd->change->dest) == 0);
2362 const gchar *dest_ext = extension_from_path(fd->change->dest);
2363 if (!dest_ext) dest_ext = "";
2364 if (!options->file_filter.disable_file_extension_checks)
2366 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2368 ret |= CHANGE_WARN_CHANGED_EXT;
2369 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2375 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2377 ret |= CHANGE_WARN_SAME;
2378 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2382 dest_dir = remove_level_from_path(fd->change->dest);
2384 if (!isdir(dest_dir))
2386 ret |= CHANGE_NO_DEST_DIR;
2387 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2389 else if (!access_file(dest_dir, W_OK))
2391 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2392 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2396 if (isfile(fd->change->dest))
2398 if (!access_file(fd->change->dest, W_OK))
2400 ret |= CHANGE_NO_WRITE_PERM_DEST;
2401 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2405 ret |= CHANGE_WARN_DEST_EXISTS;
2406 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2409 else if (isdir(fd->change->dest))
2411 ret |= CHANGE_DEST_EXISTS;
2412 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2419 /* During a rename operation, check if another planned destination file has
2422 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2423 fd->change->type == FILEDATA_CHANGE_COPY ||
2424 fd->change->type == FILEDATA_CHANGE_MOVE)
2431 if (fd1 != NULL && fd != fd1 )
2433 if (!strcmp(fd->change->dest, fd1->change->dest))
2435 ret |= CHANGE_DUPLICATE_DEST;
2441 fd->change->error = ret;
2442 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2449 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2454 ret = file_data_verify_ci(fd, list);
2456 work = fd->sidecar_files;
2459 FileData *sfd = work->data;
2461 ret |= file_data_verify_ci(sfd, list);
2468 gchar *file_data_get_error_string(gint error)
2470 GString *result = g_string_new("");
2472 if (error & CHANGE_NO_SRC)
2474 if (result->len > 0) g_string_append(result, ", ");
2475 g_string_append(result, _("file or directory does not exist"));
2478 if (error & CHANGE_DEST_EXISTS)
2480 if (result->len > 0) g_string_append(result, ", ");
2481 g_string_append(result, _("destination already exists"));
2484 if (error & CHANGE_NO_WRITE_PERM_DEST)
2486 if (result->len > 0) g_string_append(result, ", ");
2487 g_string_append(result, _("destination can't be overwritten"));
2490 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2492 if (result->len > 0) g_string_append(result, ", ");
2493 g_string_append(result, _("destination directory is not writable"));
2496 if (error & CHANGE_NO_DEST_DIR)
2498 if (result->len > 0) g_string_append(result, ", ");
2499 g_string_append(result, _("destination directory does not exist"));
2502 if (error & CHANGE_NO_WRITE_PERM_DIR)
2504 if (result->len > 0) g_string_append(result, ", ");
2505 g_string_append(result, _("source directory is not writable"));
2508 if (error & CHANGE_NO_READ_PERM)
2510 if (result->len > 0) g_string_append(result, ", ");
2511 g_string_append(result, _("no read permission"));
2514 if (error & CHANGE_WARN_NO_WRITE_PERM)
2516 if (result->len > 0) g_string_append(result, ", ");
2517 g_string_append(result, _("file is readonly"));
2520 if (error & CHANGE_WARN_DEST_EXISTS)
2522 if (result->len > 0) g_string_append(result, ", ");
2523 g_string_append(result, _("destination already exists and will be overwritten"));
2526 if (error & CHANGE_WARN_SAME)
2528 if (result->len > 0) g_string_append(result, ", ");
2529 g_string_append(result, _("source and destination are the same"));
2532 if (error & CHANGE_WARN_CHANGED_EXT)
2534 if (result->len > 0) g_string_append(result, ", ");
2535 g_string_append(result, _("source and destination have different extension"));
2538 if (error & CHANGE_WARN_UNSAVED_META)
2540 if (result->len > 0) g_string_append(result, ", ");
2541 g_string_append(result, _("there are unsaved metadata changes for the file"));
2544 if (error & CHANGE_DUPLICATE_DEST)
2546 if (result->len > 0) g_string_append(result, ", ");
2547 g_string_append(result, _("another destination file has the same filename"));
2550 return g_string_free(result, FALSE);
2553 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2556 gint all_errors = 0;
2557 gint common_errors = ~0;
2562 if (!list) return 0;
2564 num = g_list_length(list);
2565 errors = g_new(int, num);
2576 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2577 all_errors |= error;
2578 common_errors &= error;
2585 if (desc && all_errors)
2588 GString *result = g_string_new("");
2592 gchar *str = file_data_get_error_string(common_errors);
2593 g_string_append(result, str);
2594 g_string_append(result, "\n");
2608 error = errors[i] & ~common_errors;
2612 gchar *str = file_data_get_error_string(error);
2613 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2618 *desc = g_string_free(result, FALSE);
2627 * perform the change described by FileFataChangeInfo
2628 * it is used for internal operations,
2629 * this function actually operates with files on the filesystem
2630 * it should implement safe delete
2633 static gboolean file_data_perform_move(FileData *fd)
2635 g_assert(!strcmp(fd->change->source, fd->path));
2636 return move_file(fd->change->source, fd->change->dest);
2639 static gboolean file_data_perform_copy(FileData *fd)
2641 g_assert(!strcmp(fd->change->source, fd->path));
2642 return copy_file(fd->change->source, fd->change->dest);
2645 static gboolean file_data_perform_delete(FileData *fd)
2647 if (isdir(fd->path) && !islink(fd->path))
2648 return rmdir_utf8(fd->path);
2650 if (options->file_ops.safe_delete_enable)
2651 return file_util_safe_unlink(fd->path);
2653 return unlink_file(fd->path);
2656 gboolean file_data_perform_ci(FileData *fd)
2658 FileDataChangeType type = fd->change->type;
2662 case FILEDATA_CHANGE_MOVE:
2663 return file_data_perform_move(fd);
2664 case FILEDATA_CHANGE_COPY:
2665 return file_data_perform_copy(fd);
2666 case FILEDATA_CHANGE_RENAME:
2667 return file_data_perform_move(fd); /* the same as move */
2668 case FILEDATA_CHANGE_DELETE:
2669 return file_data_perform_delete(fd);
2670 case FILEDATA_CHANGE_WRITE_METADATA:
2671 return metadata_write_perform(fd);
2672 case FILEDATA_CHANGE_UNSPECIFIED:
2673 /* nothing to do here */
2681 gboolean file_data_sc_perform_ci(FileData *fd)
2684 gboolean ret = TRUE;
2685 FileDataChangeType type = fd->change->type;
2687 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2689 work = fd->sidecar_files;
2692 FileData *sfd = work->data;
2694 if (!file_data_perform_ci(sfd)) ret = FALSE;
2698 if (!file_data_perform_ci(fd)) ret = FALSE;
2704 * updates FileData structure according to FileDataChangeInfo
2707 gboolean file_data_apply_ci(FileData *fd)
2709 FileDataChangeType type = fd->change->type;
2712 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2714 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2715 file_data_planned_change_remove(fd);
2717 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2719 /* this change overwrites another file which is already known to other modules
2720 renaming fd would create duplicate FileData structure
2721 the best thing we can do is nothing
2722 FIXME: maybe we could copy stuff like marks
2724 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2728 file_data_set_path(fd, fd->change->dest);
2731 file_data_increment_version(fd);
2732 file_data_send_notification(fd, NOTIFY_CHANGE);
2737 gboolean file_data_sc_apply_ci(FileData *fd)
2740 FileDataChangeType type = fd->change->type;
2742 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2744 work = fd->sidecar_files;
2747 FileData *sfd = work->data;
2749 file_data_apply_ci(sfd);
2753 file_data_apply_ci(fd);
2758 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2761 if (fd->parent) fd = fd->parent;
2762 if (!g_list_find(list, fd)) return FALSE;
2764 work = fd->sidecar_files;
2767 if (!g_list_find(list, work->data)) return FALSE;
2773 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2778 /* change partial groups to independent files */
2783 FileData *fd = work->data;
2786 if (!file_data_list_contains_whole_group(list, fd))
2788 file_data_disable_grouping(fd, TRUE);
2791 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2797 /* remove sidecars from the list,
2798 they can be still acessed via main_fd->sidecar_files */
2802 FileData *fd = work->data;
2806 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2808 out = g_list_prepend(out, file_data_ref(fd));
2812 filelist_free(list);
2813 out = g_list_reverse(out);
2823 * notify other modules about the change described by FileDataChangeInfo
2826 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2827 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2828 implementation in view_file_list.c */
2831 typedef struct _NotifyIdleData NotifyIdleData;
2833 struct _NotifyIdleData {
2839 typedef struct _NotifyData NotifyData;
2841 struct _NotifyData {
2842 FileDataNotifyFunc func;
2844 NotifyPriority priority;
2847 static GList *notify_func_list = NULL;
2849 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2851 NotifyData *nda = (NotifyData *)a;
2852 NotifyData *ndb = (NotifyData *)b;
2854 if (nda->priority < ndb->priority) return -1;
2855 if (nda->priority > ndb->priority) return 1;
2859 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2862 GList *work = notify_func_list;
2866 NotifyData *nd = (NotifyData *)work->data;
2868 if (nd->func == func && nd->data == data)
2870 g_warning("Notify func already registered");
2876 nd = g_new(NotifyData, 1);
2879 nd->priority = priority;
2881 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2882 DEBUG_2("Notify func registered: %p", nd);
2887 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2889 GList *work = notify_func_list;
2893 NotifyData *nd = (NotifyData *)work->data;
2895 if (nd->func == func && nd->data == data)
2897 notify_func_list = g_list_delete_link(notify_func_list, work);
2899 DEBUG_2("Notify func unregistered: %p", nd);
2905 g_warning("Notify func not found");
2910 gboolean file_data_send_notification_idle_cb(gpointer data)
2912 NotifyIdleData *nid = (NotifyIdleData *)data;
2913 GList *work = notify_func_list;
2917 NotifyData *nd = (NotifyData *)work->data;
2919 nd->func(nid->fd, nid->type, nd->data);
2922 file_data_unref(nid->fd);
2927 void file_data_send_notification(FileData *fd, NotifyType type)
2929 GList *work = notify_func_list;
2933 NotifyData *nd = (NotifyData *)work->data;
2935 nd->func(fd, type, nd->data);
2939 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
2940 nid->fd = file_data_ref(fd);
2942 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
2946 static GHashTable *file_data_monitor_pool = NULL;
2947 static guint realtime_monitor_id = 0; /* event source id */
2949 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2953 file_data_check_changed_files(fd);
2955 DEBUG_1("monitor %s", fd->path);
2958 static gboolean realtime_monitor_cb(gpointer data)
2960 if (!options->update_on_time_change) return TRUE;
2961 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2965 gboolean file_data_register_real_time_monitor(FileData *fd)
2971 if (!file_data_monitor_pool)
2972 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2974 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2976 DEBUG_1("Register realtime %d %s", count, fd->path);
2979 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2981 if (!realtime_monitor_id)
2983 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2989 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2993 g_assert(file_data_monitor_pool);
2995 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2997 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2999 g_assert(count > 0);
3004 g_hash_table_remove(file_data_monitor_pool, fd);
3006 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3008 file_data_unref(fd);
3010 if (g_hash_table_size(file_data_monitor_pool) == 0)
3012 g_source_remove(realtime_monitor_id);
3013 realtime_monitor_id = 0;
3019 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */