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_mark_to_selection(FileData *fd, gint mark, MarkToSelectionMode mode, gboolean selected)
1927 gboolean mark_val = file_data_get_mark(fd, n);
1931 case MTS_MODE_MINUS: return !mark_val && selected;
1932 case MTS_MODE_SET: return mark_val;
1933 case MTS_MODE_OR: return mark_val || selected;
1934 case MTS_MODE_AND: return mark_val && selected;
1937 return selected; // arbitrary value, we shouldn't get here
1940 void file_data_selection_to_mark(FileData *fd, gint mark, SelectionToMarkMode mode)
1946 case STM_MODE_RESET: file_data_set_mark(fd, n, FALSE); break;
1947 case STM_MODE_SET: file_data_set_mark(fd, n, TRUE); break;
1948 case STM_MODE_TOGGLE: file_data_set_mark(fd, n, !file_data_get_mark(fd, n)); break;
1952 gboolean file_data_filter_file_filter(FileData *fd, GRegex *filter)
1954 return g_regex_match(filter, fd->name, static_cast<GRegexMatchFlags>(0), nullptr);
1957 GList *file_data_filter_file_filter_list(GList *list, GRegex *filter)
1964 auto fd = static_cast<FileData *>(work->data);
1968 if (!file_data_filter_file_filter(fd, filter))
1970 list = g_list_remove_link(list, link);
1971 file_data_unref(fd);
1979 static gboolean file_data_filter_class(FileData *fd, guint filter)
1983 for (i = 0; i < FILE_FORMAT_CLASSES; i++)
1985 if (filter & (1 << i))
1987 if (static_cast<FileFormatClass>(i) == filter_file_get_class(fd->path))
1997 GList *file_data_filter_class_list(GList *list, guint filter)
2004 auto fd = static_cast<FileData *>(work->data);
2008 if (!file_data_filter_class(fd, filter))
2010 list = g_list_remove_link(list, link);
2011 file_data_unref(fd);
2019 static void file_data_notify_mark_func(gpointer, gpointer value, gpointer)
2021 auto fd = static_cast<FileData *>(value);
2022 file_data_increment_version(fd);
2023 file_data_send_notification(fd, NOTIFY_MARKS);
2026 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
2028 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
2030 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
2032 file_data_get_mark_func[n] = get_mark_func;
2033 file_data_set_mark_func[n] = set_mark_func;
2034 file_data_mark_func_data[n] = data;
2035 file_data_destroy_mark_func[n] = notify;
2037 if (get_mark_func && file_data_pool)
2039 /* this effectively changes all known files */
2040 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, nullptr);
2046 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
2048 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
2049 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
2050 if (data) *data = file_data_mark_func_data[n];
2053 #pragma GCC diagnostic push
2054 #pragma GCC diagnostic ignored "-Wunused-function"
2055 gint file_data_get_user_orientation_unused(FileData *fd)
2057 return fd->user_orientation;
2060 void file_data_set_user_orientation_unused(FileData *fd, gint value)
2062 if (fd->user_orientation == value) return;
2064 fd->user_orientation = value;
2065 file_data_increment_version(fd);
2066 file_data_send_notification(fd, NOTIFY_ORIENTATION);
2068 #pragma GCC diagnostic pop
2072 * file_data - operates on the given fd
2073 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
2077 /* return list of sidecar file extensions in a string */
2078 gchar *file_data_sc_list_to_string(FileData *fd)
2081 GString *result = g_string_new("");
2083 work = fd->sidecar_files;
2086 auto sfd = static_cast<FileData *>(work->data);
2088 result = g_string_append(result, "+ ");
2089 result = g_string_append(result, sfd->extension);
2091 if (work) result = g_string_append_c(result, ' ');
2094 return g_string_free(result, FALSE);
2100 * add FileDataChangeInfo (see typedefs.h) for the given operation
2101 * uses file_data_add_change_info
2103 * fails if the fd->change already exists - change operations can't run in parallel
2104 * fd->change_info works as a lock
2106 * dest can be NULL - in this case the current name is used for now, it will
2111 FileDataChangeInfo types:
2113 MOVE - path is changed, name may be changed too
2114 RENAME - path remains unchanged, name is changed
2115 extension should remain (FIXME should we allow editing extension? it will make problems with grouping)
2116 sidecar names are changed too, extensions are not changed
2118 UPDATE - file size, date or grouping has been changed
2121 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
2123 FileDataChangeInfo *fdci;
2125 if (fd->change) return FALSE;
2127 fdci = g_new0(FileDataChangeInfo, 1);
2132 fdci->source = g_strdup(src);
2134 fdci->source = g_strdup(fd->path);
2137 fdci->dest = g_strdup(dest);
2144 static void file_data_planned_change_remove(FileData *fd)
2146 if (file_data_planned_change_hash &&
2147 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
2149 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
2151 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
2152 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
2153 file_data_unref(fd);
2154 if (g_hash_table_size(file_data_planned_change_hash) == 0)
2156 g_hash_table_destroy(file_data_planned_change_hash);
2157 file_data_planned_change_hash = nullptr;
2158 DEBUG_1("planned change: empty");
2165 void file_data_free_ci(FileData *fd)
2167 FileDataChangeInfo *fdci = fd->change;
2171 file_data_planned_change_remove(fd);
2173 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
2175 g_free(fdci->source);
2180 fd->change = nullptr;
2183 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
2185 FileDataChangeInfo *fdci = fd->change;
2187 fdci->regroup_when_finished = enable;
2190 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
2194 if (fd->parent) fd = fd->parent;
2196 if (fd->change) return FALSE;
2198 work = fd->sidecar_files;
2201 auto sfd = static_cast<FileData *>(work->data);
2203 if (sfd->change) return FALSE;
2207 file_data_add_ci(fd, type, nullptr, nullptr);
2209 work = fd->sidecar_files;
2212 auto sfd = static_cast<FileData *>(work->data);
2214 file_data_add_ci(sfd, type, nullptr, nullptr);
2221 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
2225 if (fd->parent) fd = fd->parent;
2227 if (!fd->change || fd->change->type != type) return FALSE;
2229 work = fd->sidecar_files;
2232 auto sfd = static_cast<FileData *>(work->data);
2234 if (!sfd->change || sfd->change->type != type) return FALSE;
2242 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
2244 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
2245 file_data_sc_update_ci_copy(fd, dest_path);
2249 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
2251 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
2252 file_data_sc_update_ci_move(fd, dest_path);
2256 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
2258 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
2259 file_data_sc_update_ci_rename(fd, dest_path);
2263 gboolean file_data_sc_add_ci_delete(FileData *fd)
2265 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
2268 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
2270 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
2271 file_data_sc_update_ci_unspecified(fd, dest_path);
2275 gboolean file_data_add_ci_write_metadata(FileData *fd)
2277 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, nullptr, nullptr);
2280 void file_data_sc_free_ci(FileData *fd)
2284 if (fd->parent) fd = fd->parent;
2286 file_data_free_ci(fd);
2288 work = fd->sidecar_files;
2291 auto sfd = static_cast<FileData *>(work->data);
2293 file_data_free_ci(sfd);
2298 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2301 gboolean ret = TRUE;
2306 auto fd = static_cast<FileData *>(work->data);
2308 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2315 static void file_data_sc_revert_ci_list(GList *fd_list)
2322 auto fd = static_cast<FileData *>(work->data);
2324 file_data_sc_free_ci(fd);
2329 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2336 auto fd = static_cast<FileData *>(work->data);
2338 if (!func(fd, dest))
2340 file_data_sc_revert_ci_list(work->prev);
2349 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2351 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2354 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2356 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2359 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2361 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2364 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2366 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2369 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2372 gboolean ret = TRUE;
2377 auto fd = static_cast<FileData *>(work->data);
2379 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2386 void file_data_free_ci_list(GList *fd_list)
2393 auto fd = static_cast<FileData *>(work->data);
2395 file_data_free_ci(fd);
2400 void file_data_sc_free_ci_list(GList *fd_list)
2407 auto fd = static_cast<FileData *>(work->data);
2409 file_data_sc_free_ci(fd);
2415 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2416 * fails if fd->change does not exist or the change type does not match
2419 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2421 FileDataChangeType type = fd->change->type;
2423 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2427 if (!file_data_planned_change_hash)
2428 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2430 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2432 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2433 g_hash_table_remove(file_data_planned_change_hash, old_path);
2434 file_data_unref(fd);
2437 ofd = static_cast<FileData *>(g_hash_table_lookup(file_data_planned_change_hash, new_path));
2442 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2443 g_hash_table_remove(file_data_planned_change_hash, new_path);
2444 file_data_unref(ofd);
2447 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2449 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2454 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2456 gchar *old_path = fd->change->dest;
2458 fd->change->dest = g_strdup(dest_path);
2459 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2463 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2465 const gchar *extension = registered_extension_from_path(fd->change->source);
2466 gchar *base = remove_extension_from_path(dest_path);
2467 gchar *old_path = fd->change->dest;
2469 fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2470 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2476 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2479 gchar *dest_path_full = nullptr;
2481 if (fd->parent) fd = fd->parent;
2485 dest_path = fd->path;
2487 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2489 gchar *dir = remove_level_from_path(fd->path);
2491 dest_path_full = g_build_filename(dir, dest_path, NULL);
2493 dest_path = dest_path_full;
2495 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2497 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2498 dest_path = dest_path_full;
2501 file_data_update_ci_dest(fd, dest_path);
2503 work = fd->sidecar_files;
2506 auto sfd = static_cast<FileData *>(work->data);
2508 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2512 g_free(dest_path_full);
2515 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2517 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2518 file_data_sc_update_ci(fd, dest_path);
2522 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2524 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2527 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2529 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2532 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2534 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2537 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2539 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2542 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2544 gboolean (*func)(FileData *, const gchar *))
2547 gboolean ret = TRUE;
2552 auto fd = static_cast<FileData *>(work->data);
2554 if (!func(fd, dest)) ret = FALSE;
2561 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2563 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2566 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2568 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2571 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2573 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2578 * verify source and dest paths - dest image exists, etc.
2579 * it should detect all possible problems with the planned operation
2582 gint file_data_verify_ci(FileData *fd, GList *list)
2584 gint ret = CHANGE_OK;
2586 GList *work = nullptr;
2587 FileData *fd1 = nullptr;
2591 DEBUG_1("Change checked: no change info: %s", fd->path);
2595 if (!isname(fd->path))
2597 /* this probably should not happen */
2598 ret |= CHANGE_NO_SRC;
2599 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2603 dir = remove_level_from_path(fd->path);
2605 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2606 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2607 fd->change->type != FILEDATA_CHANGE_RENAME &&
2608 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2611 ret |= CHANGE_WARN_UNSAVED_META;
2612 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2615 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2616 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2617 !access_file(fd->path, R_OK))
2619 ret |= CHANGE_NO_READ_PERM;
2620 DEBUG_1("Change checked: no read permission: %s", fd->path);
2622 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2623 !access_file(dir, W_OK))
2625 ret |= CHANGE_NO_WRITE_PERM_DIR;
2626 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2628 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2629 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2630 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2631 !access_file(fd->path, W_OK))
2633 ret |= CHANGE_WARN_NO_WRITE_PERM;
2634 DEBUG_1("Change checked: no write permission: %s", fd->path);
2636 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2637 - that means that there are no hard errors and warnings can be disabled
2638 - the destination is determined during the check
2640 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2642 /* determine destination file */
2643 gboolean have_dest = FALSE;
2644 gchar *dest_dir = nullptr;
2646 if (options->metadata.save_in_image_file)
2648 if (file_data_can_write_directly(fd))
2650 /* we can write the file directly */
2651 if (access_file(fd->path, W_OK))
2657 if (options->metadata.warn_on_write_problems)
2659 ret |= CHANGE_WARN_NO_WRITE_PERM;
2660 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2664 else if (file_data_can_write_sidecar(fd))
2666 /* we can write sidecar */
2667 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2668 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2670 file_data_update_ci_dest(fd, sidecar);
2675 if (options->metadata.warn_on_write_problems)
2677 ret |= CHANGE_WARN_NO_WRITE_PERM;
2678 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2687 /* write private metadata file under ~/.geeqie */
2689 /* If an existing metadata file exists, we will try writing to
2690 * it's location regardless of the user's preference.
2692 gchar *metadata_path = nullptr;
2694 /* but ignore XMP if we are not able to write it */
2695 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2697 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2699 if (metadata_path && !access_file(metadata_path, W_OK))
2701 g_free(metadata_path);
2702 metadata_path = nullptr;
2709 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2710 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2712 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2714 metadata_path = g_build_filename(dest_dir, filename, NULL);
2718 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2720 file_data_update_ci_dest(fd, metadata_path);
2724 ret |= CHANGE_NO_WRITE_PERM_DEST;
2725 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2727 g_free(metadata_path);
2732 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2737 same = (strcmp(fd->path, fd->change->dest) == 0);
2741 const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2742 if (!dest_ext) dest_ext = "";
2743 if (!options->file_filter.disable_file_extension_checks)
2745 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2747 ret |= CHANGE_WARN_CHANGED_EXT;
2748 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2754 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /** @FIXME this is now needed for running editors */
2756 ret |= CHANGE_WARN_SAME;
2757 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2761 dest_dir = remove_level_from_path(fd->change->dest);
2763 if (!isdir(dest_dir))
2765 ret |= CHANGE_NO_DEST_DIR;
2766 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2768 else if (!access_file(dest_dir, W_OK))
2770 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2771 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2775 if (isfile(fd->change->dest))
2777 if (!access_file(fd->change->dest, W_OK))
2779 ret |= CHANGE_NO_WRITE_PERM_DEST;
2780 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2784 ret |= CHANGE_WARN_DEST_EXISTS;
2785 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2788 else if (isdir(fd->change->dest))
2790 ret |= CHANGE_DEST_EXISTS;
2791 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2798 /* During a rename operation, check if another planned destination file has
2801 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2802 fd->change->type == FILEDATA_CHANGE_COPY ||
2803 fd->change->type == FILEDATA_CHANGE_MOVE)
2808 fd1 = static_cast<FileData *>(work->data);
2810 if (fd1 != nullptr && fd != fd1 )
2812 if (!strcmp(fd->change->dest, fd1->change->dest))
2814 ret |= CHANGE_DUPLICATE_DEST;
2820 fd->change->error = ret;
2821 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2828 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2833 ret = file_data_verify_ci(fd, list);
2835 work = fd->sidecar_files;
2838 auto sfd = static_cast<FileData *>(work->data);
2840 ret |= file_data_verify_ci(sfd, list);
2847 gchar *file_data_get_error_string(gint error)
2849 GString *result = g_string_new("");
2851 if (error & CHANGE_NO_SRC)
2853 if (result->len > 0) g_string_append(result, ", ");
2854 g_string_append(result, _("file or directory does not exist"));
2857 if (error & CHANGE_DEST_EXISTS)
2859 if (result->len > 0) g_string_append(result, ", ");
2860 g_string_append(result, _("destination already exists"));
2863 if (error & CHANGE_NO_WRITE_PERM_DEST)
2865 if (result->len > 0) g_string_append(result, ", ");
2866 g_string_append(result, _("destination can't be overwritten"));
2869 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2871 if (result->len > 0) g_string_append(result, ", ");
2872 g_string_append(result, _("destination directory is not writable"));
2875 if (error & CHANGE_NO_DEST_DIR)
2877 if (result->len > 0) g_string_append(result, ", ");
2878 g_string_append(result, _("destination directory does not exist"));
2881 if (error & CHANGE_NO_WRITE_PERM_DIR)
2883 if (result->len > 0) g_string_append(result, ", ");
2884 g_string_append(result, _("source directory is not writable"));
2887 if (error & CHANGE_NO_READ_PERM)
2889 if (result->len > 0) g_string_append(result, ", ");
2890 g_string_append(result, _("no read permission"));
2893 if (error & CHANGE_WARN_NO_WRITE_PERM)
2895 if (result->len > 0) g_string_append(result, ", ");
2896 g_string_append(result, _("file is readonly"));
2899 if (error & CHANGE_WARN_DEST_EXISTS)
2901 if (result->len > 0) g_string_append(result, ", ");
2902 g_string_append(result, _("destination already exists and will be overwritten"));
2905 if (error & CHANGE_WARN_SAME)
2907 if (result->len > 0) g_string_append(result, ", ");
2908 g_string_append(result, _("source and destination are the same"));
2911 if (error & CHANGE_WARN_CHANGED_EXT)
2913 if (result->len > 0) g_string_append(result, ", ");
2914 g_string_append(result, _("source and destination have different extension"));
2917 if (error & CHANGE_WARN_UNSAVED_META)
2919 if (result->len > 0) g_string_append(result, ", ");
2920 g_string_append(result, _("there are unsaved metadata changes for the file"));
2923 if (error & CHANGE_DUPLICATE_DEST)
2925 if (result->len > 0) g_string_append(result, ", ");
2926 g_string_append(result, _("another destination file has the same filename"));
2929 return g_string_free(result, FALSE);
2932 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2935 gint all_errors = 0;
2936 gint common_errors = ~0;
2941 if (!list) return 0;
2943 num = g_list_length(list);
2944 errors = g_new(int, num);
2952 fd = static_cast<FileData *>(work->data);
2955 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2956 all_errors |= error;
2957 common_errors &= error;
2964 if (desc && all_errors)
2967 GString *result = g_string_new("");
2971 gchar *str = file_data_get_error_string(common_errors);
2972 g_string_append(result, str);
2973 g_string_append(result, "\n");
2984 fd = static_cast<FileData *>(work->data);
2987 error = errors[i] & ~common_errors;
2991 gchar *str = file_data_get_error_string(error);
2992 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2997 *desc = g_string_free(result, FALSE);
3006 * perform the change described by FileFataChangeInfo
3007 * it is used for internal operations,
3008 * this function actually operates with files on the filesystem
3009 * it should implement safe delete
3012 static gboolean file_data_perform_move(FileData *fd)
3014 g_assert(!strcmp(fd->change->source, fd->path));
3015 return move_file(fd->change->source, fd->change->dest);
3018 static gboolean file_data_perform_copy(FileData *fd)
3020 g_assert(!strcmp(fd->change->source, fd->path));
3021 return copy_file(fd->change->source, fd->change->dest);
3024 static gboolean file_data_perform_delete(FileData *fd)
3026 if (isdir(fd->path) && !islink(fd->path))
3027 return rmdir_utf8(fd->path);
3029 if (options->file_ops.safe_delete_enable)
3030 return file_util_safe_unlink(fd->path);
3032 return unlink_file(fd->path);
3035 gboolean file_data_perform_ci(FileData *fd)
3037 /** @FIXME When a directory that is a symbolic link is deleted,
3038 * at this point fd->change is null because no FileDataChangeInfo
3039 * has been set up. Therefore there is a seg. fault.
3040 * This code simply aborts the delete.
3047 FileDataChangeType type = fd->change->type;
3051 case FILEDATA_CHANGE_MOVE:
3052 return file_data_perform_move(fd);
3053 case FILEDATA_CHANGE_COPY:
3054 return file_data_perform_copy(fd);
3055 case FILEDATA_CHANGE_RENAME:
3056 return file_data_perform_move(fd); /* the same as move */
3057 case FILEDATA_CHANGE_DELETE:
3058 return file_data_perform_delete(fd);
3059 case FILEDATA_CHANGE_WRITE_METADATA:
3060 return metadata_write_perform(fd);
3061 case FILEDATA_CHANGE_UNSPECIFIED:
3062 /* nothing to do here */
3070 gboolean file_data_sc_perform_ci(FileData *fd)
3073 gboolean ret = TRUE;
3074 FileDataChangeType type = fd->change->type;
3076 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3078 work = fd->sidecar_files;
3081 auto sfd = static_cast<FileData *>(work->data);
3083 if (!file_data_perform_ci(sfd)) ret = FALSE;
3087 if (!file_data_perform_ci(fd)) ret = FALSE;
3093 * updates FileData structure according to FileDataChangeInfo
3096 gboolean file_data_apply_ci(FileData *fd)
3098 FileDataChangeType type = fd->change->type;
3100 /** @FIXME delete ?*/
3101 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
3103 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
3104 file_data_planned_change_remove(fd);
3106 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
3108 /* this change overwrites another file which is already known to other modules
3109 renaming fd would create duplicate FileData structure
3110 the best thing we can do is nothing
3112 /** @FIXME maybe we could copy stuff like marks
3114 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
3118 file_data_set_path(fd, fd->change->dest);
3121 file_data_increment_version(fd);
3122 file_data_send_notification(fd, NOTIFY_CHANGE);
3127 gboolean file_data_sc_apply_ci(FileData *fd)
3130 FileDataChangeType type = fd->change->type;
3132 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3134 work = fd->sidecar_files;
3137 auto sfd = static_cast<FileData *>(work->data);
3139 file_data_apply_ci(sfd);
3143 file_data_apply_ci(fd);
3148 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
3151 if (fd->parent) fd = fd->parent;
3152 if (!g_list_find(list, fd)) return FALSE;
3154 work = fd->sidecar_files;
3157 if (!g_list_find(list, work->data)) return FALSE;
3163 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
3165 GList *out = nullptr;
3168 /* change partial groups to independent files */
3173 auto fd = static_cast<FileData *>(work->data);
3176 if (!file_data_list_contains_whole_group(list, fd))
3178 file_data_disable_grouping(fd, TRUE);
3181 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
3187 /* remove sidecars from the list,
3188 they can be still accessed via main_fd->sidecar_files */
3192 auto fd = static_cast<FileData *>(work->data);
3196 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
3198 out = g_list_prepend(out, file_data_ref(fd));
3202 filelist_free(list);
3203 out = g_list_reverse(out);
3213 * notify other modules about the change described by FileDataChangeInfo
3216 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks */
3217 /** @FIXME do we need the ignore_list? It looks like a workaround for ineffective
3218 implementation in view-file-list.cc */
3221 struct NotifyIdleData {
3228 FileDataNotifyFunc func;
3230 NotifyPriority priority;
3233 static GList *notify_func_list = nullptr;
3235 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
3237 auto nda = static_cast<const NotifyData *>(a);
3238 auto ndb = static_cast<const NotifyData *>(b);
3240 if (nda->priority < ndb->priority) return -1;
3241 if (nda->priority > ndb->priority) return 1;
3245 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
3248 GList *work = notify_func_list;
3252 auto nd = static_cast<NotifyData *>(work->data);
3254 if (nd->func == func && nd->data == data)
3256 g_warning("Notify func already registered");
3262 nd = g_new(NotifyData, 1);
3265 nd->priority = priority;
3267 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
3268 DEBUG_2("Notify func registered: %p", (void *)nd);
3273 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
3275 GList *work = notify_func_list;
3279 auto nd = static_cast<NotifyData *>(work->data);
3281 if (nd->func == func && nd->data == data)
3283 notify_func_list = g_list_delete_link(notify_func_list, work);
3284 DEBUG_2("Notify func unregistered: %p", (void *)nd);
3291 g_warning("Notify func not found");
3295 #pragma GCC diagnostic push
3296 #pragma GCC diagnostic ignored "-Wunused-function"
3297 gboolean file_data_send_notification_idle_cb_unused(gpointer data)
3299 auto *nid = (NotifyIdleData *)data;
3300 GList *work = notify_func_list;
3304 auto *nd = (NotifyData *)work->data;
3306 nd->func(nid->fd, nid->type, nd->data);
3309 file_data_unref(nid->fd);
3313 #pragma GCC diagnostic pop
3315 void file_data_send_notification(FileData *fd, NotifyType type)
3317 GList *work = notify_func_list;
3321 auto nd = static_cast<NotifyData *>(work->data);
3323 nd->func(fd, type, nd->data);
3327 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3328 nid->fd = file_data_ref(fd);
3330 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3334 static GHashTable *file_data_monitor_pool = nullptr;
3335 static guint realtime_monitor_id = 0; /* event source id */
3337 static void realtime_monitor_check_cb(gpointer key, gpointer, gpointer)
3339 auto fd = static_cast<FileData *>(key);
3341 file_data_check_changed_files(fd);
3343 DEBUG_1("monitor %s", fd->path);
3346 static gboolean realtime_monitor_cb(gpointer)
3348 if (!options->update_on_time_change) return TRUE;
3349 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, nullptr);
3353 gboolean file_data_register_real_time_monitor(FileData *fd)
3359 if (!file_data_monitor_pool)
3360 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3362 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3364 DEBUG_1("Register realtime %d %s", count, fd->path);
3367 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3369 if (!realtime_monitor_id)
3371 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, nullptr);
3377 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3381 g_assert(file_data_monitor_pool);
3383 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3385 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3387 g_assert(count > 0);
3392 g_hash_table_remove(file_data_monitor_pool, fd);
3394 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3396 file_data_unref(fd);
3398 if (g_hash_table_size(file_data_monitor_pool) == 0)
3400 g_source_remove(realtime_monitor_id);
3401 realtime_monitor_id = 0;
3409 *-----------------------------------------------------------------------------
3410 * Saving marks list, clearing marks
3411 * Uses file_data_pool
3412 *-----------------------------------------------------------------------------
3415 static void marks_get_files(gpointer key, gpointer value, gpointer userdata)
3417 auto file_name = static_cast<gchar *>(key);
3418 auto result = static_cast<GString *>(userdata);
3421 if (isfile(file_name))
3423 fd = static_cast<FileData *>(value);
3424 if (fd && fd->marks > 0)
3426 g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks);
3431 gboolean marks_list_load(const gchar *path)
3439 pathl = path_from_utf8(path);
3440 f = fopen(pathl, "r");
3442 if (!f) return FALSE;
3444 /* first line must start with Marks comment */
3445 if (!fgets(s_buf, sizeof(s_buf), f) ||
3446 strncmp(s_buf, "#Marks", 6) != 0)
3452 while (fgets(s_buf, sizeof(s_buf), f))
3454 if (s_buf[0]=='#') continue;
3455 file_path = strtok(s_buf, ",");
3456 marks_value = strtok(nullptr, ",");
3457 if (isfile(file_path))
3459 FileData *fd = file_data_new_no_grouping(file_path);
3464 gint mark_no = 1 << n;
3465 if (atoi(marks_value) & mark_no)
3467 file_data_set_mark(fd, n , 1);
3478 gboolean marks_list_save(gchar *path, gboolean save)
3480 SecureSaveInfo *ssi;
3483 pathl = path_from_utf8(path);
3484 ssi = secure_open(pathl);
3488 log_printf(_("Error: Unable to write marks lists to: %s\n"), path);
3492 secure_fprintf(ssi, "#Marks lists\n");
3494 GString *marks = g_string_new("");
3497 g_hash_table_foreach(file_data_pool, marks_get_files, marks);
3499 secure_fprintf(ssi, "%s", marks->str);
3500 g_string_free(marks, TRUE);
3502 secure_fprintf(ssi, "#end\n");
3503 return (secure_close(ssi) == 0);
3506 static void marks_clear(gpointer key, gpointer value, gpointer)
3508 auto file_name = static_cast<gchar *>(key);
3513 if (isfile(file_name))
3515 fd = static_cast<FileData *>(value);
3516 if (fd && fd->marks > 0)
3522 if (fd->marks & mark_no)
3524 file_data_set_mark(fd, n , 0);
3532 void marks_clear_all()
3534 g_hash_table_foreach(file_data_pool, marks_clear, nullptr);
3537 void file_data_set_page_num(FileData *fd, gint page_num)
3539 if (fd->page_total > 1 && page_num < 0)
3541 fd->page_num = fd->page_total - 1;
3543 else if (fd->page_total > 1 && page_num <= fd->page_total)
3545 fd->page_num = page_num - 1;
3551 file_data_send_notification(fd, NOTIFY_REREAD);
3554 void file_data_inc_page_num(FileData *fd)
3556 if (fd->page_total > 0 && fd->page_num < fd->page_total - 1)
3558 fd->page_num = fd->page_num + 1;
3560 else if (fd->page_total == 0)
3562 fd->page_num = fd->page_num + 1;
3564 file_data_send_notification(fd, NOTIFY_REREAD);
3567 void file_data_dec_page_num(FileData *fd)
3569 if (fd->page_num > 0)
3571 fd->page_num = fd->page_num - 1;
3573 file_data_send_notification(fd, NOTIFY_REREAD);
3576 void file_data_set_page_total(FileData *fd, gint page_total)
3578 fd->page_total = page_total;
3581 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */