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 */
1055 if (fa->format_class < fb->format_class) return -1;
1056 if (fa->format_class > fb->format_class) return 1;
1057 /* fall back to name */
1059 #ifdef HAVE_STRVERSCMP
1061 ret = strverscmp(fa->name, fb->name);
1062 if (ret != 0) return ret;
1069 if (options->file_sort.case_sensitive)
1070 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1072 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1074 if (ret != 0) return ret;
1076 /* do not return 0 unless the files are really the same
1077 file_data_pool ensures that original_path is unique
1079 return strcmp(fa->original_path, fb->original_path);
1082 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1084 filelist_sort_method = method;
1085 filelist_sort_ascend = ascend;
1086 return filelist_sort_compare_filedata(fa, fb);
1089 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1091 return filelist_sort_compare_filedata(a, b);
1094 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1096 filelist_sort_method = method;
1097 filelist_sort_ascend = ascend;
1098 return g_list_sort(list, cb);
1101 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1103 filelist_sort_method = method;
1104 filelist_sort_ascend = ascend;
1105 return g_list_insert_sorted(list, data, cb);
1108 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1110 if (method == SORT_EXIFTIME)
1112 set_exif_time_data(list);
1114 if (method == SORT_RATING)
1116 set_rating_data(list);
1118 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1121 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
1123 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1127 *-----------------------------------------------------------------------------
1128 * basename hash - grouping of sidecars in filelist
1129 *-----------------------------------------------------------------------------
1133 static GHashTable *file_data_basename_hash_new(void)
1135 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1138 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1141 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1143 list = g_hash_table_lookup(basename_hash, basename);
1147 DEBUG_1("TG: basename_hash not found for %s",fd->path);
1148 const gchar *parent_extension = registered_extension_from_path(basename);
1150 if (parent_extension)
1152 DEBUG_1("TG: parent extension %s",parent_extension);
1153 gchar *parent_basename = g_strndup(basename, parent_extension - basename);
1154 DEBUG_1("TG: parent basename %s",parent_basename);
1155 FileData *parent_fd = g_hash_table_lookup(file_data_pool, basename);
1158 DEBUG_1("TG: parent fd found");
1159 list = g_hash_table_lookup(basename_hash, parent_basename);
1160 if (!g_list_find(list, parent_fd))
1162 DEBUG_1("TG: parent fd doesn't fit");
1163 g_free(parent_basename);
1169 basename = parent_basename;
1170 fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
1176 if (!g_list_find(list, fd))
1178 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1179 g_hash_table_insert(basename_hash, basename, list);
1188 static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
1190 file_data_basename_hash_insert((GHashTable *)basename_hash, (FileData *)fd);
1193 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
1195 filelist_free((GList *)value);
1198 static void file_data_basename_hash_free(GHashTable *basename_hash)
1200 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1201 g_hash_table_destroy(basename_hash);
1205 *-----------------------------------------------------------------------------
1206 * handling sidecars in filelist
1207 *-----------------------------------------------------------------------------
1210 static GList *filelist_filter_out_sidecars(GList *flist)
1212 GList *work = flist;
1213 GList *flist_filtered = NULL;
1217 FileData *fd = work->data;
1220 if (fd->parent) /* remove fd's that are children */
1221 file_data_unref(fd);
1223 flist_filtered = g_list_prepend(flist_filtered, fd);
1227 return flist_filtered;
1230 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
1232 GList *basename_list = (GList *)value;
1233 file_data_check_sidecars(basename_list);
1237 static gboolean is_hidden_file(const gchar *name)
1239 if (name[0] != '.') return FALSE;
1240 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1245 *-----------------------------------------------------------------------------
1246 * the main filelist function
1247 *-----------------------------------------------------------------------------
1250 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1255 GList *dlist = NULL;
1256 GList *flist = NULL;
1257 GList *xmp_files = NULL;
1258 gint (*stat_func)(const gchar *path, struct stat *buf);
1259 GHashTable *basename_hash = NULL;
1261 g_assert(files || dirs);
1263 if (files) *files = NULL;
1264 if (dirs) *dirs = NULL;
1266 pathl = path_from_utf8(dir_path);
1267 if (!pathl) return FALSE;
1269 dp = opendir(pathl);
1276 if (files) basename_hash = file_data_basename_hash_new();
1278 if (follow_symlinks)
1283 while ((dir = readdir(dp)) != NULL)
1285 struct stat ent_sbuf;
1286 const gchar *name = dir->d_name;
1289 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1292 filepath = g_build_filename(pathl, name, NULL);
1293 if (stat_func(filepath, &ent_sbuf) >= 0)
1295 if (S_ISDIR(ent_sbuf.st_mode))
1297 /* we ignore the .thumbnails dir for cleanliness */
1299 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1300 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1301 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1302 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1304 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1309 if (files && filter_name_exists(name))
1311 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1312 flist = g_list_prepend(flist, fd);
1313 if (fd->sidecar_priority && !fd->disable_grouping)
1315 if (strcmp(fd->extension, ".xmp") != 0)
1316 file_data_basename_hash_insert(basename_hash, fd);
1318 xmp_files = g_list_append(xmp_files, fd);
1325 if (errno == EOVERFLOW)
1327 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1339 g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
1340 g_list_free(xmp_files);
1343 if (dirs) *dirs = dlist;
1347 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1349 *files = filelist_filter_out_sidecars(flist);
1351 if (basename_hash) file_data_basename_hash_free(basename_hash);
1356 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1358 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1361 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1363 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1366 FileData *file_data_new_group(const gchar *path_utf8)
1373 if (!stat_utf8(path_utf8, &st))
1379 if (S_ISDIR(st.st_mode))
1380 return file_data_new(path_utf8, &st, TRUE);
1382 dir = remove_level_from_path(path_utf8);
1384 filelist_read_real(dir, &files, NULL, TRUE);
1386 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1387 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1393 filelist_free(files);
1399 void filelist_free(GList *list)
1406 file_data_unref((FileData *)work->data);
1414 GList *filelist_copy(GList *list)
1416 GList *new_list = NULL;
1427 new_list = g_list_prepend(new_list, file_data_ref(fd));
1430 return g_list_reverse(new_list);
1433 GList *filelist_from_path_list(GList *list)
1435 GList *new_list = NULL;
1446 new_list = g_list_prepend(new_list, file_data_new_group(path));
1449 return g_list_reverse(new_list);
1452 GList *filelist_to_path_list(GList *list)
1454 GList *new_list = NULL;
1465 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1468 return g_list_reverse(new_list);
1471 GList *filelist_filter(GList *list, gboolean is_dir_list)
1475 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1480 FileData *fd = (FileData *)(work->data);
1481 const gchar *name = fd->name;
1483 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1484 (!is_dir_list && !filter_name_exists(name)) ||
1485 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1486 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1490 list = g_list_remove_link(list, link);
1491 file_data_unref(fd);
1502 *-----------------------------------------------------------------------------
1503 * filelist recursive
1504 *-----------------------------------------------------------------------------
1507 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1509 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1512 GList *filelist_sort_path(GList *list)
1514 return g_list_sort(list, filelist_sort_path_cb);
1517 static void filelist_recursive_append(GList **list, GList *dirs)
1524 FileData *fd = (FileData *)(work->data);
1528 if (filelist_read(fd, &f, &d))
1530 f = filelist_filter(f, FALSE);
1531 f = filelist_sort_path(f);
1532 *list = g_list_concat(*list, f);
1534 d = filelist_filter(d, TRUE);
1535 d = filelist_sort_path(d);
1536 filelist_recursive_append(list, d);
1544 GList *filelist_recursive(FileData *dir_fd)
1549 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1550 list = filelist_filter(list, FALSE);
1551 list = filelist_sort_path(list);
1553 d = filelist_filter(d, TRUE);
1554 d = filelist_sort_path(d);
1555 filelist_recursive_append(&list, d);
1562 *-----------------------------------------------------------------------------
1563 * file modification support
1564 *-----------------------------------------------------------------------------
1568 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1570 if (!fdci && fd) fdci = fd->change;
1574 g_free(fdci->source);
1579 if (fd) fd->change = NULL;
1582 static gboolean file_data_can_write_directly(FileData *fd)
1584 return filter_name_is_writable(fd->extension);
1587 static gboolean file_data_can_write_sidecar(FileData *fd)
1589 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1592 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1594 gchar *sidecar_path = NULL;
1597 if (!file_data_can_write_sidecar(fd)) return NULL;
1599 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1600 gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1603 FileData *sfd = work->data;
1605 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1607 sidecar_path = g_strdup(sfd->path);
1611 g_free(extended_extension);
1613 if (!existing_only && !sidecar_path)
1615 if (options->metadata.sidecar_extended_name)
1616 sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1619 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1620 sidecar_path = g_strconcat(base, ".xmp", NULL);
1625 return sidecar_path;
1629 * marks and orientation
1632 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1633 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1634 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1635 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1637 gboolean file_data_get_mark(FileData *fd, gint n)
1639 gboolean valid = (fd->valid_marks & (1 << n));
1641 if (file_data_get_mark_func[n] && !valid)
1643 guint old = fd->marks;
1644 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1646 if (!value != !(fd->marks & (1 << n)))
1648 fd->marks = fd->marks ^ (1 << n);
1651 fd->valid_marks |= (1 << n);
1652 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1654 file_data_unref(fd);
1656 else if (!old && fd->marks)
1662 return !!(fd->marks & (1 << n));
1665 guint file_data_get_marks(FileData *fd)
1668 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1672 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1675 if (!value == !file_data_get_mark(fd, n)) return;
1677 if (file_data_set_mark_func[n])
1679 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1684 fd->marks = fd->marks ^ (1 << n);
1686 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1688 file_data_unref(fd);
1690 else if (!old && fd->marks)
1695 file_data_increment_version(fd);
1696 file_data_send_notification(fd, NOTIFY_MARKS);
1699 gboolean file_data_filter_marks(FileData *fd, guint filter)
1702 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1703 return ((fd->marks & filter) == filter);
1706 GList *file_data_filter_marks_list(GList *list, guint filter)
1713 FileData *fd = work->data;
1717 if (!file_data_filter_marks(fd, filter))
1719 list = g_list_remove_link(list, link);
1720 file_data_unref(fd);
1728 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1730 FileData *fd = value;
1731 file_data_increment_version(fd);
1732 file_data_send_notification(fd, NOTIFY_MARKS);
1735 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1737 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1739 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1741 file_data_get_mark_func[n] = get_mark_func;
1742 file_data_set_mark_func[n] = set_mark_func;
1743 file_data_mark_func_data[n] = data;
1744 file_data_destroy_mark_func[n] = notify;
1746 if (get_mark_func && file_data_pool)
1748 /* this effectively changes all known files */
1749 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1755 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1757 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1758 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1759 if (data) *data = file_data_mark_func_data[n];
1762 gint file_data_get_user_orientation(FileData *fd)
1764 return fd->user_orientation;
1767 void file_data_set_user_orientation(FileData *fd, gint value)
1769 if (fd->user_orientation == value) return;
1771 fd->user_orientation = value;
1772 file_data_increment_version(fd);
1773 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1778 * file_data - operates on the given fd
1779 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1783 /* return list of sidecar file extensions in a string */
1784 gchar *file_data_sc_list_to_string(FileData *fd)
1787 GString *result = g_string_new("");
1789 work = fd->sidecar_files;
1792 FileData *sfd = work->data;
1794 result = g_string_append(result, "+ ");
1795 result = g_string_append(result, sfd->extension);
1797 if (work) result = g_string_append_c(result, ' ');
1800 return g_string_free(result, FALSE);
1806 * add FileDataChangeInfo (see typedefs.h) for the given operation
1807 * uses file_data_add_change_info
1809 * fails if the fd->change already exists - change operations can't run in parallel
1810 * fd->change_info works as a lock
1812 * dest can be NULL - in this case the current name is used for now, it will
1817 FileDataChangeInfo types:
1819 MOVE - path is changed, name may be changed too
1820 RENAME - path remains unchanged, name is changed
1821 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1822 sidecar names are changed too, extensions are not changed
1824 UPDATE - file size, date or grouping has been changed
1827 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1829 FileDataChangeInfo *fdci;
1831 if (fd->change) return FALSE;
1833 fdci = g_new0(FileDataChangeInfo, 1);
1838 fdci->source = g_strdup(src);
1840 fdci->source = g_strdup(fd->path);
1843 fdci->dest = g_strdup(dest);
1850 static void file_data_planned_change_remove(FileData *fd)
1852 if (file_data_planned_change_hash &&
1853 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1855 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1857 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1858 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1859 file_data_unref(fd);
1860 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1862 g_hash_table_destroy(file_data_planned_change_hash);
1863 file_data_planned_change_hash = NULL;
1864 DEBUG_1("planned change: empty");
1871 void file_data_free_ci(FileData *fd)
1873 FileDataChangeInfo *fdci = fd->change;
1877 file_data_planned_change_remove(fd);
1879 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1881 g_free(fdci->source);
1889 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1891 FileDataChangeInfo *fdci = fd->change;
1893 fdci->regroup_when_finished = enable;
1896 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1900 if (fd->parent) fd = fd->parent;
1902 if (fd->change) return FALSE;
1904 work = fd->sidecar_files;
1907 FileData *sfd = work->data;
1909 if (sfd->change) return FALSE;
1913 file_data_add_ci(fd, type, NULL, NULL);
1915 work = fd->sidecar_files;
1918 FileData *sfd = work->data;
1920 file_data_add_ci(sfd, type, NULL, NULL);
1927 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1931 if (fd->parent) fd = fd->parent;
1933 if (!fd->change || fd->change->type != type) return FALSE;
1935 work = fd->sidecar_files;
1938 FileData *sfd = work->data;
1940 if (!sfd->change || sfd->change->type != type) return FALSE;
1948 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1950 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1951 file_data_sc_update_ci_copy(fd, dest_path);
1955 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1957 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1958 file_data_sc_update_ci_move(fd, dest_path);
1962 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1964 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1965 file_data_sc_update_ci_rename(fd, dest_path);
1969 gboolean file_data_sc_add_ci_delete(FileData *fd)
1971 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1974 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1976 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1977 file_data_sc_update_ci_unspecified(fd, dest_path);
1981 gboolean file_data_add_ci_write_metadata(FileData *fd)
1983 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1986 void file_data_sc_free_ci(FileData *fd)
1990 if (fd->parent) fd = fd->parent;
1992 file_data_free_ci(fd);
1994 work = fd->sidecar_files;
1997 FileData *sfd = work->data;
1999 file_data_free_ci(sfd);
2004 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2007 gboolean ret = TRUE;
2012 FileData *fd = work->data;
2014 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2021 static void file_data_sc_revert_ci_list(GList *fd_list)
2028 FileData *fd = work->data;
2030 file_data_sc_free_ci(fd);
2035 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2042 FileData *fd = work->data;
2044 if (!func(fd, dest))
2046 file_data_sc_revert_ci_list(work->prev);
2055 gboolean file_data_sc_add_ci_copy_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_copy);
2060 gboolean file_data_sc_add_ci_move_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_move);
2065 gboolean file_data_sc_add_ci_rename_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_rename);
2070 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2072 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2075 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2078 gboolean ret = TRUE;
2083 FileData *fd = work->data;
2085 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2092 void file_data_free_ci_list(GList *fd_list)
2099 FileData *fd = work->data;
2101 file_data_free_ci(fd);
2106 void file_data_sc_free_ci_list(GList *fd_list)
2113 FileData *fd = work->data;
2115 file_data_sc_free_ci(fd);
2121 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2122 * fails if fd->change does not exist or the change type does not match
2125 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2127 FileDataChangeType type = fd->change->type;
2129 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2133 if (!file_data_planned_change_hash)
2134 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2136 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2138 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2139 g_hash_table_remove(file_data_planned_change_hash, old_path);
2140 file_data_unref(fd);
2143 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
2148 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2149 g_hash_table_remove(file_data_planned_change_hash, new_path);
2150 file_data_unref(ofd);
2153 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2155 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2160 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2162 gchar *old_path = fd->change->dest;
2164 fd->change->dest = g_strdup(dest_path);
2165 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2169 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2171 const gchar *extension = registered_extension_from_path(fd->change->source);
2172 gchar *base = remove_extension_from_path(dest_path);
2173 gchar *old_path = fd->change->dest;
2175 fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2176 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2182 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2185 gchar *dest_path_full = NULL;
2187 if (fd->parent) fd = fd->parent;
2191 dest_path = fd->path;
2193 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2195 gchar *dir = remove_level_from_path(fd->path);
2197 dest_path_full = g_build_filename(dir, dest_path, NULL);
2199 dest_path = dest_path_full;
2201 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2203 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2204 dest_path = dest_path_full;
2207 file_data_update_ci_dest(fd, dest_path);
2209 work = fd->sidecar_files;
2212 FileData *sfd = work->data;
2214 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2218 g_free(dest_path_full);
2221 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2223 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2224 file_data_sc_update_ci(fd, dest_path);
2228 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2230 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2233 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2235 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2238 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2240 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2243 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2245 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2248 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2250 gboolean (*func)(FileData *, const gchar *))
2253 gboolean ret = TRUE;
2258 FileData *fd = work->data;
2260 if (!func(fd, dest)) ret = FALSE;
2267 gboolean file_data_sc_update_ci_move_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_move);
2272 gboolean file_data_sc_update_ci_copy_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_copy);
2277 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2279 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2284 * verify source and dest paths - dest image exists, etc.
2285 * it should detect all possible problems with the planned operation
2288 gint file_data_verify_ci(FileData *fd, GList *list)
2290 gint ret = CHANGE_OK;
2293 FileData *fd1 = NULL;
2297 DEBUG_1("Change checked: no change info: %s", fd->path);
2301 if (!isname(fd->path))
2303 /* this probably should not happen */
2304 ret |= CHANGE_NO_SRC;
2305 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2309 dir = remove_level_from_path(fd->path);
2311 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2312 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2313 fd->change->type != FILEDATA_CHANGE_RENAME &&
2314 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2317 ret |= CHANGE_WARN_UNSAVED_META;
2318 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2321 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2322 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2323 !access_file(fd->path, R_OK))
2325 ret |= CHANGE_NO_READ_PERM;
2326 DEBUG_1("Change checked: no read permission: %s", fd->path);
2328 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2329 !access_file(dir, W_OK))
2331 ret |= CHANGE_NO_WRITE_PERM_DIR;
2332 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2334 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2335 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2336 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2337 !access_file(fd->path, W_OK))
2339 ret |= CHANGE_WARN_NO_WRITE_PERM;
2340 DEBUG_1("Change checked: no write permission: %s", fd->path);
2342 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2343 - that means that there are no hard errors and warnings can be disabled
2344 - the destination is determined during the check
2346 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2348 /* determine destination file */
2349 gboolean have_dest = FALSE;
2350 gchar *dest_dir = NULL;
2352 if (options->metadata.save_in_image_file)
2354 if (file_data_can_write_directly(fd))
2356 /* we can write the file directly */
2357 if (access_file(fd->path, W_OK))
2363 if (options->metadata.warn_on_write_problems)
2365 ret |= CHANGE_WARN_NO_WRITE_PERM;
2366 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2370 else if (file_data_can_write_sidecar(fd))
2372 /* we can write sidecar */
2373 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2374 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2376 file_data_update_ci_dest(fd, sidecar);
2381 if (options->metadata.warn_on_write_problems)
2383 ret |= CHANGE_WARN_NO_WRITE_PERM;
2384 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2393 /* write private metadata file under ~/.geeqie */
2395 /* If an existing metadata file exists, we will try writing to
2396 * it's location regardless of the user's preference.
2398 gchar *metadata_path = NULL;
2400 /* but ignore XMP if we are not able to write it */
2401 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2403 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2405 if (metadata_path && !access_file(metadata_path, W_OK))
2407 g_free(metadata_path);
2408 metadata_path = NULL;
2415 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2416 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2418 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2420 metadata_path = g_build_filename(dest_dir, filename, NULL);
2424 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2426 file_data_update_ci_dest(fd, metadata_path);
2431 ret |= CHANGE_NO_WRITE_PERM_DEST;
2432 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2434 g_free(metadata_path);
2439 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2444 same = (strcmp(fd->path, fd->change->dest) == 0);
2448 const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2449 if (!dest_ext) dest_ext = "";
2450 if (!options->file_filter.disable_file_extension_checks)
2452 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2454 ret |= CHANGE_WARN_CHANGED_EXT;
2455 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2461 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2463 ret |= CHANGE_WARN_SAME;
2464 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2468 dest_dir = remove_level_from_path(fd->change->dest);
2470 if (!isdir(dest_dir))
2472 ret |= CHANGE_NO_DEST_DIR;
2473 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2475 else if (!access_file(dest_dir, W_OK))
2477 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2478 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2482 if (isfile(fd->change->dest))
2484 if (!access_file(fd->change->dest, W_OK))
2486 ret |= CHANGE_NO_WRITE_PERM_DEST;
2487 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2491 ret |= CHANGE_WARN_DEST_EXISTS;
2492 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2495 else if (isdir(fd->change->dest))
2497 ret |= CHANGE_DEST_EXISTS;
2498 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2505 /* During a rename operation, check if another planned destination file has
2508 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2509 fd->change->type == FILEDATA_CHANGE_COPY ||
2510 fd->change->type == FILEDATA_CHANGE_MOVE)
2517 if (fd1 != NULL && fd != fd1 )
2519 if (!strcmp(fd->change->dest, fd1->change->dest))
2521 ret |= CHANGE_DUPLICATE_DEST;
2527 fd->change->error = ret;
2528 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2535 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2540 ret = file_data_verify_ci(fd, list);
2542 work = fd->sidecar_files;
2545 FileData *sfd = work->data;
2547 ret |= file_data_verify_ci(sfd, list);
2554 gchar *file_data_get_error_string(gint error)
2556 GString *result = g_string_new("");
2558 if (error & CHANGE_NO_SRC)
2560 if (result->len > 0) g_string_append(result, ", ");
2561 g_string_append(result, _("file or directory does not exist"));
2564 if (error & CHANGE_DEST_EXISTS)
2566 if (result->len > 0) g_string_append(result, ", ");
2567 g_string_append(result, _("destination already exists"));
2570 if (error & CHANGE_NO_WRITE_PERM_DEST)
2572 if (result->len > 0) g_string_append(result, ", ");
2573 g_string_append(result, _("destination can't be overwritten"));
2576 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2578 if (result->len > 0) g_string_append(result, ", ");
2579 g_string_append(result, _("destination directory is not writable"));
2582 if (error & CHANGE_NO_DEST_DIR)
2584 if (result->len > 0) g_string_append(result, ", ");
2585 g_string_append(result, _("destination directory does not exist"));
2588 if (error & CHANGE_NO_WRITE_PERM_DIR)
2590 if (result->len > 0) g_string_append(result, ", ");
2591 g_string_append(result, _("source directory is not writable"));
2594 if (error & CHANGE_NO_READ_PERM)
2596 if (result->len > 0) g_string_append(result, ", ");
2597 g_string_append(result, _("no read permission"));
2600 if (error & CHANGE_WARN_NO_WRITE_PERM)
2602 if (result->len > 0) g_string_append(result, ", ");
2603 g_string_append(result, _("file is readonly"));
2606 if (error & CHANGE_WARN_DEST_EXISTS)
2608 if (result->len > 0) g_string_append(result, ", ");
2609 g_string_append(result, _("destination already exists and will be overwritten"));
2612 if (error & CHANGE_WARN_SAME)
2614 if (result->len > 0) g_string_append(result, ", ");
2615 g_string_append(result, _("source and destination are the same"));
2618 if (error & CHANGE_WARN_CHANGED_EXT)
2620 if (result->len > 0) g_string_append(result, ", ");
2621 g_string_append(result, _("source and destination have different extension"));
2624 if (error & CHANGE_WARN_UNSAVED_META)
2626 if (result->len > 0) g_string_append(result, ", ");
2627 g_string_append(result, _("there are unsaved metadata changes for the file"));
2630 if (error & CHANGE_DUPLICATE_DEST)
2632 if (result->len > 0) g_string_append(result, ", ");
2633 g_string_append(result, _("another destination file has the same filename"));
2636 return g_string_free(result, FALSE);
2639 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2642 gint all_errors = 0;
2643 gint common_errors = ~0;
2648 if (!list) return 0;
2650 num = g_list_length(list);
2651 errors = g_new(int, num);
2662 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2663 all_errors |= error;
2664 common_errors &= error;
2671 if (desc && all_errors)
2674 GString *result = g_string_new("");
2678 gchar *str = file_data_get_error_string(common_errors);
2679 g_string_append(result, str);
2680 g_string_append(result, "\n");
2694 error = errors[i] & ~common_errors;
2698 gchar *str = file_data_get_error_string(error);
2699 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2704 *desc = g_string_free(result, FALSE);
2713 * perform the change described by FileFataChangeInfo
2714 * it is used for internal operations,
2715 * this function actually operates with files on the filesystem
2716 * it should implement safe delete
2719 static gboolean file_data_perform_move(FileData *fd)
2721 g_assert(!strcmp(fd->change->source, fd->path));
2722 return move_file(fd->change->source, fd->change->dest);
2725 static gboolean file_data_perform_copy(FileData *fd)
2727 g_assert(!strcmp(fd->change->source, fd->path));
2728 return copy_file(fd->change->source, fd->change->dest);
2731 static gboolean file_data_perform_delete(FileData *fd)
2733 if (isdir(fd->path) && !islink(fd->path))
2734 return rmdir_utf8(fd->path);
2736 if (options->file_ops.safe_delete_enable)
2737 return file_util_safe_unlink(fd->path);
2739 return unlink_file(fd->path);
2742 gboolean file_data_perform_ci(FileData *fd)
2744 FileDataChangeType type = fd->change->type;
2748 case FILEDATA_CHANGE_MOVE:
2749 return file_data_perform_move(fd);
2750 case FILEDATA_CHANGE_COPY:
2751 return file_data_perform_copy(fd);
2752 case FILEDATA_CHANGE_RENAME:
2753 return file_data_perform_move(fd); /* the same as move */
2754 case FILEDATA_CHANGE_DELETE:
2755 return file_data_perform_delete(fd);
2756 case FILEDATA_CHANGE_WRITE_METADATA:
2757 return metadata_write_perform(fd);
2758 case FILEDATA_CHANGE_UNSPECIFIED:
2759 /* nothing to do here */
2767 gboolean file_data_sc_perform_ci(FileData *fd)
2770 gboolean ret = TRUE;
2771 FileDataChangeType type = fd->change->type;
2773 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2775 work = fd->sidecar_files;
2778 FileData *sfd = work->data;
2780 if (!file_data_perform_ci(sfd)) ret = FALSE;
2784 if (!file_data_perform_ci(fd)) ret = FALSE;
2790 * updates FileData structure according to FileDataChangeInfo
2793 gboolean file_data_apply_ci(FileData *fd)
2795 FileDataChangeType type = fd->change->type;
2798 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2800 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2801 file_data_planned_change_remove(fd);
2803 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2805 /* this change overwrites another file which is already known to other modules
2806 renaming fd would create duplicate FileData structure
2807 the best thing we can do is nothing
2808 FIXME: maybe we could copy stuff like marks
2810 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2814 file_data_set_path(fd, fd->change->dest);
2817 file_data_increment_version(fd);
2818 file_data_send_notification(fd, NOTIFY_CHANGE);
2823 gboolean file_data_sc_apply_ci(FileData *fd)
2826 FileDataChangeType type = fd->change->type;
2828 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2830 work = fd->sidecar_files;
2833 FileData *sfd = work->data;
2835 file_data_apply_ci(sfd);
2839 file_data_apply_ci(fd);
2844 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2847 if (fd->parent) fd = fd->parent;
2848 if (!g_list_find(list, fd)) return FALSE;
2850 work = fd->sidecar_files;
2853 if (!g_list_find(list, work->data)) return FALSE;
2859 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2864 /* change partial groups to independent files */
2869 FileData *fd = work->data;
2872 if (!file_data_list_contains_whole_group(list, fd))
2874 file_data_disable_grouping(fd, TRUE);
2877 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2883 /* remove sidecars from the list,
2884 they can be still acessed via main_fd->sidecar_files */
2888 FileData *fd = work->data;
2892 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2894 out = g_list_prepend(out, file_data_ref(fd));
2898 filelist_free(list);
2899 out = g_list_reverse(out);
2909 * notify other modules about the change described by FileDataChangeInfo
2912 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2913 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2914 implementation in view_file_list.c */
2917 typedef struct _NotifyIdleData NotifyIdleData;
2919 struct _NotifyIdleData {
2925 typedef struct _NotifyData NotifyData;
2927 struct _NotifyData {
2928 FileDataNotifyFunc func;
2930 NotifyPriority priority;
2933 static GList *notify_func_list = NULL;
2935 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2937 NotifyData *nda = (NotifyData *)a;
2938 NotifyData *ndb = (NotifyData *)b;
2940 if (nda->priority < ndb->priority) return -1;
2941 if (nda->priority > ndb->priority) return 1;
2945 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2948 GList *work = notify_func_list;
2952 NotifyData *nd = (NotifyData *)work->data;
2954 if (nd->func == func && nd->data == data)
2956 g_warning("Notify func already registered");
2962 nd = g_new(NotifyData, 1);
2965 nd->priority = priority;
2967 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2968 DEBUG_2("Notify func registered: %p", nd);
2973 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2975 GList *work = notify_func_list;
2979 NotifyData *nd = (NotifyData *)work->data;
2981 if (nd->func == func && nd->data == data)
2983 notify_func_list = g_list_delete_link(notify_func_list, work);
2985 DEBUG_2("Notify func unregistered: %p", nd);
2991 g_warning("Notify func not found");
2996 gboolean file_data_send_notification_idle_cb(gpointer data)
2998 NotifyIdleData *nid = (NotifyIdleData *)data;
2999 GList *work = notify_func_list;
3003 NotifyData *nd = (NotifyData *)work->data;
3005 nd->func(nid->fd, nid->type, nd->data);
3008 file_data_unref(nid->fd);
3013 void file_data_send_notification(FileData *fd, NotifyType type)
3015 GList *work = notify_func_list;
3019 NotifyData *nd = (NotifyData *)work->data;
3021 nd->func(fd, type, nd->data);
3025 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3026 nid->fd = file_data_ref(fd);
3028 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3032 static GHashTable *file_data_monitor_pool = NULL;
3033 static guint realtime_monitor_id = 0; /* event source id */
3035 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
3039 file_data_check_changed_files(fd);
3041 DEBUG_1("monitor %s", fd->path);
3044 static gboolean realtime_monitor_cb(gpointer data)
3046 if (!options->update_on_time_change) return TRUE;
3047 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
3051 gboolean file_data_register_real_time_monitor(FileData *fd)
3057 if (!file_data_monitor_pool)
3058 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3060 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3062 DEBUG_1("Register realtime %d %s", count, fd->path);
3065 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3067 if (!realtime_monitor_id)
3069 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
3075 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3079 g_assert(file_data_monitor_pool);
3081 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3083 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3085 g_assert(count > 0);
3090 g_hash_table_remove(file_data_monitor_pool, fd);
3092 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3094 file_data_unref(fd);
3096 if (g_hash_table_size(file_data_monitor_pool) == 0)
3098 g_source_remove(realtime_monitor_id);
3099 realtime_monitor_id = 0;
3105 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */