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.
35 #include <glib-object.h>
44 #include "filefilter.h"
45 #include "histogram.h"
47 #include "main-defines.h"
52 #include "secure-save.h"
53 #include "thumb-standard.h"
55 #include "ui-fileops.h"
58 gint global_file_data_count = 0;
61 static GHashTable *file_data_pool = nullptr;
62 static GHashTable *file_data_planned_change_hash = nullptr;
64 static gint sidecar_file_priority(const gchar *extension);
65 static void file_data_check_sidecars(const GList *basename_list);
66 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
69 static SortType filelist_sort_method = SORT_NONE;
70 static gboolean filelist_sort_ascend = TRUE;
71 static gboolean filelist_sort_case = TRUE;
74 *-----------------------------------------------------------------------------
75 * text conversion utils
76 *-----------------------------------------------------------------------------
79 gchar *text_from_size(gint64 size)
89 /* what I would like to use is printf("%'d", size)
90 * BUT: not supported on every libc :(
94 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
95 a = g_strdup_printf("%d%09d", static_cast<guint>(size / 1000000000), static_cast<guint>(size % 1000000000));
99 a = g_strdup_printf("%d", static_cast<guint>(size));
105 b = g_new(gchar, l + n + 1);
130 gchar *text_from_size_abrev(gint64 size)
132 if (size < static_cast<gint64>(1024))
134 return g_strdup_printf(_("%d bytes"), static_cast<gint>(size));
136 if (size < static_cast<gint64>(1048576))
138 return g_strdup_printf(_("%.1f KiB"), static_cast<gdouble>(size) / 1024.0);
140 if (size < static_cast<gint64>(1073741824))
142 return g_strdup_printf(_("%.1f MiB"), static_cast<gdouble>(size) / 1048576.0);
145 /* to avoid overflowing the gdouble, do division in two steps */
147 return g_strdup_printf(_("%.1f GiB"), static_cast<gdouble>(size) / 1024.0);
150 /* note: returned string is valid until next call to text_from_time() */
151 const gchar *text_from_time(time_t t)
153 static gchar *ret = nullptr;
157 GError *error = nullptr;
159 btime = localtime(&t);
161 /* the %x warning about 2 digit years is not an error */
162 buflen = strftime(buf, sizeof(buf), "%x %X", btime);
163 if (buflen < 1) return "";
166 ret = g_locale_to_utf8(buf, buflen, nullptr, nullptr, &error);
169 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
178 *-----------------------------------------------------------------------------
179 * changed files detection and notification
180 *-----------------------------------------------------------------------------
183 void file_data_increment_version(FileData *fd)
189 fd->parent->version++;
190 fd->parent->valid_marks = 0;
194 static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st)
196 if (fd->size != st->st_size ||
197 fd->date != st->st_mtime)
199 fd->size = st->st_size;
200 fd->date = st->st_mtime;
201 fd->cdate = st->st_ctime;
202 fd->mode = st->st_mode;
203 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
204 fd->thumb_pixbuf = nullptr;
205 file_data_increment_version(fd);
206 file_data_send_notification(fd, NOTIFY_REREAD);
212 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
214 gboolean ret = FALSE;
217 ret = file_data_check_changed_single_file(fd, st);
219 work = fd->sidecar_files;
222 auto sfd = static_cast<FileData *>(work->data);
226 if (!stat_utf8(sfd->path, &st))
231 file_data_disconnect_sidecar_file(fd, sfd);
233 file_data_increment_version(sfd);
234 file_data_send_notification(sfd, NOTIFY_REREAD);
235 file_data_unref(sfd);
239 ret |= file_data_check_changed_files_recursive(sfd, &st);
245 gboolean file_data_check_changed_files(FileData *fd)
247 gboolean ret = FALSE;
250 if (fd->parent) fd = fd->parent;
252 if (!stat_utf8(fd->path, &st))
256 FileData *sfd = nullptr;
258 /* parent is missing, we have to rebuild whole group */
263 /* file_data_disconnect_sidecar_file might delete the file,
264 we have to keep the reference to prevent this */
265 sidecars = filelist_copy(fd->sidecar_files);
270 sfd = static_cast<FileData *>(work->data);
273 file_data_disconnect_sidecar_file(fd, sfd);
275 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
276 /* now we can release the sidecars */
277 filelist_free(sidecars);
278 file_data_increment_version(fd);
279 file_data_send_notification(fd, NOTIFY_REREAD);
284 ret |= file_data_check_changed_files_recursive(fd, &st);
291 *-----------------------------------------------------------------------------
292 * file name, extension, sorting, ...
293 *-----------------------------------------------------------------------------
296 static void file_data_set_collate_keys(FileData *fd)
298 gchar *caseless_name;
301 valid_name = g_filename_display_name(fd->name);
302 caseless_name = g_utf8_casefold(valid_name, -1);
304 g_free(fd->collate_key_name);
305 g_free(fd->collate_key_name_nocase);
307 fd->collate_key_name_natural = g_utf8_collate_key_for_filename(fd->name, -1);
308 fd->collate_key_name_nocase_natural = g_utf8_collate_key_for_filename(caseless_name, -1);
309 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
310 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
313 g_free(caseless_name);
316 static void file_data_set_path(FileData *fd, const gchar *path)
318 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
319 g_assert(file_data_pool);
323 if (fd->original_path)
325 g_hash_table_remove(file_data_pool, fd->original_path);
326 g_free(fd->original_path);
329 g_assert(!g_hash_table_lookup(file_data_pool, path));
331 fd->original_path = g_strdup(path);
332 g_hash_table_insert(file_data_pool, fd->original_path, fd);
334 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
336 fd->path = g_strdup(path);
338 fd->extension = fd->name + 1;
339 file_data_set_collate_keys(fd);
343 fd->path = g_strdup(path);
344 fd->name = filename_from_path(fd->path);
346 if (strcmp(fd->name, "..") == 0)
348 gchar *dir = remove_level_from_path(path);
350 fd->path = remove_level_from_path(dir);
353 fd->extension = fd->name + 2;
354 file_data_set_collate_keys(fd);
358 if (strcmp(fd->name, ".") == 0)
361 fd->path = remove_level_from_path(path);
363 fd->extension = fd->name + 1;
364 file_data_set_collate_keys(fd);
368 fd->extension = registered_extension_from_path(fd->path);
369 if (fd->extension == nullptr)
371 fd->extension = fd->name + strlen(fd->name);
374 fd->sidecar_priority = sidecar_file_priority(fd->extension);
375 file_data_set_collate_keys(fd);
379 *-----------------------------------------------------------------------------
380 * create or reuse Filedata
381 *-----------------------------------------------------------------------------
384 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
390 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
392 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
395 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
397 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
403 if (!fd && file_data_planned_change_hash)
405 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_planned_change_hash, path_utf8));
408 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
409 if (!isfile(fd->path))
412 file_data_apply_ci(fd);
423 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
425 #ifdef DEBUG_FILEDATA
428 file_data_check_changed_single_file(fd, st);
430 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
435 fd = g_new0(FileData, 1);
436 #ifdef DEBUG_FILEDATA
437 global_file_data_count++;
438 DEBUG_2("file data count++: %d", global_file_data_count);
441 fd->size = st->st_size;
442 fd->date = st->st_mtime;
443 fd->cdate = st->st_ctime;
444 fd->mode = st->st_mode;
446 fd->magick = FD_MAGICK;
448 fd->rating = STAR_RATING_NOT_READ;
449 fd->format_class = filter_file_get_class(path_utf8);
453 user = getpwuid(st->st_uid);
456 fd->owner = g_strdup_printf("%u", st->st_uid);
460 fd->owner = g_strdup(user->pw_name);
463 group = getgrgid(st->st_gid);
466 fd->group = g_strdup_printf("%u", st->st_gid);
470 fd->group = g_strdup(group->gr_name);
473 fd->sym_link = get_symbolic_link(path_utf8);
475 if (disable_sidecars) fd->disable_grouping = TRUE;
477 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
482 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
484 gchar *path_utf8 = path_to_utf8(path);
485 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
491 FileData *file_data_new_simple(const gchar *path_utf8)
496 if (!stat_utf8(path_utf8, &st))
502 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
503 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
512 void read_exif_time_data(FileData *file)
514 if (file->exifdate > 0)
516 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
527 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
528 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), (void *)file, file->path);
540 sscanf(tmp, "%4u:%2u:%2u %2u:%2u:%2u", &year, &month, &day, &hour, &min, &sec);
541 time_str.tm_year = year - 1900;
542 time_str.tm_mon = month - 1;
543 time_str.tm_mday = day;
544 time_str.tm_hour = hour;
545 time_str.tm_min = min;
546 time_str.tm_sec = sec;
547 time_str.tm_isdst = 0;
549 file->exifdate = mktime(&time_str);
555 void read_exif_time_digitized_data(FileData *file)
557 if (file->exifdate_digitized > 0)
559 DEBUG_1("%s set_exif_time_digitized_data: Already exists for %s", get_exec_time(), file->path);
570 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeDigitized");
571 DEBUG_2("%s set_exif_time_digitized_data: reading %p %s", get_exec_time(), (void *)file, file->path);
583 sscanf(tmp, "%4u:%2u:%2u %2u:%2u:%2u", &year, &month, &day, &hour, &min, &sec);
584 time_str.tm_year = year - 1900;
585 time_str.tm_mon = month - 1;
586 time_str.tm_mday = day;
587 time_str.tm_hour = hour;
588 time_str.tm_min = min;
589 time_str.tm_sec = sec;
590 time_str.tm_isdst = 0;
592 file->exifdate_digitized = mktime(&time_str);
598 void read_rating_data(FileData *file)
602 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
605 file->rating = atoi(rating_str);
614 #pragma GCC diagnostic push
615 #pragma GCC diagnostic ignored "-Wunused-function"
616 void set_exif_time_data_unused(GList *files)
618 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
622 auto *file = static_cast<FileData *>(files->data);
624 read_exif_time_data(file);
629 void set_exif_time_digitized_data_unused(GList *files)
631 DEBUG_1("%s set_exif_time_digitized_data: ...", get_exec_time());
635 auto *file = static_cast<FileData *>(files->data);
637 read_exif_time_digitized_data(file);
642 void set_rating_data_unused(GList *files)
645 DEBUG_1("%s set_rating_data: ...", get_exec_time());
649 auto *file = static_cast<FileData *>(files->data);
650 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
653 file->rating = atoi(rating_str);
659 #pragma GCC diagnostic pop
661 FileData *file_data_new_no_grouping(const gchar *path_utf8)
665 if (!stat_utf8(path_utf8, &st))
671 return file_data_new(path_utf8, &st, TRUE);
674 FileData *file_data_new_dir(const gchar *path_utf8)
678 if (!stat_utf8(path_utf8, &st))
684 /* dir or non-existing yet */
685 g_assert(S_ISDIR(st.st_mode));
687 return file_data_new(path_utf8, &st, TRUE);
691 *-----------------------------------------------------------------------------
693 *-----------------------------------------------------------------------------
696 #ifdef DEBUG_FILEDATA
697 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
699 FileData *file_data_ref(FileData *fd)
702 if (fd == nullptr) return nullptr;
703 if (fd->magick != FD_MAGICK)
704 #ifdef DEBUG_FILEDATA
705 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, (void *)fd);
707 log_printf("Error: fd magick mismatch fd=%p", fd);
709 g_assert(fd->magick == FD_MAGICK);
712 #ifdef DEBUG_FILEDATA
713 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->path, file, line);
715 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
721 * @brief Print ref. count and image name
724 * Print image ref. count and full path name of all images in
725 * the file_data_pool.
727 * Used only by debug_fd()
729 void file_data_dump()
731 #ifdef DEBUG_FILEDATA
737 list = g_hash_table_get_values(file_data_pool);
739 log_printf("%d", global_file_data_count);
740 log_printf("%d", g_list_length(list));
745 fd = static_cast<FileData *>(work->data);
746 log_printf("%-4d %s", fd->ref, fd->path);
755 static void file_data_free(FileData *fd)
757 g_assert(fd->magick == FD_MAGICK);
758 g_assert(fd->ref == 0);
759 g_assert(!fd->locked);
761 #ifdef DEBUG_FILEDATA
762 global_file_data_count--;
763 DEBUG_2("file data count--: %d", global_file_data_count);
766 metadata_cache_free(fd);
767 g_hash_table_remove(file_data_pool, fd->original_path);
770 g_free(fd->original_path);
771 g_free(fd->collate_key_name);
772 g_free(fd->collate_key_name_nocase);
773 g_free(fd->extended_extension);
774 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
775 histmap_free(fd->histmap);
778 g_free(fd->sym_link);
779 g_free(fd->format_name);
780 g_assert(fd->sidecar_files == nullptr); /* sidecar files must be freed before calling this */
782 file_data_change_info_free(nullptr, fd);
787 * @brief Checks if the FileData is referenced
789 * Checks the refcount and whether the FileData is locked.
791 static gboolean file_data_check_has_ref(FileData *fd)
793 return fd->ref > 0 || fd->locked;
797 * @brief Consider freeing a FileData.
799 * This function will free a FileData and its children provided that neither its parent nor it has
800 * a positive refcount, and provided that neither is locked.
802 static void file_data_consider_free(FileData *fd)
805 FileData *parent = fd->parent ? fd->parent : fd;
807 g_assert(fd->magick == FD_MAGICK);
808 if (file_data_check_has_ref(fd)) return;
809 if (file_data_check_has_ref(parent)) return;
811 work = parent->sidecar_files;
814 auto sfd = static_cast<FileData *>(work->data);
815 if (file_data_check_has_ref(sfd)) return;
819 /* Neither the parent nor the siblings are referenced, so we can free everything */
820 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
821 fd->path, fd->parent ? parent->path : "-");
823 g_list_free_full(parent->sidecar_files, reinterpret_cast<GDestroyNotify>(file_data_free));
824 parent->sidecar_files = nullptr;
826 file_data_free(parent);
829 #ifdef DEBUG_FILEDATA
830 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
832 void file_data_unref(FileData *fd)
835 if (fd == nullptr) return;
836 if (fd->magick != FD_MAGICK)
837 #ifdef DEBUG_FILEDATA
838 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, (void *)fd);
840 log_printf("Error: fd magick mismatch fd=%p", fd);
842 g_assert(fd->magick == FD_MAGICK);
845 #ifdef DEBUG_FILEDATA
846 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->locked, fd->path,
849 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
852 // Free FileData if it's no longer ref'd
853 file_data_consider_free(fd);
857 * @brief Lock the FileData in memory.
859 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
860 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
861 * even if the code would continue to function properly even if the FileData were freed. Code that
862 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
864 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
865 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
867 void file_data_lock(FileData *fd)
869 if (fd == nullptr) return;
870 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
872 g_assert(fd->magick == FD_MAGICK);
875 DEBUG_2("file_data_ref fd=%p (%d): '%s'", (void *)fd, fd->ref, fd->path);
879 * @brief Reset the maintain-FileData-in-memory lock
881 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
882 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
883 * keeping it from being freed.
885 void file_data_unlock(FileData *fd)
887 if (fd == nullptr) return;
888 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
890 g_assert(fd->magick == FD_MAGICK);
893 // Free FileData if it's no longer ref'd
894 file_data_consider_free(fd);
898 * @brief Lock all of the FileDatas in the provided list
900 * @see file_data_lock(#FileData)
902 void file_data_lock_list(GList *list)
909 auto fd = static_cast<FileData *>(work->data);
916 * @brief Unlock all of the FileDatas in the provided list
918 * @see #file_data_unlock(#FileData)
920 void file_data_unlock_list(GList *list)
927 auto fd = static_cast<FileData *>(work->data);
929 file_data_unlock(fd);
934 *-----------------------------------------------------------------------------
935 * sidecar file info struct
936 *-----------------------------------------------------------------------------
939 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
941 auto fda = static_cast<const FileData *>(a);
942 auto fdb = static_cast<const FileData *>(b);
944 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
945 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
947 return strcmp(fdb->extension, fda->extension);
951 static gint sidecar_file_priority(const gchar *extension)
956 if (extension == nullptr)
959 work = sidecar_ext_get_list();
962 auto ext = static_cast<gchar *>(work->data);
965 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
971 static void file_data_check_sidecars(const GList *basename_list)
973 /* basename_list contains the new group - first is the parent, then sorted sidecars */
974 /* all files in the list have ref count > 0 */
981 if (!basename_list) return;
984 DEBUG_2("basename start");
985 work = basename_list;
988 auto fd = static_cast<FileData *>(work->data);
990 g_assert(fd->magick == FD_MAGICK);
991 DEBUG_2("basename: %p %s", (void *)fd, fd->name);
994 g_assert(fd->parent->magick == FD_MAGICK);
995 DEBUG_2(" parent: %p", (void *)fd->parent);
997 s_work = fd->sidecar_files;
1000 auto sfd = static_cast<FileData *>(s_work->data);
1001 s_work = s_work->next;
1002 g_assert(sfd->magick == FD_MAGICK);
1003 DEBUG_2(" sidecar: %p %s", (void *)sfd, sfd->name);
1006 g_assert(fd->parent == nullptr || fd->sidecar_files == nullptr);
1009 parent_fd = static_cast<FileData *>(basename_list->data);
1011 /* check if the second and next entries of basename_list are already connected
1012 as sidecars of the first entry (parent_fd) */
1013 work = basename_list->next;
1014 s_work = parent_fd->sidecar_files;
1016 while (work && s_work)
1018 if (work->data != s_work->data) break;
1020 s_work = s_work->next;
1023 if (!work && !s_work)
1025 DEBUG_2("basename no change");
1026 return; /* no change in grouping */
1029 /* we have to regroup it */
1031 /* first, disconnect everything and send notification*/
1033 work = basename_list;
1036 auto fd = static_cast<FileData *>(work->data);
1038 g_assert(fd->parent == nullptr || fd->sidecar_files == nullptr);
1042 FileData *old_parent = fd->parent;
1043 g_assert(old_parent->parent == nullptr || old_parent->sidecar_files == nullptr);
1044 file_data_ref(old_parent);
1045 file_data_disconnect_sidecar_file(old_parent, fd);
1046 file_data_send_notification(old_parent, NOTIFY_REREAD);
1047 file_data_unref(old_parent);
1050 while (fd->sidecar_files)
1052 auto sfd = static_cast<FileData *>(fd->sidecar_files->data);
1053 g_assert(sfd->parent == nullptr || sfd->sidecar_files == nullptr);
1055 file_data_disconnect_sidecar_file(fd, sfd);
1056 file_data_send_notification(sfd, NOTIFY_REREAD);
1057 file_data_unref(sfd);
1059 file_data_send_notification(fd, NOTIFY_GROUPING);
1061 g_assert(fd->parent == nullptr && fd->sidecar_files == nullptr);
1064 /* now we can form the new group */
1065 work = basename_list->next;
1066 new_sidecars = nullptr;
1069 auto sfd = static_cast<FileData *>(work->data);
1070 g_assert(sfd->magick == FD_MAGICK);
1071 g_assert(sfd->parent == nullptr && sfd->sidecar_files == nullptr);
1072 sfd->parent = parent_fd;
1073 new_sidecars = g_list_prepend(new_sidecars, sfd);
1076 g_assert(parent_fd->sidecar_files == nullptr);
1077 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
1078 DEBUG_1("basename group changed for %s", parent_fd->path);
1082 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
1084 g_assert(target->magick == FD_MAGICK);
1085 g_assert(sfd->magick == FD_MAGICK);
1086 g_assert(g_list_find(target->sidecar_files, sfd));
1088 file_data_ref(target);
1091 g_assert(sfd->parent == target);
1093 file_data_increment_version(sfd); /* increments both sfd and target */
1095 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
1096 sfd->parent = nullptr;
1097 g_free(sfd->extended_extension);
1098 sfd->extended_extension = nullptr;
1100 file_data_unref(target);
1101 file_data_unref(sfd);
1104 /* disables / enables grouping for particular file, sends UPDATE notification */
1105 void file_data_disable_grouping(FileData *fd, gboolean disable)
1107 if (!fd->disable_grouping == !disable) return;
1109 fd->disable_grouping = !!disable;
1115 FileData *parent = file_data_ref(fd->parent);
1116 file_data_disconnect_sidecar_file(parent, fd);
1117 file_data_send_notification(parent, NOTIFY_GROUPING);
1118 file_data_unref(parent);
1120 else if (fd->sidecar_files)
1122 GList *sidecar_files = filelist_copy(fd->sidecar_files);
1123 GList *work = sidecar_files;
1126 auto sfd = static_cast<FileData *>(work->data);
1128 file_data_disconnect_sidecar_file(fd, sfd);
1129 file_data_send_notification(sfd, NOTIFY_GROUPING);
1131 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
1132 filelist_free(sidecar_files);
1136 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
1141 file_data_increment_version(fd);
1142 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
1144 file_data_send_notification(fd, NOTIFY_GROUPING);
1147 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
1154 auto fd = static_cast<FileData *>(work->data);
1156 file_data_disable_grouping(fd, disable);
1164 *-----------------------------------------------------------------------------
1166 *-----------------------------------------------------------------------------
1170 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
1173 if (!filelist_sort_ascend)
1178 switch (filelist_sort_method)
1183 if (fa->size < fb->size) return -1;
1184 if (fa->size > fb->size) return 1;
1185 /* fall back to name */
1188 if (fa->date < fb->date) return -1;
1189 if (fa->date > fb->date) return 1;
1190 /* fall back to name */
1193 if (fa->cdate < fb->cdate) return -1;
1194 if (fa->cdate > fb->cdate) return 1;
1195 /* fall back to name */
1198 if (fa->exifdate < fb->exifdate) return -1;
1199 if (fa->exifdate > fb->exifdate) return 1;
1200 /* fall back to name */
1202 case SORT_EXIFTIMEDIGITIZED:
1203 if (fa->exifdate_digitized < fb->exifdate_digitized) return -1;
1204 if (fa->exifdate_digitized > fb->exifdate_digitized) return 1;
1205 /* fall back to name */
1208 if (fa->rating < fb->rating) return -1;
1209 if (fa->rating > fb->rating) return 1;
1210 /* fall back to name */
1213 if (fa->format_class < fb->format_class) return -1;
1214 if (fa->format_class > fb->format_class) return 1;
1215 /* fall back to name */
1218 ret = strcmp(fa->collate_key_name_natural, fb->collate_key_name_natural);
1219 if (ret != 0) return ret;
1220 /* fall back to name */
1226 if (filelist_sort_case)
1227 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1229 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1231 if (ret != 0) return ret;
1233 /* do not return 0 unless the files are really the same
1234 file_data_pool ensures that original_path is unique
1236 return strcmp(fa->original_path, fb->original_path);
1239 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1241 filelist_sort_method = method;
1242 filelist_sort_ascend = ascend;
1243 return filelist_sort_compare_filedata(fa, fb);
1246 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1248 return filelist_sort_compare_filedata(static_cast<FileData *>(a), static_cast<FileData *>(b));
1251 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, gboolean case_sensitive, GCompareFunc cb)
1253 filelist_sort_method = method;
1254 filelist_sort_ascend = ascend;
1255 filelist_sort_case = case_sensitive;
1256 return g_list_sort(list, cb);
1259 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, gboolean case_sensitive, GCompareFunc cb)
1261 filelist_sort_method = method;
1262 filelist_sort_ascend = ascend;
1263 filelist_sort_case = case_sensitive;
1264 return g_list_insert_sorted(list, data, cb);
1267 GList *filelist_sort(GList *list, SortType method, gboolean ascend, gboolean case_sensitive)
1269 return filelist_sort_full(list, method, ascend, case_sensitive, reinterpret_cast<GCompareFunc>(filelist_sort_file_cb));
1272 #pragma GCC diagnostic push
1273 #pragma GCC diagnostic ignored "-Wunused-function"
1274 GList *filelist_insert_sort_unused(GList *list, FileData *fd, SortType method, gboolean ascend)
1276 return filelist_insert_sort_full(list, fd, method, ascend, ascend, (GCompareFunc) filelist_sort_file_cb);
1278 #pragma GCC diagnostic pop
1281 *-----------------------------------------------------------------------------
1282 * basename hash - grouping of sidecars in filelist
1283 *-----------------------------------------------------------------------------
1287 static GHashTable *file_data_basename_hash_new()
1289 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, nullptr);
1292 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1295 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1297 list = static_cast<GList *>(g_hash_table_lookup(basename_hash, basename));
1301 DEBUG_1("TG: basename_hash not found for %s",fd->path);
1302 const gchar *parent_extension = registered_extension_from_path(basename);
1304 if (parent_extension)
1306 DEBUG_1("TG: parent extension %s",parent_extension);
1307 gchar *parent_basename = g_strndup(basename, parent_extension - basename);
1308 DEBUG_1("TG: parent basename %s",parent_basename);
1309 auto parent_fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, basename));
1312 DEBUG_1("TG: parent fd found");
1313 list = static_cast<GList *>(g_hash_table_lookup(basename_hash, parent_basename));
1314 if (!g_list_find(list, parent_fd))
1316 DEBUG_1("TG: parent fd doesn't fit");
1317 g_free(parent_basename);
1323 basename = parent_basename;
1324 fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
1330 if (!g_list_find(list, fd))
1332 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1333 g_hash_table_insert(basename_hash, basename, list);
1342 static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
1344 file_data_basename_hash_insert(static_cast<GHashTable *>(basename_hash), static_cast<FileData *>(fd));
1347 static void file_data_basename_hash_remove_list(gpointer, gpointer value, gpointer)
1349 filelist_free(static_cast<GList *>(value));
1352 static void file_data_basename_hash_free(GHashTable *basename_hash)
1354 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, nullptr);
1355 g_hash_table_destroy(basename_hash);
1359 *-----------------------------------------------------------------------------
1360 * handling sidecars in filelist
1361 *-----------------------------------------------------------------------------
1364 static GList *filelist_filter_out_sidecars(GList *flist)
1366 GList *work = flist;
1367 GList *flist_filtered = nullptr;
1371 auto fd = static_cast<FileData *>(work->data);
1374 if (fd->parent) /* remove fd's that are children */
1375 file_data_unref(fd);
1377 flist_filtered = g_list_prepend(flist_filtered, fd);
1381 return flist_filtered;
1384 static void file_data_basename_hash_to_sidecars(gpointer, gpointer value, gpointer)
1386 auto basename_list = static_cast<GList *>(value);
1387 file_data_check_sidecars(basename_list);
1391 static gboolean is_hidden_file(const gchar *name)
1393 if (name[0] != '.') return FALSE;
1394 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1399 *-----------------------------------------------------------------------------
1400 * the main filelist function
1401 *-----------------------------------------------------------------------------
1404 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1409 GList *dlist = nullptr;
1410 GList *flist = nullptr;
1411 GList *xmp_files = nullptr;
1412 gint (*stat_func)(const gchar *path, struct stat *buf);
1413 GHashTable *basename_hash = nullptr;
1415 g_assert(files || dirs);
1417 if (files) *files = nullptr;
1418 if (dirs) *dirs = nullptr;
1420 pathl = path_from_utf8(dir_path);
1421 if (!pathl) return FALSE;
1423 dp = opendir(pathl);
1430 if (files) basename_hash = file_data_basename_hash_new();
1432 if (follow_symlinks)
1437 while ((dir = readdir(dp)) != nullptr)
1439 struct stat ent_sbuf;
1440 const gchar *name = dir->d_name;
1443 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1446 filepath = g_build_filename(pathl, name, NULL);
1447 if (stat_func(filepath, &ent_sbuf) >= 0)
1449 if (S_ISDIR(ent_sbuf.st_mode))
1451 /* we ignore the .thumbnails dir for cleanliness */
1453 (name[0] != '.' || (name[1] != '\0' && (name[1] != '.' || name[2] != '\0'))) &&
1454 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1455 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1456 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1458 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1463 if (files && filter_name_exists(name))
1465 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1466 flist = g_list_prepend(flist, fd);
1467 if (fd->sidecar_priority && !fd->disable_grouping)
1469 if (strcmp(fd->extension, ".xmp") != 0)
1470 file_data_basename_hash_insert(basename_hash, fd);
1472 xmp_files = g_list_append(xmp_files, fd);
1479 if (errno == EOVERFLOW)
1481 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1493 g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
1494 g_list_free(xmp_files);
1497 if (dirs) *dirs = dlist;
1501 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, nullptr);
1503 *files = filelist_filter_out_sidecars(flist);
1505 if (basename_hash) file_data_basename_hash_free(basename_hash);
1510 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1512 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1515 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1517 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1520 FileData *file_data_new_group(const gchar *path_utf8)
1527 if (!file_data_pool)
1529 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
1532 if (!stat_utf8(path_utf8, &st))
1538 if (S_ISDIR(st.st_mode))
1539 return file_data_new(path_utf8, &st, TRUE);
1541 dir = remove_level_from_path(path_utf8);
1543 filelist_read_real(dir, &files, nullptr, TRUE);
1545 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
1546 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1552 filelist_free(files);
1558 void filelist_free(GList *list)
1565 file_data_unref((FileData *)work->data);
1573 GList *filelist_copy(GList *list)
1575 GList *new_list = nullptr;
1577 for (GList *work = list; work; work = work->next)
1579 auto fd = static_cast<FileData *>(work->data);
1581 new_list = g_list_prepend(new_list, file_data_ref(fd));
1584 return g_list_reverse(new_list);
1587 GList *filelist_from_path_list(GList *list)
1589 GList *new_list = nullptr;
1597 path = static_cast<gchar *>(work->data);
1600 new_list = g_list_prepend(new_list, file_data_new_group(path));
1603 return g_list_reverse(new_list);
1606 GList *filelist_to_path_list(GList *list)
1608 GList *new_list = nullptr;
1616 fd = static_cast<FileData *>(work->data);
1619 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1622 return g_list_reverse(new_list);
1625 GList *filelist_filter(GList *list, gboolean is_dir_list)
1629 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1634 auto fd = static_cast<FileData *>(work->data);
1635 const gchar *name = fd->name;
1639 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1640 (!is_dir_list && !filter_name_exists(name)) ||
1641 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1642 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1644 list = g_list_remove_link(list, link);
1645 file_data_unref(fd);
1654 *-----------------------------------------------------------------------------
1655 * filelist recursive
1656 *-----------------------------------------------------------------------------
1659 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1661 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1664 GList *filelist_sort_path(GList *list)
1666 return g_list_sort(list, filelist_sort_path_cb);
1669 static void filelist_recursive_append(GList **list, GList *dirs)
1676 auto fd = static_cast<FileData *>(work->data);
1680 if (filelist_read(fd, &f, &d))
1682 f = filelist_filter(f, FALSE);
1683 f = filelist_sort_path(f);
1684 *list = g_list_concat(*list, f);
1686 d = filelist_filter(d, TRUE);
1687 d = filelist_sort_path(d);
1688 filelist_recursive_append(list, d);
1696 static void filelist_recursive_append_full(GList **list, GList *dirs, SortType method, gboolean ascend, gboolean case_sensitive)
1703 auto fd = static_cast<FileData *>(work->data);
1707 if (filelist_read(fd, &f, &d))
1709 f = filelist_filter(f, FALSE);
1710 f = filelist_sort_full(f, method, ascend, case_sensitive, reinterpret_cast<GCompareFunc>(filelist_sort_file_cb));
1711 *list = g_list_concat(*list, f);
1713 d = filelist_filter(d, TRUE);
1714 d = filelist_sort_path(d);
1715 filelist_recursive_append_full(list, d, method, ascend, case_sensitive);
1723 GList *filelist_recursive(FileData *dir_fd)
1728 if (!filelist_read(dir_fd, &list, &d)) return nullptr;
1729 list = filelist_filter(list, FALSE);
1730 list = filelist_sort_path(list);
1732 d = filelist_filter(d, TRUE);
1733 d = filelist_sort_path(d);
1734 filelist_recursive_append(&list, d);
1740 GList *filelist_recursive_full(FileData *dir_fd, SortType method, gboolean ascend, gboolean case_sensitive)
1745 if (!filelist_read(dir_fd, &list, &d)) return nullptr;
1746 list = filelist_filter(list, FALSE);
1747 list = filelist_sort_full(list, method, ascend, case_sensitive, reinterpret_cast<GCompareFunc>(filelist_sort_file_cb));
1749 d = filelist_filter(d, TRUE);
1750 d = filelist_sort_path(d);
1751 filelist_recursive_append_full(&list, d, method, ascend, case_sensitive);
1758 *-----------------------------------------------------------------------------
1759 * file modification support
1760 *-----------------------------------------------------------------------------
1764 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1766 if (!fdci && fd) fdci = fd->change;
1770 g_free(fdci->source);
1775 if (fd) fd->change = nullptr;
1778 static gboolean file_data_can_write_directly(FileData *fd)
1780 return filter_name_is_writable(fd->extension);
1783 static gboolean file_data_can_write_sidecar(FileData *fd)
1785 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1788 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1790 gchar *sidecar_path = nullptr;
1793 if (!file_data_can_write_sidecar(fd)) return nullptr;
1795 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1796 gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1799 auto sfd = static_cast<FileData *>(work->data);
1801 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1803 sidecar_path = g_strdup(sfd->path);
1807 g_free(extended_extension);
1809 if (!existing_only && !sidecar_path)
1811 if (options->metadata.sidecar_extended_name)
1812 sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1815 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1816 sidecar_path = g_strconcat(base, ".xmp", NULL);
1821 return sidecar_path;
1825 * marks and orientation
1828 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1829 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1830 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1831 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1833 gboolean file_data_get_mark(FileData *fd, gint n)
1835 gboolean valid = (fd->valid_marks & (1 << n));
1837 if (file_data_get_mark_func[n] && !valid)
1839 guint old = fd->marks;
1840 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1842 if (!value != !(fd->marks & (1 << n)))
1844 fd->marks = fd->marks ^ (1 << n);
1847 fd->valid_marks |= (1 << n);
1848 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1850 file_data_unref(fd);
1852 else if (!old && fd->marks)
1858 return !!(fd->marks & (1 << n));
1861 guint file_data_get_marks(FileData *fd)
1864 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1868 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1871 if (!value == !file_data_get_mark(fd, n)) return;
1873 if (file_data_set_mark_func[n])
1875 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1880 fd->marks = fd->marks ^ (1 << n);
1882 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1884 file_data_unref(fd);
1886 else if (!old && fd->marks)
1891 file_data_increment_version(fd);
1892 file_data_send_notification(fd, NOTIFY_MARKS);
1895 gboolean file_data_filter_marks(FileData *fd, guint filter)
1898 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1899 return ((fd->marks & filter) == filter);
1902 GList *file_data_filter_marks_list(GList *list, guint filter)
1909 auto fd = static_cast<FileData *>(work->data);
1913 if (!file_data_filter_marks(fd, filter))
1915 list = g_list_remove_link(list, link);
1916 file_data_unref(fd);
1924 gboolean file_data_filter_file_filter(FileData *fd, GRegex *filter)
1926 return g_regex_match(filter, fd->name, static_cast<GRegexMatchFlags>(0), nullptr);
1929 GList *file_data_filter_file_filter_list(GList *list, GRegex *filter)
1936 auto fd = static_cast<FileData *>(work->data);
1940 if (!file_data_filter_file_filter(fd, filter))
1942 list = g_list_remove_link(list, link);
1943 file_data_unref(fd);
1951 static gboolean file_data_filter_class(FileData *fd, guint filter)
1955 for (i = 0; i < FILE_FORMAT_CLASSES; i++)
1957 if (filter & (1 << i))
1959 if (static_cast<FileFormatClass>(i) == filter_file_get_class(fd->path))
1969 GList *file_data_filter_class_list(GList *list, guint filter)
1976 auto fd = static_cast<FileData *>(work->data);
1980 if (!file_data_filter_class(fd, filter))
1982 list = g_list_remove_link(list, link);
1983 file_data_unref(fd);
1991 static void file_data_notify_mark_func(gpointer, gpointer value, gpointer)
1993 auto fd = static_cast<FileData *>(value);
1994 file_data_increment_version(fd);
1995 file_data_send_notification(fd, NOTIFY_MARKS);
1998 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
2000 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
2002 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
2004 file_data_get_mark_func[n] = get_mark_func;
2005 file_data_set_mark_func[n] = set_mark_func;
2006 file_data_mark_func_data[n] = data;
2007 file_data_destroy_mark_func[n] = notify;
2009 if (get_mark_func && file_data_pool)
2011 /* this effectively changes all known files */
2012 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, nullptr);
2018 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
2020 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
2021 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
2022 if (data) *data = file_data_mark_func_data[n];
2025 #pragma GCC diagnostic push
2026 #pragma GCC diagnostic ignored "-Wunused-function"
2027 gint file_data_get_user_orientation_unused(FileData *fd)
2029 return fd->user_orientation;
2032 void file_data_set_user_orientation_unused(FileData *fd, gint value)
2034 if (fd->user_orientation == value) return;
2036 fd->user_orientation = value;
2037 file_data_increment_version(fd);
2038 file_data_send_notification(fd, NOTIFY_ORIENTATION);
2040 #pragma GCC diagnostic pop
2044 * file_data - operates on the given fd
2045 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
2049 /* return list of sidecar file extensions in a string */
2050 gchar *file_data_sc_list_to_string(FileData *fd)
2053 GString *result = g_string_new("");
2055 work = fd->sidecar_files;
2058 auto sfd = static_cast<FileData *>(work->data);
2060 result = g_string_append(result, "+ ");
2061 result = g_string_append(result, sfd->extension);
2063 if (work) result = g_string_append_c(result, ' ');
2066 return g_string_free(result, FALSE);
2072 * add FileDataChangeInfo (see typedefs.h) for the given operation
2073 * uses file_data_add_change_info
2075 * fails if the fd->change already exists - change operations can't run in parallel
2076 * fd->change_info works as a lock
2078 * dest can be NULL - in this case the current name is used for now, it will
2083 FileDataChangeInfo types:
2085 MOVE - path is changed, name may be changed too
2086 RENAME - path remains unchanged, name is changed
2087 extension should remain (FIXME should we allow editing extension? it will make problems with grouping)
2088 sidecar names are changed too, extensions are not changed
2090 UPDATE - file size, date or grouping has been changed
2093 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
2095 FileDataChangeInfo *fdci;
2097 if (fd->change) return FALSE;
2099 fdci = g_new0(FileDataChangeInfo, 1);
2104 fdci->source = g_strdup(src);
2106 fdci->source = g_strdup(fd->path);
2109 fdci->dest = g_strdup(dest);
2116 static void file_data_planned_change_remove(FileData *fd)
2118 if (file_data_planned_change_hash &&
2119 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
2121 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
2123 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
2124 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
2125 file_data_unref(fd);
2126 if (g_hash_table_size(file_data_planned_change_hash) == 0)
2128 g_hash_table_destroy(file_data_planned_change_hash);
2129 file_data_planned_change_hash = nullptr;
2130 DEBUG_1("planned change: empty");
2137 void file_data_free_ci(FileData *fd)
2139 FileDataChangeInfo *fdci = fd->change;
2143 file_data_planned_change_remove(fd);
2145 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
2147 g_free(fdci->source);
2152 fd->change = nullptr;
2155 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
2157 FileDataChangeInfo *fdci = fd->change;
2159 fdci->regroup_when_finished = enable;
2162 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
2166 if (fd->parent) fd = fd->parent;
2168 if (fd->change) return FALSE;
2170 work = fd->sidecar_files;
2173 auto sfd = static_cast<FileData *>(work->data);
2175 if (sfd->change) return FALSE;
2179 file_data_add_ci(fd, type, nullptr, nullptr);
2181 work = fd->sidecar_files;
2184 auto sfd = static_cast<FileData *>(work->data);
2186 file_data_add_ci(sfd, type, nullptr, nullptr);
2193 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
2197 if (fd->parent) fd = fd->parent;
2199 if (!fd->change || fd->change->type != type) return FALSE;
2201 work = fd->sidecar_files;
2204 auto sfd = static_cast<FileData *>(work->data);
2206 if (!sfd->change || sfd->change->type != type) return FALSE;
2214 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
2216 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
2217 file_data_sc_update_ci_copy(fd, dest_path);
2221 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
2223 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
2224 file_data_sc_update_ci_move(fd, dest_path);
2228 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
2230 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
2231 file_data_sc_update_ci_rename(fd, dest_path);
2235 gboolean file_data_sc_add_ci_delete(FileData *fd)
2237 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
2240 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
2242 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
2243 file_data_sc_update_ci_unspecified(fd, dest_path);
2247 gboolean file_data_add_ci_write_metadata(FileData *fd)
2249 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, nullptr, nullptr);
2252 void file_data_sc_free_ci(FileData *fd)
2256 if (fd->parent) fd = fd->parent;
2258 file_data_free_ci(fd);
2260 work = fd->sidecar_files;
2263 auto sfd = static_cast<FileData *>(work->data);
2265 file_data_free_ci(sfd);
2270 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2273 gboolean ret = TRUE;
2278 auto fd = static_cast<FileData *>(work->data);
2280 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2287 static void file_data_sc_revert_ci_list(GList *fd_list)
2294 auto fd = static_cast<FileData *>(work->data);
2296 file_data_sc_free_ci(fd);
2301 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2308 auto fd = static_cast<FileData *>(work->data);
2310 if (!func(fd, dest))
2312 file_data_sc_revert_ci_list(work->prev);
2321 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2323 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2326 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2328 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2331 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2333 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2336 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2338 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2341 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2344 gboolean ret = TRUE;
2349 auto fd = static_cast<FileData *>(work->data);
2351 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2358 void file_data_free_ci_list(GList *fd_list)
2365 auto fd = static_cast<FileData *>(work->data);
2367 file_data_free_ci(fd);
2372 void file_data_sc_free_ci_list(GList *fd_list)
2379 auto fd = static_cast<FileData *>(work->data);
2381 file_data_sc_free_ci(fd);
2387 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2388 * fails if fd->change does not exist or the change type does not match
2391 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2393 FileDataChangeType type = fd->change->type;
2395 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2399 if (!file_data_planned_change_hash)
2400 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2402 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2404 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2405 g_hash_table_remove(file_data_planned_change_hash, old_path);
2406 file_data_unref(fd);
2409 ofd = static_cast<FileData *>(g_hash_table_lookup(file_data_planned_change_hash, new_path));
2414 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2415 g_hash_table_remove(file_data_planned_change_hash, new_path);
2416 file_data_unref(ofd);
2419 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2421 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2426 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2428 gchar *old_path = fd->change->dest;
2430 fd->change->dest = g_strdup(dest_path);
2431 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2435 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2437 const gchar *extension = registered_extension_from_path(fd->change->source);
2438 gchar *base = remove_extension_from_path(dest_path);
2439 gchar *old_path = fd->change->dest;
2441 fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2442 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2448 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2451 gchar *dest_path_full = nullptr;
2453 if (fd->parent) fd = fd->parent;
2457 dest_path = fd->path;
2459 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2461 gchar *dir = remove_level_from_path(fd->path);
2463 dest_path_full = g_build_filename(dir, dest_path, NULL);
2465 dest_path = dest_path_full;
2467 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2469 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2470 dest_path = dest_path_full;
2473 file_data_update_ci_dest(fd, dest_path);
2475 work = fd->sidecar_files;
2478 auto sfd = static_cast<FileData *>(work->data);
2480 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2484 g_free(dest_path_full);
2487 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2489 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2490 file_data_sc_update_ci(fd, dest_path);
2494 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2496 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2499 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2501 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2504 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2506 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2509 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2511 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2514 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2516 gboolean (*func)(FileData *, const gchar *))
2519 gboolean ret = TRUE;
2524 auto fd = static_cast<FileData *>(work->data);
2526 if (!func(fd, dest)) ret = FALSE;
2533 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2535 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2538 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2540 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2543 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2545 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2550 * verify source and dest paths - dest image exists, etc.
2551 * it should detect all possible problems with the planned operation
2554 gint file_data_verify_ci(FileData *fd, GList *list)
2556 gint ret = CHANGE_OK;
2558 GList *work = nullptr;
2559 FileData *fd1 = nullptr;
2563 DEBUG_1("Change checked: no change info: %s", fd->path);
2567 if (!isname(fd->path))
2569 /* this probably should not happen */
2570 ret |= CHANGE_NO_SRC;
2571 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2575 dir = remove_level_from_path(fd->path);
2577 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2578 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2579 fd->change->type != FILEDATA_CHANGE_RENAME &&
2580 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2583 ret |= CHANGE_WARN_UNSAVED_META;
2584 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2587 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2588 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2589 !access_file(fd->path, R_OK))
2591 ret |= CHANGE_NO_READ_PERM;
2592 DEBUG_1("Change checked: no read permission: %s", fd->path);
2594 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2595 !access_file(dir, W_OK))
2597 ret |= CHANGE_NO_WRITE_PERM_DIR;
2598 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2600 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2601 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2602 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2603 !access_file(fd->path, W_OK))
2605 ret |= CHANGE_WARN_NO_WRITE_PERM;
2606 DEBUG_1("Change checked: no write permission: %s", fd->path);
2608 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2609 - that means that there are no hard errors and warnings can be disabled
2610 - the destination is determined during the check
2612 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2614 /* determine destination file */
2615 gboolean have_dest = FALSE;
2616 gchar *dest_dir = nullptr;
2618 if (options->metadata.save_in_image_file)
2620 if (file_data_can_write_directly(fd))
2622 /* we can write the file directly */
2623 if (access_file(fd->path, W_OK))
2629 if (options->metadata.warn_on_write_problems)
2631 ret |= CHANGE_WARN_NO_WRITE_PERM;
2632 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2636 else if (file_data_can_write_sidecar(fd))
2638 /* we can write sidecar */
2639 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2640 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2642 file_data_update_ci_dest(fd, sidecar);
2647 if (options->metadata.warn_on_write_problems)
2649 ret |= CHANGE_WARN_NO_WRITE_PERM;
2650 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2659 /* write private metadata file under ~/.geeqie */
2661 /* If an existing metadata file exists, we will try writing to
2662 * it's location regardless of the user's preference.
2664 gchar *metadata_path = nullptr;
2666 /* but ignore XMP if we are not able to write it */
2667 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2669 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2671 if (metadata_path && !access_file(metadata_path, W_OK))
2673 g_free(metadata_path);
2674 metadata_path = nullptr;
2681 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2682 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2684 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2686 metadata_path = g_build_filename(dest_dir, filename, NULL);
2690 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2692 file_data_update_ci_dest(fd, metadata_path);
2696 ret |= CHANGE_NO_WRITE_PERM_DEST;
2697 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2699 g_free(metadata_path);
2704 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2709 same = (strcmp(fd->path, fd->change->dest) == 0);
2713 const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2714 if (!dest_ext) dest_ext = "";
2715 if (!options->file_filter.disable_file_extension_checks)
2717 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2719 ret |= CHANGE_WARN_CHANGED_EXT;
2720 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2726 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /** @FIXME this is now needed for running editors */
2728 ret |= CHANGE_WARN_SAME;
2729 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2733 dest_dir = remove_level_from_path(fd->change->dest);
2735 if (!isdir(dest_dir))
2737 ret |= CHANGE_NO_DEST_DIR;
2738 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2740 else if (!access_file(dest_dir, W_OK))
2742 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2743 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2747 if (isfile(fd->change->dest))
2749 if (!access_file(fd->change->dest, W_OK))
2751 ret |= CHANGE_NO_WRITE_PERM_DEST;
2752 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2756 ret |= CHANGE_WARN_DEST_EXISTS;
2757 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2760 else if (isdir(fd->change->dest))
2762 ret |= CHANGE_DEST_EXISTS;
2763 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2770 /* During a rename operation, check if another planned destination file has
2773 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2774 fd->change->type == FILEDATA_CHANGE_COPY ||
2775 fd->change->type == FILEDATA_CHANGE_MOVE)
2780 fd1 = static_cast<FileData *>(work->data);
2782 if (fd1 != nullptr && fd != fd1 )
2784 if (!strcmp(fd->change->dest, fd1->change->dest))
2786 ret |= CHANGE_DUPLICATE_DEST;
2792 fd->change->error = ret;
2793 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2800 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2805 ret = file_data_verify_ci(fd, list);
2807 work = fd->sidecar_files;
2810 auto sfd = static_cast<FileData *>(work->data);
2812 ret |= file_data_verify_ci(sfd, list);
2819 gchar *file_data_get_error_string(gint error)
2821 GString *result = g_string_new("");
2823 if (error & CHANGE_NO_SRC)
2825 if (result->len > 0) g_string_append(result, ", ");
2826 g_string_append(result, _("file or directory does not exist"));
2829 if (error & CHANGE_DEST_EXISTS)
2831 if (result->len > 0) g_string_append(result, ", ");
2832 g_string_append(result, _("destination already exists"));
2835 if (error & CHANGE_NO_WRITE_PERM_DEST)
2837 if (result->len > 0) g_string_append(result, ", ");
2838 g_string_append(result, _("destination can't be overwritten"));
2841 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2843 if (result->len > 0) g_string_append(result, ", ");
2844 g_string_append(result, _("destination directory is not writable"));
2847 if (error & CHANGE_NO_DEST_DIR)
2849 if (result->len > 0) g_string_append(result, ", ");
2850 g_string_append(result, _("destination directory does not exist"));
2853 if (error & CHANGE_NO_WRITE_PERM_DIR)
2855 if (result->len > 0) g_string_append(result, ", ");
2856 g_string_append(result, _("source directory is not writable"));
2859 if (error & CHANGE_NO_READ_PERM)
2861 if (result->len > 0) g_string_append(result, ", ");
2862 g_string_append(result, _("no read permission"));
2865 if (error & CHANGE_WARN_NO_WRITE_PERM)
2867 if (result->len > 0) g_string_append(result, ", ");
2868 g_string_append(result, _("file is readonly"));
2871 if (error & CHANGE_WARN_DEST_EXISTS)
2873 if (result->len > 0) g_string_append(result, ", ");
2874 g_string_append(result, _("destination already exists and will be overwritten"));
2877 if (error & CHANGE_WARN_SAME)
2879 if (result->len > 0) g_string_append(result, ", ");
2880 g_string_append(result, _("source and destination are the same"));
2883 if (error & CHANGE_WARN_CHANGED_EXT)
2885 if (result->len > 0) g_string_append(result, ", ");
2886 g_string_append(result, _("source and destination have different extension"));
2889 if (error & CHANGE_WARN_UNSAVED_META)
2891 if (result->len > 0) g_string_append(result, ", ");
2892 g_string_append(result, _("there are unsaved metadata changes for the file"));
2895 if (error & CHANGE_DUPLICATE_DEST)
2897 if (result->len > 0) g_string_append(result, ", ");
2898 g_string_append(result, _("another destination file has the same filename"));
2901 return g_string_free(result, FALSE);
2904 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2907 gint all_errors = 0;
2908 gint common_errors = ~0;
2913 if (!list) return 0;
2915 num = g_list_length(list);
2916 errors = g_new(int, num);
2924 fd = static_cast<FileData *>(work->data);
2927 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2928 all_errors |= error;
2929 common_errors &= error;
2936 if (desc && all_errors)
2939 GString *result = g_string_new("");
2943 gchar *str = file_data_get_error_string(common_errors);
2944 g_string_append(result, str);
2945 g_string_append(result, "\n");
2956 fd = static_cast<FileData *>(work->data);
2959 error = errors[i] & ~common_errors;
2963 gchar *str = file_data_get_error_string(error);
2964 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2969 *desc = g_string_free(result, FALSE);
2978 * perform the change described by FileFataChangeInfo
2979 * it is used for internal operations,
2980 * this function actually operates with files on the filesystem
2981 * it should implement safe delete
2984 static gboolean file_data_perform_move(FileData *fd)
2986 g_assert(!strcmp(fd->change->source, fd->path));
2987 return move_file(fd->change->source, fd->change->dest);
2990 static gboolean file_data_perform_copy(FileData *fd)
2992 g_assert(!strcmp(fd->change->source, fd->path));
2993 return copy_file(fd->change->source, fd->change->dest);
2996 static gboolean file_data_perform_delete(FileData *fd)
2998 if (isdir(fd->path) && !islink(fd->path))
2999 return rmdir_utf8(fd->path);
3001 if (options->file_ops.safe_delete_enable)
3002 return file_util_safe_unlink(fd->path);
3004 return unlink_file(fd->path);
3007 gboolean file_data_perform_ci(FileData *fd)
3009 /** @FIXME When a directory that is a symbolic link is deleted,
3010 * at this point fd->change is null because no FileDataChangeInfo
3011 * has been set up. Therefore there is a seg. fault.
3012 * This code simply aborts the delete.
3019 FileDataChangeType type = fd->change->type;
3023 case FILEDATA_CHANGE_MOVE:
3024 return file_data_perform_move(fd);
3025 case FILEDATA_CHANGE_COPY:
3026 return file_data_perform_copy(fd);
3027 case FILEDATA_CHANGE_RENAME:
3028 return file_data_perform_move(fd); /* the same as move */
3029 case FILEDATA_CHANGE_DELETE:
3030 return file_data_perform_delete(fd);
3031 case FILEDATA_CHANGE_WRITE_METADATA:
3032 return metadata_write_perform(fd);
3033 case FILEDATA_CHANGE_UNSPECIFIED:
3034 /* nothing to do here */
3042 gboolean file_data_sc_perform_ci(FileData *fd)
3045 gboolean ret = TRUE;
3046 FileDataChangeType type = fd->change->type;
3048 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3050 work = fd->sidecar_files;
3053 auto sfd = static_cast<FileData *>(work->data);
3055 if (!file_data_perform_ci(sfd)) ret = FALSE;
3059 if (!file_data_perform_ci(fd)) ret = FALSE;
3065 * updates FileData structure according to FileDataChangeInfo
3068 gboolean file_data_apply_ci(FileData *fd)
3070 FileDataChangeType type = fd->change->type;
3072 /** @FIXME delete ?*/
3073 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
3075 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
3076 file_data_planned_change_remove(fd);
3078 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
3080 /* this change overwrites another file which is already known to other modules
3081 renaming fd would create duplicate FileData structure
3082 the best thing we can do is nothing
3084 /** @FIXME maybe we could copy stuff like marks
3086 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
3090 file_data_set_path(fd, fd->change->dest);
3093 file_data_increment_version(fd);
3094 file_data_send_notification(fd, NOTIFY_CHANGE);
3099 gboolean file_data_sc_apply_ci(FileData *fd)
3102 FileDataChangeType type = fd->change->type;
3104 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3106 work = fd->sidecar_files;
3109 auto sfd = static_cast<FileData *>(work->data);
3111 file_data_apply_ci(sfd);
3115 file_data_apply_ci(fd);
3120 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
3123 if (fd->parent) fd = fd->parent;
3124 if (!g_list_find(list, fd)) return FALSE;
3126 work = fd->sidecar_files;
3129 if (!g_list_find(list, work->data)) return FALSE;
3135 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
3137 GList *out = nullptr;
3140 /* change partial groups to independent files */
3145 auto fd = static_cast<FileData *>(work->data);
3148 if (!file_data_list_contains_whole_group(list, fd))
3150 file_data_disable_grouping(fd, TRUE);
3153 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
3159 /* remove sidecars from the list,
3160 they can be still accessed via main_fd->sidecar_files */
3164 auto fd = static_cast<FileData *>(work->data);
3168 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
3170 out = g_list_prepend(out, file_data_ref(fd));
3174 filelist_free(list);
3175 out = g_list_reverse(out);
3185 * notify other modules about the change described by FileDataChangeInfo
3188 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks */
3189 /** @FIXME do we need the ignore_list? It looks like a workaround for ineffective
3190 implementation in view-file-list.cc */
3193 struct NotifyIdleData {
3200 FileDataNotifyFunc func;
3202 NotifyPriority priority;
3205 static GList *notify_func_list = nullptr;
3207 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
3209 auto nda = static_cast<const NotifyData *>(a);
3210 auto ndb = static_cast<const NotifyData *>(b);
3212 if (nda->priority < ndb->priority) return -1;
3213 if (nda->priority > ndb->priority) return 1;
3217 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
3220 GList *work = notify_func_list;
3224 auto nd = static_cast<NotifyData *>(work->data);
3226 if (nd->func == func && nd->data == data)
3228 g_warning("Notify func already registered");
3234 nd = g_new(NotifyData, 1);
3237 nd->priority = priority;
3239 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
3240 DEBUG_2("Notify func registered: %p", (void *)nd);
3245 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
3247 GList *work = notify_func_list;
3251 auto nd = static_cast<NotifyData *>(work->data);
3253 if (nd->func == func && nd->data == data)
3255 notify_func_list = g_list_delete_link(notify_func_list, work);
3256 DEBUG_2("Notify func unregistered: %p", (void *)nd);
3263 g_warning("Notify func not found");
3267 #pragma GCC diagnostic push
3268 #pragma GCC diagnostic ignored "-Wunused-function"
3269 gboolean file_data_send_notification_idle_cb_unused(gpointer data)
3271 auto *nid = (NotifyIdleData *)data;
3272 GList *work = notify_func_list;
3276 auto *nd = (NotifyData *)work->data;
3278 nd->func(nid->fd, nid->type, nd->data);
3281 file_data_unref(nid->fd);
3285 #pragma GCC diagnostic pop
3287 void file_data_send_notification(FileData *fd, NotifyType type)
3289 GList *work = notify_func_list;
3293 auto nd = static_cast<NotifyData *>(work->data);
3295 nd->func(fd, type, nd->data);
3299 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3300 nid->fd = file_data_ref(fd);
3302 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3306 static GHashTable *file_data_monitor_pool = nullptr;
3307 static guint realtime_monitor_id = 0; /* event source id */
3309 static void realtime_monitor_check_cb(gpointer key, gpointer, gpointer)
3311 auto fd = static_cast<FileData *>(key);
3313 file_data_check_changed_files(fd);
3315 DEBUG_1("monitor %s", fd->path);
3318 static gboolean realtime_monitor_cb(gpointer)
3320 if (!options->update_on_time_change) return TRUE;
3321 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, nullptr);
3325 gboolean file_data_register_real_time_monitor(FileData *fd)
3331 if (!file_data_monitor_pool)
3332 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3334 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3336 DEBUG_1("Register realtime %d %s", count, fd->path);
3339 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3341 if (!realtime_monitor_id)
3343 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, nullptr);
3349 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3353 g_assert(file_data_monitor_pool);
3355 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3357 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3359 g_assert(count > 0);
3364 g_hash_table_remove(file_data_monitor_pool, fd);
3366 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3368 file_data_unref(fd);
3370 if (g_hash_table_size(file_data_monitor_pool) == 0)
3372 g_source_remove(realtime_monitor_id);
3373 realtime_monitor_id = 0;
3381 *-----------------------------------------------------------------------------
3382 * Saving marks list, clearing marks
3383 * Uses file_data_pool
3384 *-----------------------------------------------------------------------------
3387 static void marks_get_files(gpointer key, gpointer value, gpointer userdata)
3389 auto file_name = static_cast<gchar *>(key);
3390 auto result = static_cast<GString *>(userdata);
3393 if (isfile(file_name))
3395 fd = static_cast<FileData *>(value);
3396 if (fd && fd->marks > 0)
3398 g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks);
3403 gboolean marks_list_load(const gchar *path)
3411 pathl = path_from_utf8(path);
3412 f = fopen(pathl, "r");
3414 if (!f) return FALSE;
3416 /* first line must start with Marks comment */
3417 if (!fgets(s_buf, sizeof(s_buf), f) ||
3418 strncmp(s_buf, "#Marks", 6) != 0)
3424 while (fgets(s_buf, sizeof(s_buf), f))
3426 if (s_buf[0]=='#') continue;
3427 file_path = strtok(s_buf, ",");
3428 marks_value = strtok(nullptr, ",");
3429 if (isfile(file_path))
3431 FileData *fd = file_data_new_no_grouping(file_path);
3436 gint mark_no = 1 << n;
3437 if (atoi(marks_value) & mark_no)
3439 file_data_set_mark(fd, n , 1);
3450 gboolean marks_list_save(gchar *path, gboolean save)
3452 SecureSaveInfo *ssi;
3455 pathl = path_from_utf8(path);
3456 ssi = secure_open(pathl);
3460 log_printf(_("Error: Unable to write marks lists to: %s\n"), path);
3464 secure_fprintf(ssi, "#Marks lists\n");
3466 GString *marks = g_string_new("");
3469 g_hash_table_foreach(file_data_pool, marks_get_files, marks);
3471 secure_fprintf(ssi, "%s", marks->str);
3472 g_string_free(marks, TRUE);
3474 secure_fprintf(ssi, "#end\n");
3475 return (secure_close(ssi) == 0);
3478 static void marks_clear(gpointer key, gpointer value, gpointer)
3480 auto file_name = static_cast<gchar *>(key);
3485 if (isfile(file_name))
3487 fd = static_cast<FileData *>(value);
3488 if (fd && fd->marks > 0)
3494 if (fd->marks & mark_no)
3496 file_data_set_mark(fd, n , 0);
3504 void marks_clear_all()
3506 g_hash_table_foreach(file_data_pool, marks_clear, nullptr);
3509 void file_data_set_page_num(FileData *fd, gint page_num)
3511 if (fd->page_total > 1 && page_num < 0)
3513 fd->page_num = fd->page_total - 1;
3515 else if (fd->page_total > 1 && page_num <= fd->page_total)
3517 fd->page_num = page_num - 1;
3523 file_data_send_notification(fd, NOTIFY_REREAD);
3526 void file_data_inc_page_num(FileData *fd)
3528 if (fd->page_total > 0 && fd->page_num < fd->page_total - 1)
3530 fd->page_num = fd->page_num + 1;
3532 else if (fd->page_total == 0)
3534 fd->page_num = fd->page_num + 1;
3536 file_data_send_notification(fd, NOTIFY_REREAD);
3539 void file_data_dec_page_num(FileData *fd)
3541 if (fd->page_num > 0)
3543 fd->page_num = fd->page_num - 1;
3545 file_data_send_notification(fd, NOTIFY_REREAD);
3548 void file_data_set_page_total(FileData *fd, gint page_total)
3550 fd->page_total = page_total;
3553 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */