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 #if GTK_CHECK_VERSION(2, 8, 0)
283 if (options->file_sort.natural)
285 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
286 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
290 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
291 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
294 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
295 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
299 g_free(caseless_name);
302 static void file_data_set_path(FileData *fd, const gchar *path)
304 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
305 g_assert(file_data_pool);
309 if (fd->original_path)
311 g_hash_table_remove(file_data_pool, fd->original_path);
312 g_free(fd->original_path);
315 g_assert(!g_hash_table_lookup(file_data_pool, path));
317 fd->original_path = g_strdup(path);
318 g_hash_table_insert(file_data_pool, fd->original_path, fd);
320 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
322 fd->path = g_strdup(path);
324 fd->extension = fd->name + 1;
325 file_data_set_collate_keys(fd);
329 fd->path = g_strdup(path);
330 fd->name = filename_from_path(fd->path);
332 if (strcmp(fd->name, "..") == 0)
334 gchar *dir = remove_level_from_path(path);
336 fd->path = remove_level_from_path(dir);
339 fd->extension = fd->name + 2;
340 file_data_set_collate_keys(fd);
343 else if (strcmp(fd->name, ".") == 0)
346 fd->path = remove_level_from_path(path);
348 fd->extension = fd->name + 1;
349 file_data_set_collate_keys(fd);
353 fd->extension = registered_extension_from_path(fd->path);
354 if (fd->extension == NULL)
356 fd->extension = fd->name + strlen(fd->name);
359 fd->sidecar_priority = sidecar_file_priority(fd->extension);
360 file_data_set_collate_keys(fd);
364 *-----------------------------------------------------------------------------
365 * create or reuse Filedata
366 *-----------------------------------------------------------------------------
369 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
373 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
375 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
378 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
380 fd = g_hash_table_lookup(file_data_pool, path_utf8);
386 if (!fd && file_data_planned_change_hash)
388 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
391 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
392 if (!isfile(fd->path))
395 file_data_apply_ci(fd);
408 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
411 changed = file_data_check_changed_single_file(fd, st);
413 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
418 fd = g_new0(FileData, 1);
419 #ifdef DEBUG_FILEDATA
420 global_file_data_count++;
421 DEBUG_2("file data count++: %d", global_file_data_count);
424 fd->size = st->st_size;
425 fd->date = st->st_mtime;
426 fd->cdate = st->st_ctime;
427 fd->mode = st->st_mode;
429 fd->magick = FD_MAGICK;
432 fd->format_class = filter_file_get_class(path_utf8);
434 if (disable_sidecars) fd->disable_grouping = TRUE;
436 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
441 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
443 gchar *path_utf8 = path_to_utf8(path);
444 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
450 FileData *file_data_new_simple(const gchar *path_utf8)
455 if (!stat_utf8(path_utf8, &st))
461 fd = g_hash_table_lookup(file_data_pool, path_utf8);
462 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
471 void read_exif_time_data(FileData *file)
473 if (file->exifdate > 0)
475 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
479 file->exif = exif_read_fd(file);
483 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
484 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), file, file->path);
489 uint year, month, day, hour, min, sec;
491 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
492 time_str.tm_year = year - 1900;
493 time_str.tm_mon = month - 1;
494 time_str.tm_mday = day;
495 time_str.tm_hour = hour;
496 time_str.tm_min = min;
497 time_str.tm_sec = sec;
498 time_str.tm_isdst = 0;
500 file->exifdate = mktime(&time_str);
506 void set_exif_time_data(GList *files)
508 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
512 FileData *file = files->data;
514 read_exif_time_data(file);
519 void set_rating_data(GList *files)
522 DEBUG_1("%s set_rating_data: ...", get_exec_time());
526 FileData *file = files->data;
527 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
530 file->rating = atoi(rating_str);
537 FileData *file_data_new_no_grouping(const gchar *path_utf8)
541 if (!stat_utf8(path_utf8, &st))
547 return file_data_new(path_utf8, &st, TRUE);
550 FileData *file_data_new_dir(const gchar *path_utf8)
554 if (!stat_utf8(path_utf8, &st))
560 /* dir or non-existing yet */
561 g_assert(S_ISDIR(st.st_mode));
563 return file_data_new(path_utf8, &st, TRUE);
567 *-----------------------------------------------------------------------------
569 *-----------------------------------------------------------------------------
572 #ifdef DEBUG_FILEDATA
573 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
575 FileData *file_data_ref(FileData *fd)
578 if (fd == NULL) return NULL;
579 if (fd->magick != FD_MAGICK)
580 #ifdef DEBUG_FILEDATA
581 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, fd);
583 log_printf("Error: fd magick mismatch fd=%p", fd);
585 g_assert(fd->magick == FD_MAGICK);
588 #ifdef DEBUG_FILEDATA
589 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", fd, fd->ref, fd->path, file, line);
591 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
596 static void file_data_free(FileData *fd)
598 g_assert(fd->magick == FD_MAGICK);
599 g_assert(fd->ref == 0);
600 g_assert(!fd->locked);
602 #ifdef DEBUG_FILEDATA
603 global_file_data_count--;
604 DEBUG_2("file data count--: %d", global_file_data_count);
607 metadata_cache_free(fd);
608 g_hash_table_remove(file_data_pool, fd->original_path);
611 g_free(fd->original_path);
612 g_free(fd->collate_key_name);
613 g_free(fd->collate_key_name_nocase);
614 g_free(fd->extended_extension);
615 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
616 histmap_free(fd->histmap);
618 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
620 file_data_change_info_free(NULL, fd);
625 * \brief Checks if the FileData is referenced
627 * Checks the refcount and whether the FileData is locked.
629 static gboolean file_data_check_has_ref(FileData *fd)
631 return fd->ref > 0 || fd->locked;
635 * \brief Consider freeing a FileData.
637 * This function will free a FileData and its children provided that neither its parent nor it has
638 * a positive refcount, and provided that neither is locked.
640 static void file_data_consider_free(FileData *fd)
643 FileData *parent = fd->parent ? fd->parent : fd;
645 g_assert(fd->magick == FD_MAGICK);
646 if (file_data_check_has_ref(fd)) return;
647 if (file_data_check_has_ref(parent)) return;
649 work = parent->sidecar_files;
652 FileData *sfd = work->data;
653 if (file_data_check_has_ref(sfd)) return;
657 /* Neither the parent nor the siblings are referenced, so we can free everything */
658 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
659 fd->path, fd->parent ? parent->path : "-");
661 work = parent->sidecar_files;
664 FileData *sfd = work->data;
669 g_list_free(parent->sidecar_files);
670 parent->sidecar_files = NULL;
672 file_data_free(parent);
675 #ifdef DEBUG_FILEDATA
676 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
678 void file_data_unref(FileData *fd)
681 if (fd == NULL) return;
682 if (fd->magick != FD_MAGICK)
683 #ifdef DEBUG_FILEDATA
684 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, fd);
686 log_printf("Error: fd magick mismatch fd=%p", fd);
688 g_assert(fd->magick == FD_MAGICK);
691 #ifdef DEBUG_FILEDATA
692 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", fd, fd->ref, fd->locked, fd->path,
695 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
698 // Free FileData if it's no longer ref'd
699 file_data_consider_free(fd);
703 * \brief Lock the FileData in memory.
705 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
706 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
707 * even if the code would continue to function properly even if the FileData were freed. Code that
708 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
710 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
711 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
713 void file_data_lock(FileData *fd)
715 if (fd == NULL) return;
716 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", fd);
718 g_assert(fd->magick == FD_MAGICK);
721 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
725 * \brief Reset the maintain-FileData-in-memory lock
727 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
728 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
729 * keeping it from being freed.
731 void file_data_unlock(FileData *fd)
733 if (fd == NULL) return;
734 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", fd);
736 g_assert(fd->magick == FD_MAGICK);
739 // Free FileData if it's no longer ref'd
740 file_data_consider_free(fd);
744 * \brief Lock all of the FileDatas in the provided list
746 * \see file_data_lock(FileData)
748 void file_data_lock_list(GList *list)
755 FileData *fd = work->data;
762 * \brief Unlock all of the FileDatas in the provided list
764 * \see file_data_unlock(FileData)
766 void file_data_unlock_list(GList *list)
773 FileData *fd = work->data;
775 file_data_unlock(fd);
780 *-----------------------------------------------------------------------------
781 * sidecar file info struct
782 *-----------------------------------------------------------------------------
785 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
787 const FileData *fda = a;
788 const FileData *fdb = b;
790 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
791 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
793 return strcmp(fdb->extension, fda->extension);
797 static gint sidecar_file_priority(const gchar *extension)
802 if (extension == NULL)
805 work = sidecar_ext_get_list();
808 gchar *ext = work->data;
811 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
817 static void file_data_check_sidecars(const GList *basename_list)
819 /* basename_list contains the new group - first is the parent, then sorted sidecars */
820 /* all files in the list have ref count > 0 */
823 GList *s_work, *new_sidecars;
826 if (!basename_list) return;
829 DEBUG_2("basename start");
830 work = basename_list;
833 FileData *fd = work->data;
835 g_assert(fd->magick == FD_MAGICK);
836 DEBUG_2("basename: %p %s", fd, fd->name);
839 g_assert(fd->parent->magick == FD_MAGICK);
840 DEBUG_2(" parent: %p", fd->parent);
842 s_work = fd->sidecar_files;
845 FileData *sfd = s_work->data;
846 s_work = s_work->next;
847 g_assert(sfd->magick == FD_MAGICK);
848 DEBUG_2(" sidecar: %p %s", sfd, sfd->name);
851 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
854 parent_fd = basename_list->data;
856 /* check if the second and next entries of basename_list are already connected
857 as sidecars of the first entry (parent_fd) */
858 work = basename_list->next;
859 s_work = parent_fd->sidecar_files;
861 while (work && s_work)
863 if (work->data != s_work->data) break;
865 s_work = s_work->next;
868 if (!work && !s_work)
870 DEBUG_2("basename no change");
871 return; /* no change in grouping */
874 /* we have to regroup it */
876 /* first, disconnect everything and send notification*/
878 work = basename_list;
881 FileData *fd = work->data;
883 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
887 FileData *old_parent = fd->parent;
888 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
889 file_data_ref(old_parent);
890 file_data_disconnect_sidecar_file(old_parent, fd);
891 file_data_send_notification(old_parent, NOTIFY_REREAD);
892 file_data_unref(old_parent);
895 while (fd->sidecar_files)
897 FileData *sfd = fd->sidecar_files->data;
898 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
900 file_data_disconnect_sidecar_file(fd, sfd);
901 file_data_send_notification(sfd, NOTIFY_REREAD);
902 file_data_unref(sfd);
904 file_data_send_notification(fd, NOTIFY_GROUPING);
906 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
909 /* now we can form the new group */
910 work = basename_list->next;
914 FileData *sfd = work->data;
915 g_assert(sfd->magick == FD_MAGICK);
916 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
917 sfd->parent = parent_fd;
918 new_sidecars = g_list_prepend(new_sidecars, sfd);
921 g_assert(parent_fd->sidecar_files == NULL);
922 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
923 DEBUG_1("basename group changed for %s", parent_fd->path);
927 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
929 g_assert(target->magick == FD_MAGICK);
930 g_assert(sfd->magick == FD_MAGICK);
931 g_assert(g_list_find(target->sidecar_files, sfd));
933 file_data_ref(target);
936 g_assert(sfd->parent == target);
938 file_data_increment_version(sfd); /* increments both sfd and target */
940 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
942 g_free(sfd->extended_extension);
943 sfd->extended_extension = NULL;
945 file_data_unref(target);
946 file_data_unref(sfd);
949 /* disables / enables grouping for particular file, sends UPDATE notification */
950 void file_data_disable_grouping(FileData *fd, gboolean disable)
952 if (!fd->disable_grouping == !disable) return;
954 fd->disable_grouping = !!disable;
960 FileData *parent = file_data_ref(fd->parent);
961 file_data_disconnect_sidecar_file(parent, fd);
962 file_data_send_notification(parent, NOTIFY_GROUPING);
963 file_data_unref(parent);
965 else if (fd->sidecar_files)
967 GList *sidecar_files = filelist_copy(fd->sidecar_files);
968 GList *work = sidecar_files;
971 FileData *sfd = work->data;
973 file_data_disconnect_sidecar_file(fd, sfd);
974 file_data_send_notification(sfd, NOTIFY_GROUPING);
976 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
977 filelist_free(sidecar_files);
981 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
986 file_data_increment_version(fd);
987 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
989 file_data_send_notification(fd, NOTIFY_GROUPING);
992 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
999 FileData *fd = work->data;
1001 file_data_disable_grouping(fd, disable);
1009 *-----------------------------------------------------------------------------
1011 *-----------------------------------------------------------------------------
1015 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
1018 if (!filelist_sort_ascend)
1025 switch (filelist_sort_method)
1030 if (fa->size < fb->size) return -1;
1031 if (fa->size > fb->size) return 1;
1032 /* fall back to name */
1035 if (fa->date < fb->date) return -1;
1036 if (fa->date > fb->date) return 1;
1037 /* fall back to name */
1040 if (fa->cdate < fb->cdate) return -1;
1041 if (fa->cdate > fb->cdate) return 1;
1042 /* fall back to name */
1045 if (fa->exifdate < fb->exifdate) return -1;
1046 if (fa->exifdate > fb->exifdate) return 1;
1047 /* fall back to name */
1050 if (fa->rating < fb->rating) return -1;
1051 if (fa->rating > fb->rating) return 1;
1052 /* fall back to name */
1054 #ifdef HAVE_STRVERSCMP
1056 ret = strverscmp(fa->name, fb->name);
1057 if (ret != 0) return ret;
1064 if (options->file_sort.case_sensitive)
1065 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1067 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1069 if (ret != 0) return ret;
1071 /* do not return 0 unless the files are really the same
1072 file_data_pool ensures that original_path is unique
1074 return strcmp(fa->original_path, fb->original_path);
1077 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1079 filelist_sort_method = method;
1080 filelist_sort_ascend = ascend;
1081 return filelist_sort_compare_filedata(fa, fb);
1084 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1086 return filelist_sort_compare_filedata(a, b);
1089 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1091 filelist_sort_method = method;
1092 filelist_sort_ascend = ascend;
1093 return g_list_sort(list, cb);
1096 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1098 filelist_sort_method = method;
1099 filelist_sort_ascend = ascend;
1100 return g_list_insert_sorted(list, data, cb);
1103 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1105 if (method == SORT_EXIFTIME)
1107 set_exif_time_data(list);
1109 if (method == SORT_RATING)
1111 set_rating_data(list);
1113 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1116 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
1118 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1122 *-----------------------------------------------------------------------------
1123 * basename hash - grouping of sidecars in filelist
1124 *-----------------------------------------------------------------------------
1128 static GHashTable *file_data_basename_hash_new(void)
1130 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1133 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1136 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1138 list = g_hash_table_lookup(basename_hash, basename);
1142 DEBUG_1("TG: basename_hash not found for %s",fd->path);
1143 const gchar *parent_extension = registered_extension_from_path(basename);
1145 if (parent_extension)
1147 DEBUG_1("TG: parent extension %s",parent_extension);
1148 gchar *parent_basename = g_strndup(basename, parent_extension - basename);
1149 DEBUG_1("TG: parent basename %s",parent_basename);
1150 FileData *parent_fd = g_hash_table_lookup(file_data_pool, basename);
1153 DEBUG_1("TG: parent fd found");
1154 list = g_hash_table_lookup(basename_hash, parent_basename);
1155 if (!g_list_find(list, parent_fd))
1157 DEBUG_1("TG: parent fd doesn't fit");
1158 g_free(parent_basename);
1164 basename = parent_basename;
1165 fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
1171 if (!g_list_find(list, fd))
1173 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1174 g_hash_table_insert(basename_hash, basename, list);
1183 static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
1185 file_data_basename_hash_insert((GHashTable *)basename_hash, (FileData *)fd);
1188 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
1190 filelist_free((GList *)value);
1193 static void file_data_basename_hash_free(GHashTable *basename_hash)
1195 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1196 g_hash_table_destroy(basename_hash);
1200 *-----------------------------------------------------------------------------
1201 * handling sidecars in filelist
1202 *-----------------------------------------------------------------------------
1205 static GList *filelist_filter_out_sidecars(GList *flist)
1207 GList *work = flist;
1208 GList *flist_filtered = NULL;
1212 FileData *fd = work->data;
1215 if (fd->parent) /* remove fd's that are children */
1216 file_data_unref(fd);
1218 flist_filtered = g_list_prepend(flist_filtered, fd);
1222 return flist_filtered;
1225 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
1227 GList *basename_list = (GList *)value;
1228 file_data_check_sidecars(basename_list);
1232 static gboolean is_hidden_file(const gchar *name)
1234 if (name[0] != '.') return FALSE;
1235 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1240 *-----------------------------------------------------------------------------
1241 * the main filelist function
1242 *-----------------------------------------------------------------------------
1245 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1250 GList *dlist = NULL;
1251 GList *flist = NULL;
1252 GList *xmp_files = NULL;
1253 gint (*stat_func)(const gchar *path, struct stat *buf);
1254 GHashTable *basename_hash = NULL;
1256 g_assert(files || dirs);
1258 if (files) *files = NULL;
1259 if (dirs) *dirs = NULL;
1261 pathl = path_from_utf8(dir_path);
1262 if (!pathl) return FALSE;
1264 dp = opendir(pathl);
1271 if (files) basename_hash = file_data_basename_hash_new();
1273 if (follow_symlinks)
1278 while ((dir = readdir(dp)) != NULL)
1280 struct stat ent_sbuf;
1281 const gchar *name = dir->d_name;
1284 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1287 filepath = g_build_filename(pathl, name, NULL);
1288 if (stat_func(filepath, &ent_sbuf) >= 0)
1290 if (S_ISDIR(ent_sbuf.st_mode))
1292 /* we ignore the .thumbnails dir for cleanliness */
1294 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1295 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1296 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1297 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1299 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1304 if (files && filter_name_exists(name))
1306 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1307 flist = g_list_prepend(flist, fd);
1308 if (fd->sidecar_priority && !fd->disable_grouping)
1310 if (strcmp(fd->extension, ".xmp") != 0)
1311 file_data_basename_hash_insert(basename_hash, fd);
1313 xmp_files = g_list_append(xmp_files, fd);
1320 if (errno == EOVERFLOW)
1322 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1334 g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
1335 g_list_free(xmp_files);
1338 if (dirs) *dirs = dlist;
1342 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1344 *files = filelist_filter_out_sidecars(flist);
1346 if (basename_hash) file_data_basename_hash_free(basename_hash);
1351 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1353 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1356 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1358 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1361 FileData *file_data_new_group(const gchar *path_utf8)
1368 if (!stat_utf8(path_utf8, &st))
1374 if (S_ISDIR(st.st_mode))
1375 return file_data_new(path_utf8, &st, TRUE);
1377 dir = remove_level_from_path(path_utf8);
1379 filelist_read_real(dir, &files, NULL, TRUE);
1381 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1382 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1388 filelist_free(files);
1394 void filelist_free(GList *list)
1401 file_data_unref((FileData *)work->data);
1409 GList *filelist_copy(GList *list)
1411 GList *new_list = NULL;
1422 new_list = g_list_prepend(new_list, file_data_ref(fd));
1425 return g_list_reverse(new_list);
1428 GList *filelist_from_path_list(GList *list)
1430 GList *new_list = NULL;
1441 new_list = g_list_prepend(new_list, file_data_new_group(path));
1444 return g_list_reverse(new_list);
1447 GList *filelist_to_path_list(GList *list)
1449 GList *new_list = NULL;
1460 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1463 return g_list_reverse(new_list);
1466 GList *filelist_filter(GList *list, gboolean is_dir_list)
1470 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1475 FileData *fd = (FileData *)(work->data);
1476 const gchar *name = fd->name;
1478 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1479 (!is_dir_list && !filter_name_exists(name)) ||
1480 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1481 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1485 list = g_list_remove_link(list, link);
1486 file_data_unref(fd);
1497 *-----------------------------------------------------------------------------
1498 * filelist recursive
1499 *-----------------------------------------------------------------------------
1502 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1504 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1507 GList *filelist_sort_path(GList *list)
1509 return g_list_sort(list, filelist_sort_path_cb);
1512 static void filelist_recursive_append(GList **list, GList *dirs)
1519 FileData *fd = (FileData *)(work->data);
1523 if (filelist_read(fd, &f, &d))
1525 f = filelist_filter(f, FALSE);
1526 f = filelist_sort_path(f);
1527 *list = g_list_concat(*list, f);
1529 d = filelist_filter(d, TRUE);
1530 d = filelist_sort_path(d);
1531 filelist_recursive_append(list, d);
1539 GList *filelist_recursive(FileData *dir_fd)
1544 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1545 list = filelist_filter(list, FALSE);
1546 list = filelist_sort_path(list);
1548 d = filelist_filter(d, TRUE);
1549 d = filelist_sort_path(d);
1550 filelist_recursive_append(&list, d);
1557 *-----------------------------------------------------------------------------
1558 * file modification support
1559 *-----------------------------------------------------------------------------
1563 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1565 if (!fdci && fd) fdci = fd->change;
1569 g_free(fdci->source);
1574 if (fd) fd->change = NULL;
1577 static gboolean file_data_can_write_directly(FileData *fd)
1579 return filter_name_is_writable(fd->extension);
1582 static gboolean file_data_can_write_sidecar(FileData *fd)
1584 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1587 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1589 gchar *sidecar_path = NULL;
1592 if (!file_data_can_write_sidecar(fd)) return NULL;
1594 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1595 gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1598 FileData *sfd = work->data;
1600 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1602 sidecar_path = g_strdup(sfd->path);
1606 g_free(extended_extension);
1608 if (!existing_only && !sidecar_path)
1610 if (options->metadata.sidecar_extended_name)
1611 sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1614 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1615 sidecar_path = g_strconcat(base, ".xmp", NULL);
1620 return sidecar_path;
1624 * marks and orientation
1627 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1628 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1629 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1630 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1632 gboolean file_data_get_mark(FileData *fd, gint n)
1634 gboolean valid = (fd->valid_marks & (1 << n));
1636 if (file_data_get_mark_func[n] && !valid)
1638 guint old = fd->marks;
1639 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1641 if (!value != !(fd->marks & (1 << n)))
1643 fd->marks = fd->marks ^ (1 << n);
1646 fd->valid_marks |= (1 << n);
1647 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1649 file_data_unref(fd);
1651 else if (!old && fd->marks)
1657 return !!(fd->marks & (1 << n));
1660 guint file_data_get_marks(FileData *fd)
1663 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1667 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1670 if (!value == !file_data_get_mark(fd, n)) return;
1672 if (file_data_set_mark_func[n])
1674 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1679 fd->marks = fd->marks ^ (1 << n);
1681 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1683 file_data_unref(fd);
1685 else if (!old && fd->marks)
1690 file_data_increment_version(fd);
1691 file_data_send_notification(fd, NOTIFY_MARKS);
1694 gboolean file_data_filter_marks(FileData *fd, guint filter)
1697 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1698 return ((fd->marks & filter) == filter);
1701 GList *file_data_filter_marks_list(GList *list, guint filter)
1708 FileData *fd = work->data;
1712 if (!file_data_filter_marks(fd, filter))
1714 list = g_list_remove_link(list, link);
1715 file_data_unref(fd);
1723 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1725 FileData *fd = value;
1726 file_data_increment_version(fd);
1727 file_data_send_notification(fd, NOTIFY_MARKS);
1730 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1732 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1734 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1736 file_data_get_mark_func[n] = get_mark_func;
1737 file_data_set_mark_func[n] = set_mark_func;
1738 file_data_mark_func_data[n] = data;
1739 file_data_destroy_mark_func[n] = notify;
1741 if (get_mark_func && file_data_pool)
1743 /* this effectively changes all known files */
1744 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1750 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1752 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1753 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1754 if (data) *data = file_data_mark_func_data[n];
1757 gint file_data_get_user_orientation(FileData *fd)
1759 return fd->user_orientation;
1762 void file_data_set_user_orientation(FileData *fd, gint value)
1764 if (fd->user_orientation == value) return;
1766 fd->user_orientation = value;
1767 file_data_increment_version(fd);
1768 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1773 * file_data - operates on the given fd
1774 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1778 /* return list of sidecar file extensions in a string */
1779 gchar *file_data_sc_list_to_string(FileData *fd)
1782 GString *result = g_string_new("");
1784 work = fd->sidecar_files;
1787 FileData *sfd = work->data;
1789 result = g_string_append(result, "+ ");
1790 result = g_string_append(result, sfd->extension);
1792 if (work) result = g_string_append_c(result, ' ');
1795 return g_string_free(result, FALSE);
1801 * add FileDataChangeInfo (see typedefs.h) for the given operation
1802 * uses file_data_add_change_info
1804 * fails if the fd->change already exists - change operations can't run in parallel
1805 * fd->change_info works as a lock
1807 * dest can be NULL - in this case the current name is used for now, it will
1812 FileDataChangeInfo types:
1814 MOVE - path is changed, name may be changed too
1815 RENAME - path remains unchanged, name is changed
1816 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1817 sidecar names are changed too, extensions are not changed
1819 UPDATE - file size, date or grouping has been changed
1822 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1824 FileDataChangeInfo *fdci;
1826 if (fd->change) return FALSE;
1828 fdci = g_new0(FileDataChangeInfo, 1);
1833 fdci->source = g_strdup(src);
1835 fdci->source = g_strdup(fd->path);
1838 fdci->dest = g_strdup(dest);
1845 static void file_data_planned_change_remove(FileData *fd)
1847 if (file_data_planned_change_hash &&
1848 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1850 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1852 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1853 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1854 file_data_unref(fd);
1855 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1857 g_hash_table_destroy(file_data_planned_change_hash);
1858 file_data_planned_change_hash = NULL;
1859 DEBUG_1("planned change: empty");
1866 void file_data_free_ci(FileData *fd)
1868 FileDataChangeInfo *fdci = fd->change;
1872 file_data_planned_change_remove(fd);
1874 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1876 g_free(fdci->source);
1884 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1886 FileDataChangeInfo *fdci = fd->change;
1888 fdci->regroup_when_finished = enable;
1891 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1895 if (fd->parent) fd = fd->parent;
1897 if (fd->change) return FALSE;
1899 work = fd->sidecar_files;
1902 FileData *sfd = work->data;
1904 if (sfd->change) return FALSE;
1908 file_data_add_ci(fd, type, NULL, NULL);
1910 work = fd->sidecar_files;
1913 FileData *sfd = work->data;
1915 file_data_add_ci(sfd, type, NULL, NULL);
1922 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1926 if (fd->parent) fd = fd->parent;
1928 if (!fd->change || fd->change->type != type) return FALSE;
1930 work = fd->sidecar_files;
1933 FileData *sfd = work->data;
1935 if (!sfd->change || sfd->change->type != type) return FALSE;
1943 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1945 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1946 file_data_sc_update_ci_copy(fd, dest_path);
1950 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1952 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1953 file_data_sc_update_ci_move(fd, dest_path);
1957 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1959 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1960 file_data_sc_update_ci_rename(fd, dest_path);
1964 gboolean file_data_sc_add_ci_delete(FileData *fd)
1966 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1969 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1971 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1972 file_data_sc_update_ci_unspecified(fd, dest_path);
1976 gboolean file_data_add_ci_write_metadata(FileData *fd)
1978 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1981 void file_data_sc_free_ci(FileData *fd)
1985 if (fd->parent) fd = fd->parent;
1987 file_data_free_ci(fd);
1989 work = fd->sidecar_files;
1992 FileData *sfd = work->data;
1994 file_data_free_ci(sfd);
1999 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2002 gboolean ret = TRUE;
2007 FileData *fd = work->data;
2009 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2016 static void file_data_sc_revert_ci_list(GList *fd_list)
2023 FileData *fd = work->data;
2025 file_data_sc_free_ci(fd);
2030 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2037 FileData *fd = work->data;
2039 if (!func(fd, dest))
2041 file_data_sc_revert_ci_list(work->prev);
2050 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2052 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2055 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2057 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2060 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2062 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2065 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2067 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2070 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2073 gboolean ret = TRUE;
2078 FileData *fd = work->data;
2080 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2087 void file_data_free_ci_list(GList *fd_list)
2094 FileData *fd = work->data;
2096 file_data_free_ci(fd);
2101 void file_data_sc_free_ci_list(GList *fd_list)
2108 FileData *fd = work->data;
2110 file_data_sc_free_ci(fd);
2116 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2117 * fails if fd->change does not exist or the change type does not match
2120 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2122 FileDataChangeType type = fd->change->type;
2124 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2128 if (!file_data_planned_change_hash)
2129 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2131 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2133 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2134 g_hash_table_remove(file_data_planned_change_hash, old_path);
2135 file_data_unref(fd);
2138 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
2143 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2144 g_hash_table_remove(file_data_planned_change_hash, new_path);
2145 file_data_unref(ofd);
2148 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2150 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2155 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2157 gchar *old_path = fd->change->dest;
2159 fd->change->dest = g_strdup(dest_path);
2160 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2164 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2166 const gchar *extension = registered_extension_from_path(fd->change->source);
2167 gchar *base = remove_extension_from_path(dest_path);
2168 gchar *old_path = fd->change->dest;
2170 fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2171 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2177 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2180 gchar *dest_path_full = NULL;
2182 if (fd->parent) fd = fd->parent;
2186 dest_path = fd->path;
2188 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2190 gchar *dir = remove_level_from_path(fd->path);
2192 dest_path_full = g_build_filename(dir, dest_path, NULL);
2194 dest_path = dest_path_full;
2196 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2198 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2199 dest_path = dest_path_full;
2202 file_data_update_ci_dest(fd, dest_path);
2204 work = fd->sidecar_files;
2207 FileData *sfd = work->data;
2209 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2213 g_free(dest_path_full);
2216 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2218 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2219 file_data_sc_update_ci(fd, dest_path);
2223 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2225 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2228 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2230 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2233 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2235 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2238 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2240 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2243 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2245 gboolean (*func)(FileData *, const gchar *))
2248 gboolean ret = TRUE;
2253 FileData *fd = work->data;
2255 if (!func(fd, dest)) ret = FALSE;
2262 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2264 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2267 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2269 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2272 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2274 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2279 * verify source and dest paths - dest image exists, etc.
2280 * it should detect all possible problems with the planned operation
2283 gint file_data_verify_ci(FileData *fd, GList *list)
2285 gint ret = CHANGE_OK;
2288 FileData *fd1 = NULL;
2292 DEBUG_1("Change checked: no change info: %s", fd->path);
2296 if (!isname(fd->path))
2298 /* this probably should not happen */
2299 ret |= CHANGE_NO_SRC;
2300 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2304 dir = remove_level_from_path(fd->path);
2306 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2307 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2308 fd->change->type != FILEDATA_CHANGE_RENAME &&
2309 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2312 ret |= CHANGE_WARN_UNSAVED_META;
2313 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2316 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2317 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2318 !access_file(fd->path, R_OK))
2320 ret |= CHANGE_NO_READ_PERM;
2321 DEBUG_1("Change checked: no read permission: %s", fd->path);
2323 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2324 !access_file(dir, W_OK))
2326 ret |= CHANGE_NO_WRITE_PERM_DIR;
2327 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2329 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2330 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2331 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2332 !access_file(fd->path, W_OK))
2334 ret |= CHANGE_WARN_NO_WRITE_PERM;
2335 DEBUG_1("Change checked: no write permission: %s", fd->path);
2337 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2338 - that means that there are no hard errors and warnings can be disabled
2339 - the destination is determined during the check
2341 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2343 /* determine destination file */
2344 gboolean have_dest = FALSE;
2345 gchar *dest_dir = NULL;
2347 if (options->metadata.save_in_image_file)
2349 if (file_data_can_write_directly(fd))
2351 /* we can write the file directly */
2352 if (access_file(fd->path, W_OK))
2358 if (options->metadata.warn_on_write_problems)
2360 ret |= CHANGE_WARN_NO_WRITE_PERM;
2361 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2365 else if (file_data_can_write_sidecar(fd))
2367 /* we can write sidecar */
2368 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2369 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2371 file_data_update_ci_dest(fd, sidecar);
2376 if (options->metadata.warn_on_write_problems)
2378 ret |= CHANGE_WARN_NO_WRITE_PERM;
2379 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2388 /* write private metadata file under ~/.geeqie */
2390 /* If an existing metadata file exists, we will try writing to
2391 * it's location regardless of the user's preference.
2393 gchar *metadata_path = NULL;
2395 /* but ignore XMP if we are not able to write it */
2396 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2398 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2400 if (metadata_path && !access_file(metadata_path, W_OK))
2402 g_free(metadata_path);
2403 metadata_path = NULL;
2410 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2411 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2413 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2415 metadata_path = g_build_filename(dest_dir, filename, NULL);
2419 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2421 file_data_update_ci_dest(fd, metadata_path);
2426 ret |= CHANGE_NO_WRITE_PERM_DEST;
2427 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2429 g_free(metadata_path);
2434 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2439 same = (strcmp(fd->path, fd->change->dest) == 0);
2443 const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2444 if (!dest_ext) dest_ext = "";
2445 if (!options->file_filter.disable_file_extension_checks)
2447 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2449 ret |= CHANGE_WARN_CHANGED_EXT;
2450 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2456 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2458 ret |= CHANGE_WARN_SAME;
2459 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2463 dest_dir = remove_level_from_path(fd->change->dest);
2465 if (!isdir(dest_dir))
2467 ret |= CHANGE_NO_DEST_DIR;
2468 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2470 else if (!access_file(dest_dir, W_OK))
2472 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2473 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2477 if (isfile(fd->change->dest))
2479 if (!access_file(fd->change->dest, W_OK))
2481 ret |= CHANGE_NO_WRITE_PERM_DEST;
2482 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2486 ret |= CHANGE_WARN_DEST_EXISTS;
2487 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2490 else if (isdir(fd->change->dest))
2492 ret |= CHANGE_DEST_EXISTS;
2493 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2500 /* During a rename operation, check if another planned destination file has
2503 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2504 fd->change->type == FILEDATA_CHANGE_COPY ||
2505 fd->change->type == FILEDATA_CHANGE_MOVE)
2512 if (fd1 != NULL && fd != fd1 )
2514 if (!strcmp(fd->change->dest, fd1->change->dest))
2516 ret |= CHANGE_DUPLICATE_DEST;
2522 fd->change->error = ret;
2523 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2530 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2535 ret = file_data_verify_ci(fd, list);
2537 work = fd->sidecar_files;
2540 FileData *sfd = work->data;
2542 ret |= file_data_verify_ci(sfd, list);
2549 gchar *file_data_get_error_string(gint error)
2551 GString *result = g_string_new("");
2553 if (error & CHANGE_NO_SRC)
2555 if (result->len > 0) g_string_append(result, ", ");
2556 g_string_append(result, _("file or directory does not exist"));
2559 if (error & CHANGE_DEST_EXISTS)
2561 if (result->len > 0) g_string_append(result, ", ");
2562 g_string_append(result, _("destination already exists"));
2565 if (error & CHANGE_NO_WRITE_PERM_DEST)
2567 if (result->len > 0) g_string_append(result, ", ");
2568 g_string_append(result, _("destination can't be overwritten"));
2571 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2573 if (result->len > 0) g_string_append(result, ", ");
2574 g_string_append(result, _("destination directory is not writable"));
2577 if (error & CHANGE_NO_DEST_DIR)
2579 if (result->len > 0) g_string_append(result, ", ");
2580 g_string_append(result, _("destination directory does not exist"));
2583 if (error & CHANGE_NO_WRITE_PERM_DIR)
2585 if (result->len > 0) g_string_append(result, ", ");
2586 g_string_append(result, _("source directory is not writable"));
2589 if (error & CHANGE_NO_READ_PERM)
2591 if (result->len > 0) g_string_append(result, ", ");
2592 g_string_append(result, _("no read permission"));
2595 if (error & CHANGE_WARN_NO_WRITE_PERM)
2597 if (result->len > 0) g_string_append(result, ", ");
2598 g_string_append(result, _("file is readonly"));
2601 if (error & CHANGE_WARN_DEST_EXISTS)
2603 if (result->len > 0) g_string_append(result, ", ");
2604 g_string_append(result, _("destination already exists and will be overwritten"));
2607 if (error & CHANGE_WARN_SAME)
2609 if (result->len > 0) g_string_append(result, ", ");
2610 g_string_append(result, _("source and destination are the same"));
2613 if (error & CHANGE_WARN_CHANGED_EXT)
2615 if (result->len > 0) g_string_append(result, ", ");
2616 g_string_append(result, _("source and destination have different extension"));
2619 if (error & CHANGE_WARN_UNSAVED_META)
2621 if (result->len > 0) g_string_append(result, ", ");
2622 g_string_append(result, _("there are unsaved metadata changes for the file"));
2625 if (error & CHANGE_DUPLICATE_DEST)
2627 if (result->len > 0) g_string_append(result, ", ");
2628 g_string_append(result, _("another destination file has the same filename"));
2631 return g_string_free(result, FALSE);
2634 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2637 gint all_errors = 0;
2638 gint common_errors = ~0;
2643 if (!list) return 0;
2645 num = g_list_length(list);
2646 errors = g_new(int, num);
2657 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2658 all_errors |= error;
2659 common_errors &= error;
2666 if (desc && all_errors)
2669 GString *result = g_string_new("");
2673 gchar *str = file_data_get_error_string(common_errors);
2674 g_string_append(result, str);
2675 g_string_append(result, "\n");
2689 error = errors[i] & ~common_errors;
2693 gchar *str = file_data_get_error_string(error);
2694 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2699 *desc = g_string_free(result, FALSE);
2708 * perform the change described by FileFataChangeInfo
2709 * it is used for internal operations,
2710 * this function actually operates with files on the filesystem
2711 * it should implement safe delete
2714 static gboolean file_data_perform_move(FileData *fd)
2716 g_assert(!strcmp(fd->change->source, fd->path));
2717 return move_file(fd->change->source, fd->change->dest);
2720 static gboolean file_data_perform_copy(FileData *fd)
2722 g_assert(!strcmp(fd->change->source, fd->path));
2723 return copy_file(fd->change->source, fd->change->dest);
2726 static gboolean file_data_perform_delete(FileData *fd)
2728 if (isdir(fd->path) && !islink(fd->path))
2729 return rmdir_utf8(fd->path);
2731 if (options->file_ops.safe_delete_enable)
2732 return file_util_safe_unlink(fd->path);
2734 return unlink_file(fd->path);
2737 gboolean file_data_perform_ci(FileData *fd)
2739 FileDataChangeType type = fd->change->type;
2743 case FILEDATA_CHANGE_MOVE:
2744 return file_data_perform_move(fd);
2745 case FILEDATA_CHANGE_COPY:
2746 return file_data_perform_copy(fd);
2747 case FILEDATA_CHANGE_RENAME:
2748 return file_data_perform_move(fd); /* the same as move */
2749 case FILEDATA_CHANGE_DELETE:
2750 return file_data_perform_delete(fd);
2751 case FILEDATA_CHANGE_WRITE_METADATA:
2752 return metadata_write_perform(fd);
2753 case FILEDATA_CHANGE_UNSPECIFIED:
2754 /* nothing to do here */
2762 gboolean file_data_sc_perform_ci(FileData *fd)
2765 gboolean ret = TRUE;
2766 FileDataChangeType type = fd->change->type;
2768 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2770 work = fd->sidecar_files;
2773 FileData *sfd = work->data;
2775 if (!file_data_perform_ci(sfd)) ret = FALSE;
2779 if (!file_data_perform_ci(fd)) ret = FALSE;
2785 * updates FileData structure according to FileDataChangeInfo
2788 gboolean file_data_apply_ci(FileData *fd)
2790 FileDataChangeType type = fd->change->type;
2793 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2795 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2796 file_data_planned_change_remove(fd);
2798 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2800 /* this change overwrites another file which is already known to other modules
2801 renaming fd would create duplicate FileData structure
2802 the best thing we can do is nothing
2803 FIXME: maybe we could copy stuff like marks
2805 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2809 file_data_set_path(fd, fd->change->dest);
2812 file_data_increment_version(fd);
2813 file_data_send_notification(fd, NOTIFY_CHANGE);
2818 gboolean file_data_sc_apply_ci(FileData *fd)
2821 FileDataChangeType type = fd->change->type;
2823 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2825 work = fd->sidecar_files;
2828 FileData *sfd = work->data;
2830 file_data_apply_ci(sfd);
2834 file_data_apply_ci(fd);
2839 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2842 if (fd->parent) fd = fd->parent;
2843 if (!g_list_find(list, fd)) return FALSE;
2845 work = fd->sidecar_files;
2848 if (!g_list_find(list, work->data)) return FALSE;
2854 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2859 /* change partial groups to independent files */
2864 FileData *fd = work->data;
2867 if (!file_data_list_contains_whole_group(list, fd))
2869 file_data_disable_grouping(fd, TRUE);
2872 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2878 /* remove sidecars from the list,
2879 they can be still acessed via main_fd->sidecar_files */
2883 FileData *fd = work->data;
2887 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2889 out = g_list_prepend(out, file_data_ref(fd));
2893 filelist_free(list);
2894 out = g_list_reverse(out);
2904 * notify other modules about the change described by FileDataChangeInfo
2907 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2908 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2909 implementation in view_file_list.c */
2912 typedef struct _NotifyIdleData NotifyIdleData;
2914 struct _NotifyIdleData {
2920 typedef struct _NotifyData NotifyData;
2922 struct _NotifyData {
2923 FileDataNotifyFunc func;
2925 NotifyPriority priority;
2928 static GList *notify_func_list = NULL;
2930 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2932 NotifyData *nda = (NotifyData *)a;
2933 NotifyData *ndb = (NotifyData *)b;
2935 if (nda->priority < ndb->priority) return -1;
2936 if (nda->priority > ndb->priority) return 1;
2940 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2943 GList *work = notify_func_list;
2947 NotifyData *nd = (NotifyData *)work->data;
2949 if (nd->func == func && nd->data == data)
2951 g_warning("Notify func already registered");
2957 nd = g_new(NotifyData, 1);
2960 nd->priority = priority;
2962 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2963 DEBUG_2("Notify func registered: %p", nd);
2968 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2970 GList *work = notify_func_list;
2974 NotifyData *nd = (NotifyData *)work->data;
2976 if (nd->func == func && nd->data == data)
2978 notify_func_list = g_list_delete_link(notify_func_list, work);
2980 DEBUG_2("Notify func unregistered: %p", nd);
2986 g_warning("Notify func not found");
2991 gboolean file_data_send_notification_idle_cb(gpointer data)
2993 NotifyIdleData *nid = (NotifyIdleData *)data;
2994 GList *work = notify_func_list;
2998 NotifyData *nd = (NotifyData *)work->data;
3000 nd->func(nid->fd, nid->type, nd->data);
3003 file_data_unref(nid->fd);
3008 void file_data_send_notification(FileData *fd, NotifyType type)
3010 GList *work = notify_func_list;
3014 NotifyData *nd = (NotifyData *)work->data;
3016 nd->func(fd, type, nd->data);
3020 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3021 nid->fd = file_data_ref(fd);
3023 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3027 static GHashTable *file_data_monitor_pool = NULL;
3028 static guint realtime_monitor_id = 0; /* event source id */
3030 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
3034 file_data_check_changed_files(fd);
3036 DEBUG_1("monitor %s", fd->path);
3039 static gboolean realtime_monitor_cb(gpointer data)
3041 if (!options->update_on_time_change) return TRUE;
3042 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
3046 gboolean file_data_register_real_time_monitor(FileData *fd)
3052 if (!file_data_monitor_pool)
3053 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3055 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3057 DEBUG_1("Register realtime %d %s", count, fd->path);
3060 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3062 if (!realtime_monitor_id)
3064 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
3070 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3074 g_assert(file_data_monitor_pool);
3076 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3078 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3080 g_assert(count > 0);
3085 g_hash_table_remove(file_data_monitor_pool, fd);
3087 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3089 file_data_unref(fd);
3091 if (g_hash_table_size(file_data_monitor_pool) == 0)
3093 g_source_remove(realtime_monitor_id);
3094 realtime_monitor_id = 0;
3100 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */