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.
26 #include "filefilter.h"
31 #include "main-defines.h"
33 #include "thumb-standard.h"
34 #include "ui-fileops.h"
37 #include "histogram.h"
38 #include "secure-save.h"
47 gint global_file_data_count = 0;
50 static GHashTable *file_data_pool = nullptr;
51 static GHashTable *file_data_planned_change_hash = nullptr;
53 static gint sidecar_file_priority(const gchar *extension);
54 static void file_data_check_sidecars(const GList *basename_list);
55 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
58 static SortType filelist_sort_method = SORT_NONE;
59 static gboolean filelist_sort_ascend = TRUE;
60 static gboolean filelist_sort_case = TRUE;
63 *-----------------------------------------------------------------------------
64 * text conversion utils
65 *-----------------------------------------------------------------------------
68 gchar *text_from_size(gint64 size)
78 /* what I would like to use is printf("%'d", size)
79 * BUT: not supported on every libc :(
83 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
84 a = g_strdup_printf("%d%09d", static_cast<guint>(size / 1000000000), static_cast<guint>(size % 1000000000));
88 a = g_strdup_printf("%d", static_cast<guint>(size));
94 b = g_new(gchar, l + n + 1);
119 gchar *text_from_size_abrev(gint64 size)
121 if (size < static_cast<gint64>(1024))
123 return g_strdup_printf(_("%d bytes"), static_cast<gint>(size));
125 if (size < static_cast<gint64>(1048576))
127 return g_strdup_printf(_("%.1f KiB"), static_cast<gdouble>(size) / 1024.0);
129 if (size < static_cast<gint64>(1073741824))
131 return g_strdup_printf(_("%.1f MiB"), static_cast<gdouble>(size) / 1048576.0);
134 /* to avoid overflowing the gdouble, do division in two steps */
136 return g_strdup_printf(_("%.1f GiB"), static_cast<gdouble>(size) / 1024.0);
139 /* note: returned string is valid until next call to text_from_time() */
140 const gchar *text_from_time(time_t t)
142 static gchar *ret = nullptr;
146 GError *error = nullptr;
148 btime = localtime(&t);
150 /* the %x warning about 2 digit years is not an error */
151 buflen = strftime(buf, sizeof(buf), "%x %X", btime);
152 if (buflen < 1) return "";
155 ret = g_locale_to_utf8(buf, buflen, nullptr, nullptr, &error);
158 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
167 *-----------------------------------------------------------------------------
168 * changed files detection and notification
169 *-----------------------------------------------------------------------------
172 void file_data_increment_version(FileData *fd)
178 fd->parent->version++;
179 fd->parent->valid_marks = 0;
183 static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st)
185 if (fd->size != st->st_size ||
186 fd->date != st->st_mtime)
188 fd->size = st->st_size;
189 fd->date = st->st_mtime;
190 fd->cdate = st->st_ctime;
191 fd->mode = st->st_mode;
192 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
193 fd->thumb_pixbuf = nullptr;
194 file_data_increment_version(fd);
195 file_data_send_notification(fd, NOTIFY_REREAD);
201 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
203 gboolean ret = FALSE;
206 ret = file_data_check_changed_single_file(fd, st);
208 work = fd->sidecar_files;
211 auto sfd = static_cast<FileData *>(work->data);
215 if (!stat_utf8(sfd->path, &st))
220 file_data_disconnect_sidecar_file(fd, sfd);
222 file_data_increment_version(sfd);
223 file_data_send_notification(sfd, NOTIFY_REREAD);
224 file_data_unref(sfd);
228 ret |= file_data_check_changed_files_recursive(sfd, &st);
234 gboolean file_data_check_changed_files(FileData *fd)
236 gboolean ret = FALSE;
239 if (fd->parent) fd = fd->parent;
241 if (!stat_utf8(fd->path, &st))
245 FileData *sfd = nullptr;
247 /* parent is missing, we have to rebuild whole group */
252 /* file_data_disconnect_sidecar_file might delete the file,
253 we have to keep the reference to prevent this */
254 sidecars = filelist_copy(fd->sidecar_files);
259 sfd = static_cast<FileData *>(work->data);
262 file_data_disconnect_sidecar_file(fd, sfd);
264 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
265 /* now we can release the sidecars */
266 filelist_free(sidecars);
267 file_data_increment_version(fd);
268 file_data_send_notification(fd, NOTIFY_REREAD);
273 ret |= file_data_check_changed_files_recursive(fd, &st);
280 *-----------------------------------------------------------------------------
281 * file name, extension, sorting, ...
282 *-----------------------------------------------------------------------------
285 static void file_data_set_collate_keys(FileData *fd)
287 gchar *caseless_name;
290 valid_name = g_filename_display_name(fd->name);
291 caseless_name = g_utf8_casefold(valid_name, -1);
293 g_free(fd->collate_key_name);
294 g_free(fd->collate_key_name_nocase);
296 fd->collate_key_name_natural = g_utf8_collate_key_for_filename(fd->name, -1);
297 fd->collate_key_name_nocase_natural = g_utf8_collate_key_for_filename(caseless_name, -1);
298 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
299 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
302 g_free(caseless_name);
305 static void file_data_set_path(FileData *fd, const gchar *path)
307 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
308 g_assert(file_data_pool);
312 if (fd->original_path)
314 g_hash_table_remove(file_data_pool, fd->original_path);
315 g_free(fd->original_path);
318 g_assert(!g_hash_table_lookup(file_data_pool, path));
320 fd->original_path = g_strdup(path);
321 g_hash_table_insert(file_data_pool, fd->original_path, fd);
323 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
325 fd->path = g_strdup(path);
327 fd->extension = fd->name + 1;
328 file_data_set_collate_keys(fd);
332 fd->path = g_strdup(path);
333 fd->name = filename_from_path(fd->path);
335 if (strcmp(fd->name, "..") == 0)
337 gchar *dir = remove_level_from_path(path);
339 fd->path = remove_level_from_path(dir);
342 fd->extension = fd->name + 2;
343 file_data_set_collate_keys(fd);
347 if (strcmp(fd->name, ".") == 0)
350 fd->path = remove_level_from_path(path);
352 fd->extension = fd->name + 1;
353 file_data_set_collate_keys(fd);
357 fd->extension = registered_extension_from_path(fd->path);
358 if (fd->extension == nullptr)
360 fd->extension = fd->name + strlen(fd->name);
363 fd->sidecar_priority = sidecar_file_priority(fd->extension);
364 file_data_set_collate_keys(fd);
368 *-----------------------------------------------------------------------------
369 * create or reuse Filedata
370 *-----------------------------------------------------------------------------
373 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
379 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
381 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
384 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
386 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
392 if (!fd && file_data_planned_change_hash)
394 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_planned_change_hash, path_utf8));
397 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
398 if (!isfile(fd->path))
401 file_data_apply_ci(fd);
412 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
414 #ifdef DEBUG_FILEDATA
417 file_data_check_changed_single_file(fd, st);
419 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
424 fd = g_new0(FileData, 1);
425 #ifdef DEBUG_FILEDATA
426 global_file_data_count++;
427 DEBUG_2("file data count++: %d", global_file_data_count);
430 fd->size = st->st_size;
431 fd->date = st->st_mtime;
432 fd->cdate = st->st_ctime;
433 fd->mode = st->st_mode;
435 fd->magick = FD_MAGICK;
437 fd->rating = STAR_RATING_NOT_READ;
438 fd->format_class = filter_file_get_class(path_utf8);
442 user = getpwuid(st->st_uid);
445 fd->owner = g_strdup_printf("%u", st->st_uid);
449 fd->owner = g_strdup(user->pw_name);
452 group = getgrgid(st->st_gid);
455 fd->group = g_strdup_printf("%u", st->st_gid);
459 fd->group = g_strdup(group->gr_name);
462 fd->sym_link = get_symbolic_link(path_utf8);
464 if (disable_sidecars) fd->disable_grouping = TRUE;
466 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
471 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
473 gchar *path_utf8 = path_to_utf8(path);
474 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
480 FileData *file_data_new_simple(const gchar *path_utf8)
485 if (!stat_utf8(path_utf8, &st))
491 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
492 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
501 void read_exif_time_data(FileData *file)
503 if (file->exifdate > 0)
505 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
516 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
517 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), (void *)file, file->path);
529 sscanf(tmp, "%4u:%2u:%2u %2u:%2u:%2u", &year, &month, &day, &hour, &min, &sec);
530 time_str.tm_year = year - 1900;
531 time_str.tm_mon = month - 1;
532 time_str.tm_mday = day;
533 time_str.tm_hour = hour;
534 time_str.tm_min = min;
535 time_str.tm_sec = sec;
536 time_str.tm_isdst = 0;
538 file->exifdate = mktime(&time_str);
544 void read_exif_time_digitized_data(FileData *file)
546 if (file->exifdate_digitized > 0)
548 DEBUG_1("%s set_exif_time_digitized_data: Already exists for %s", get_exec_time(), file->path);
559 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeDigitized");
560 DEBUG_2("%s set_exif_time_digitized_data: reading %p %s", get_exec_time(), (void *)file, file->path);
572 sscanf(tmp, "%4u:%2u:%2u %2u:%2u:%2u", &year, &month, &day, &hour, &min, &sec);
573 time_str.tm_year = year - 1900;
574 time_str.tm_mon = month - 1;
575 time_str.tm_mday = day;
576 time_str.tm_hour = hour;
577 time_str.tm_min = min;
578 time_str.tm_sec = sec;
579 time_str.tm_isdst = 0;
581 file->exifdate_digitized = mktime(&time_str);
587 void read_rating_data(FileData *file)
591 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
594 file->rating = atoi(rating_str);
603 #pragma GCC diagnostic push
604 #pragma GCC diagnostic ignored "-Wunused-function"
605 void set_exif_time_data_unused(GList *files)
607 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
611 auto *file = static_cast<FileData *>(files->data);
613 read_exif_time_data(file);
618 void set_exif_time_digitized_data_unused(GList *files)
620 DEBUG_1("%s set_exif_time_digitized_data: ...", get_exec_time());
624 auto *file = static_cast<FileData *>(files->data);
626 read_exif_time_digitized_data(file);
631 void set_rating_data_unused(GList *files)
634 DEBUG_1("%s set_rating_data: ...", get_exec_time());
638 auto *file = static_cast<FileData *>(files->data);
639 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
642 file->rating = atoi(rating_str);
648 #pragma GCC diagnostic pop
650 FileData *file_data_new_no_grouping(const gchar *path_utf8)
654 if (!stat_utf8(path_utf8, &st))
660 return file_data_new(path_utf8, &st, TRUE);
663 FileData *file_data_new_dir(const gchar *path_utf8)
667 if (!stat_utf8(path_utf8, &st))
673 /* dir or non-existing yet */
674 g_assert(S_ISDIR(st.st_mode));
676 return file_data_new(path_utf8, &st, TRUE);
680 *-----------------------------------------------------------------------------
682 *-----------------------------------------------------------------------------
685 #ifdef DEBUG_FILEDATA
686 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
688 FileData *file_data_ref(FileData *fd)
691 if (fd == nullptr) return nullptr;
692 if (fd->magick != FD_MAGICK)
693 #ifdef DEBUG_FILEDATA
694 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, (void *)fd);
696 log_printf("Error: fd magick mismatch fd=%p", fd);
698 g_assert(fd->magick == FD_MAGICK);
701 #ifdef DEBUG_FILEDATA
702 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->path, file, line);
704 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
710 * @brief Print ref. count and image name
713 * Print image ref. count and full path name of all images in
714 * the file_data_pool.
716 * Used only by DEBUG_FD()
718 void file_data_dump()
720 #ifdef DEBUG_FILEDATA
726 list = g_hash_table_get_values(file_data_pool);
728 log_printf("%d", global_file_data_count);
729 log_printf("%d", g_list_length(list));
734 fd = static_cast<FileData *>(work->data);
735 log_printf("%-4d %s", fd->ref, fd->path);
744 static void file_data_free(FileData *fd)
746 g_assert(fd->magick == FD_MAGICK);
747 g_assert(fd->ref == 0);
748 g_assert(!fd->locked);
750 #ifdef DEBUG_FILEDATA
751 global_file_data_count--;
752 DEBUG_2("file data count--: %d", global_file_data_count);
755 metadata_cache_free(fd);
756 g_hash_table_remove(file_data_pool, fd->original_path);
759 g_free(fd->original_path);
760 g_free(fd->collate_key_name);
761 g_free(fd->collate_key_name_nocase);
762 g_free(fd->extended_extension);
763 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
764 histmap_free(fd->histmap);
767 g_free(fd->sym_link);
768 g_free(fd->format_name);
769 g_assert(fd->sidecar_files == nullptr); /* sidecar files must be freed before calling this */
771 file_data_change_info_free(nullptr, fd);
776 * @brief Checks if the FileData is referenced
778 * Checks the refcount and whether the FileData is locked.
780 static gboolean file_data_check_has_ref(FileData *fd)
782 return fd->ref > 0 || fd->locked;
786 * @brief Consider freeing a FileData.
788 * This function will free a FileData and its children provided that neither its parent nor it has
789 * a positive refcount, and provided that neither is locked.
791 static void file_data_consider_free(FileData *fd)
794 FileData *parent = fd->parent ? fd->parent : fd;
796 g_assert(fd->magick == FD_MAGICK);
797 if (file_data_check_has_ref(fd)) return;
798 if (file_data_check_has_ref(parent)) return;
800 work = parent->sidecar_files;
803 auto sfd = static_cast<FileData *>(work->data);
804 if (file_data_check_has_ref(sfd)) return;
808 /* Neither the parent nor the siblings are referenced, so we can free everything */
809 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
810 fd->path, fd->parent ? parent->path : "-");
812 g_list_free_full(parent->sidecar_files, reinterpret_cast<GDestroyNotify>(file_data_free));
813 parent->sidecar_files = nullptr;
815 file_data_free(parent);
818 #ifdef DEBUG_FILEDATA
819 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
821 void file_data_unref(FileData *fd)
824 if (fd == nullptr) return;
825 if (fd->magick != FD_MAGICK)
826 #ifdef DEBUG_FILEDATA
827 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, (void *)fd);
829 log_printf("Error: fd magick mismatch fd=%p", fd);
831 g_assert(fd->magick == FD_MAGICK);
834 #ifdef DEBUG_FILEDATA
835 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->locked, fd->path,
838 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
841 // Free FileData if it's no longer ref'd
842 file_data_consider_free(fd);
846 * @brief Lock the FileData in memory.
848 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
849 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
850 * even if the code would continue to function properly even if the FileData were freed. Code that
851 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
853 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
854 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
856 void file_data_lock(FileData *fd)
858 if (fd == nullptr) return;
859 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
861 g_assert(fd->magick == FD_MAGICK);
864 DEBUG_2("file_data_ref fd=%p (%d): '%s'", (void *)fd, fd->ref, fd->path);
868 * @brief Reset the maintain-FileData-in-memory lock
870 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
871 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
872 * keeping it from being freed.
874 void file_data_unlock(FileData *fd)
876 if (fd == nullptr) return;
877 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
879 g_assert(fd->magick == FD_MAGICK);
882 // Free FileData if it's no longer ref'd
883 file_data_consider_free(fd);
887 * @brief Lock all of the FileDatas in the provided list
889 * @see file_data_lock(#FileData)
891 void file_data_lock_list(GList *list)
898 auto fd = static_cast<FileData *>(work->data);
905 * @brief Unlock all of the FileDatas in the provided list
907 * @see #file_data_unlock(#FileData)
909 void file_data_unlock_list(GList *list)
916 auto fd = static_cast<FileData *>(work->data);
918 file_data_unlock(fd);
923 *-----------------------------------------------------------------------------
924 * sidecar file info struct
925 *-----------------------------------------------------------------------------
928 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
930 auto fda = static_cast<const FileData *>(a);
931 auto fdb = static_cast<const FileData *>(b);
933 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
934 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
936 return strcmp(fdb->extension, fda->extension);
940 static gint sidecar_file_priority(const gchar *extension)
945 if (extension == nullptr)
948 work = sidecar_ext_get_list();
951 auto ext = static_cast<gchar *>(work->data);
954 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
960 static void file_data_check_sidecars(const GList *basename_list)
962 /* basename_list contains the new group - first is the parent, then sorted sidecars */
963 /* all files in the list have ref count > 0 */
970 if (!basename_list) return;
973 DEBUG_2("basename start");
974 work = basename_list;
977 auto fd = static_cast<FileData *>(work->data);
979 g_assert(fd->magick == FD_MAGICK);
980 DEBUG_2("basename: %p %s", (void *)fd, fd->name);
983 g_assert(fd->parent->magick == FD_MAGICK);
984 DEBUG_2(" parent: %p", (void *)fd->parent);
986 s_work = fd->sidecar_files;
989 auto sfd = static_cast<FileData *>(s_work->data);
990 s_work = s_work->next;
991 g_assert(sfd->magick == FD_MAGICK);
992 DEBUG_2(" sidecar: %p %s", (void *)sfd, sfd->name);
995 g_assert(fd->parent == nullptr || fd->sidecar_files == nullptr);
998 parent_fd = static_cast<FileData *>(basename_list->data);
1000 /* check if the second and next entries of basename_list are already connected
1001 as sidecars of the first entry (parent_fd) */
1002 work = basename_list->next;
1003 s_work = parent_fd->sidecar_files;
1005 while (work && s_work)
1007 if (work->data != s_work->data) break;
1009 s_work = s_work->next;
1012 if (!work && !s_work)
1014 DEBUG_2("basename no change");
1015 return; /* no change in grouping */
1018 /* we have to regroup it */
1020 /* first, disconnect everything and send notification*/
1022 work = basename_list;
1025 auto fd = static_cast<FileData *>(work->data);
1027 g_assert(fd->parent == nullptr || fd->sidecar_files == nullptr);
1031 FileData *old_parent = fd->parent;
1032 g_assert(old_parent->parent == nullptr || old_parent->sidecar_files == nullptr);
1033 file_data_ref(old_parent);
1034 file_data_disconnect_sidecar_file(old_parent, fd);
1035 file_data_send_notification(old_parent, NOTIFY_REREAD);
1036 file_data_unref(old_parent);
1039 while (fd->sidecar_files)
1041 auto sfd = static_cast<FileData *>(fd->sidecar_files->data);
1042 g_assert(sfd->parent == nullptr || sfd->sidecar_files == nullptr);
1044 file_data_disconnect_sidecar_file(fd, sfd);
1045 file_data_send_notification(sfd, NOTIFY_REREAD);
1046 file_data_unref(sfd);
1048 file_data_send_notification(fd, NOTIFY_GROUPING);
1050 g_assert(fd->parent == nullptr && fd->sidecar_files == nullptr);
1053 /* now we can form the new group */
1054 work = basename_list->next;
1055 new_sidecars = nullptr;
1058 auto sfd = static_cast<FileData *>(work->data);
1059 g_assert(sfd->magick == FD_MAGICK);
1060 g_assert(sfd->parent == nullptr && sfd->sidecar_files == nullptr);
1061 sfd->parent = parent_fd;
1062 new_sidecars = g_list_prepend(new_sidecars, sfd);
1065 g_assert(parent_fd->sidecar_files == nullptr);
1066 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
1067 DEBUG_1("basename group changed for %s", parent_fd->path);
1071 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
1073 g_assert(target->magick == FD_MAGICK);
1074 g_assert(sfd->magick == FD_MAGICK);
1075 g_assert(g_list_find(target->sidecar_files, sfd));
1077 file_data_ref(target);
1080 g_assert(sfd->parent == target);
1082 file_data_increment_version(sfd); /* increments both sfd and target */
1084 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
1085 sfd->parent = nullptr;
1086 g_free(sfd->extended_extension);
1087 sfd->extended_extension = nullptr;
1089 file_data_unref(target);
1090 file_data_unref(sfd);
1093 /* disables / enables grouping for particular file, sends UPDATE notification */
1094 void file_data_disable_grouping(FileData *fd, gboolean disable)
1096 if (!fd->disable_grouping == !disable) return;
1098 fd->disable_grouping = !!disable;
1104 FileData *parent = file_data_ref(fd->parent);
1105 file_data_disconnect_sidecar_file(parent, fd);
1106 file_data_send_notification(parent, NOTIFY_GROUPING);
1107 file_data_unref(parent);
1109 else if (fd->sidecar_files)
1111 GList *sidecar_files = filelist_copy(fd->sidecar_files);
1112 GList *work = sidecar_files;
1115 auto sfd = static_cast<FileData *>(work->data);
1117 file_data_disconnect_sidecar_file(fd, sfd);
1118 file_data_send_notification(sfd, NOTIFY_GROUPING);
1120 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
1121 filelist_free(sidecar_files);
1125 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
1130 file_data_increment_version(fd);
1131 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
1133 file_data_send_notification(fd, NOTIFY_GROUPING);
1136 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
1143 auto fd = static_cast<FileData *>(work->data);
1145 file_data_disable_grouping(fd, disable);
1153 *-----------------------------------------------------------------------------
1155 *-----------------------------------------------------------------------------
1159 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
1162 if (!filelist_sort_ascend)
1169 switch (filelist_sort_method)
1174 if (fa->size < fb->size) return -1;
1175 if (fa->size > fb->size) return 1;
1176 /* fall back to name */
1179 if (fa->date < fb->date) return -1;
1180 if (fa->date > fb->date) return 1;
1181 /* fall back to name */
1184 if (fa->cdate < fb->cdate) return -1;
1185 if (fa->cdate > fb->cdate) return 1;
1186 /* fall back to name */
1189 if (fa->exifdate < fb->exifdate) return -1;
1190 if (fa->exifdate > fb->exifdate) return 1;
1191 /* fall back to name */
1193 case SORT_EXIFTIMEDIGITIZED:
1194 if (fa->exifdate_digitized < fb->exifdate_digitized) return -1;
1195 if (fa->exifdate_digitized > fb->exifdate_digitized) return 1;
1196 /* fall back to name */
1199 if (fa->rating < fb->rating) return -1;
1200 if (fa->rating > fb->rating) return 1;
1201 /* fall back to name */
1204 if (fa->format_class < fb->format_class) return -1;
1205 if (fa->format_class > fb->format_class) return 1;
1206 /* fall back to name */
1209 ret = strcmp(fa->collate_key_name_natural, fb->collate_key_name_natural);
1210 if (ret != 0) return ret;
1211 /* fall back to name */
1217 if (filelist_sort_case)
1218 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1220 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1222 if (ret != 0) return ret;
1224 /* do not return 0 unless the files are really the same
1225 file_data_pool ensures that original_path is unique
1227 return strcmp(fa->original_path, fb->original_path);
1230 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1232 filelist_sort_method = method;
1233 filelist_sort_ascend = ascend;
1234 return filelist_sort_compare_filedata(fa, fb);
1237 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1239 return filelist_sort_compare_filedata(static_cast<FileData *>(a), static_cast<FileData *>(b));
1242 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, gboolean case_sensitive, GCompareFunc cb)
1244 filelist_sort_method = method;
1245 filelist_sort_ascend = ascend;
1246 filelist_sort_case = case_sensitive;
1247 return g_list_sort(list, cb);
1250 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, gboolean case_sensitive, GCompareFunc cb)
1252 filelist_sort_method = method;
1253 filelist_sort_ascend = ascend;
1254 filelist_sort_case = case_sensitive;
1255 return g_list_insert_sorted(list, data, cb);
1258 GList *filelist_sort(GList *list, SortType method, gboolean ascend, gboolean case_sensitive)
1260 return filelist_sort_full(list, method, ascend, case_sensitive, reinterpret_cast<GCompareFunc>(filelist_sort_file_cb));
1263 #pragma GCC diagnostic push
1264 #pragma GCC diagnostic ignored "-Wunused-function"
1265 GList *filelist_insert_sort_unused(GList *list, FileData *fd, SortType method, gboolean ascend)
1267 return filelist_insert_sort_full(list, fd, method, ascend, ascend, (GCompareFunc) filelist_sort_file_cb);
1269 #pragma GCC diagnostic pop
1272 *-----------------------------------------------------------------------------
1273 * basename hash - grouping of sidecars in filelist
1274 *-----------------------------------------------------------------------------
1278 static GHashTable *file_data_basename_hash_new()
1280 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, nullptr);
1283 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1286 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1288 list = static_cast<GList *>(g_hash_table_lookup(basename_hash, basename));
1292 DEBUG_1("TG: basename_hash not found for %s",fd->path);
1293 const gchar *parent_extension = registered_extension_from_path(basename);
1295 if (parent_extension)
1297 DEBUG_1("TG: parent extension %s",parent_extension);
1298 gchar *parent_basename = g_strndup(basename, parent_extension - basename);
1299 DEBUG_1("TG: parent basename %s",parent_basename);
1300 auto parent_fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, basename));
1303 DEBUG_1("TG: parent fd found");
1304 list = static_cast<GList *>(g_hash_table_lookup(basename_hash, parent_basename));
1305 if (!g_list_find(list, parent_fd))
1307 DEBUG_1("TG: parent fd doesn't fit");
1308 g_free(parent_basename);
1314 basename = parent_basename;
1315 fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
1321 if (!g_list_find(list, fd))
1323 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1324 g_hash_table_insert(basename_hash, basename, list);
1333 static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
1335 file_data_basename_hash_insert(static_cast<GHashTable *>(basename_hash), static_cast<FileData *>(fd));
1338 static void file_data_basename_hash_remove_list(gpointer, gpointer value, gpointer)
1340 filelist_free(static_cast<GList *>(value));
1343 static void file_data_basename_hash_free(GHashTable *basename_hash)
1345 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, nullptr);
1346 g_hash_table_destroy(basename_hash);
1350 *-----------------------------------------------------------------------------
1351 * handling sidecars in filelist
1352 *-----------------------------------------------------------------------------
1355 static GList *filelist_filter_out_sidecars(GList *flist)
1357 GList *work = flist;
1358 GList *flist_filtered = nullptr;
1362 auto fd = static_cast<FileData *>(work->data);
1365 if (fd->parent) /* remove fd's that are children */
1366 file_data_unref(fd);
1368 flist_filtered = g_list_prepend(flist_filtered, fd);
1372 return flist_filtered;
1375 static void file_data_basename_hash_to_sidecars(gpointer, gpointer value, gpointer)
1377 auto basename_list = static_cast<GList *>(value);
1378 file_data_check_sidecars(basename_list);
1382 static gboolean is_hidden_file(const gchar *name)
1384 if (name[0] != '.') return FALSE;
1385 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1390 *-----------------------------------------------------------------------------
1391 * the main filelist function
1392 *-----------------------------------------------------------------------------
1395 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1400 GList *dlist = nullptr;
1401 GList *flist = nullptr;
1402 GList *xmp_files = nullptr;
1403 gint (*stat_func)(const gchar *path, struct stat *buf);
1404 GHashTable *basename_hash = nullptr;
1406 g_assert(files || dirs);
1408 if (files) *files = nullptr;
1409 if (dirs) *dirs = nullptr;
1411 pathl = path_from_utf8(dir_path);
1412 if (!pathl) return FALSE;
1414 dp = opendir(pathl);
1421 if (files) basename_hash = file_data_basename_hash_new();
1423 if (follow_symlinks)
1428 while ((dir = readdir(dp)) != nullptr)
1430 struct stat ent_sbuf;
1431 const gchar *name = dir->d_name;
1434 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1437 filepath = g_build_filename(pathl, name, NULL);
1438 if (stat_func(filepath, &ent_sbuf) >= 0)
1440 if (S_ISDIR(ent_sbuf.st_mode))
1442 /* we ignore the .thumbnails dir for cleanliness */
1444 (name[0] != '.' || (name[1] != '\0' && (name[1] != '.' || name[2] != '\0'))) &&
1445 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1446 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1447 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1449 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1454 if (files && filter_name_exists(name))
1456 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1457 flist = g_list_prepend(flist, fd);
1458 if (fd->sidecar_priority && !fd->disable_grouping)
1460 if (strcmp(fd->extension, ".xmp") != 0)
1461 file_data_basename_hash_insert(basename_hash, fd);
1463 xmp_files = g_list_append(xmp_files, fd);
1470 if (errno == EOVERFLOW)
1472 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1484 g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
1485 g_list_free(xmp_files);
1488 if (dirs) *dirs = dlist;
1492 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, nullptr);
1494 *files = filelist_filter_out_sidecars(flist);
1496 if (basename_hash) file_data_basename_hash_free(basename_hash);
1501 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1503 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1506 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1508 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1511 FileData *file_data_new_group(const gchar *path_utf8)
1518 if (!file_data_pool)
1520 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
1523 if (!stat_utf8(path_utf8, &st))
1529 if (S_ISDIR(st.st_mode))
1530 return file_data_new(path_utf8, &st, TRUE);
1532 dir = remove_level_from_path(path_utf8);
1534 filelist_read_real(dir, &files, nullptr, TRUE);
1536 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
1537 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1543 filelist_free(files);
1549 void filelist_free(GList *list)
1556 file_data_unref((FileData *)work->data);
1564 GList *filelist_copy(GList *list)
1566 GList *new_list = nullptr;
1568 for (GList *work = list; work; work = work->next)
1570 auto fd = static_cast<FileData *>(work->data);
1572 new_list = g_list_prepend(new_list, file_data_ref(fd));
1575 return g_list_reverse(new_list);
1578 GList *filelist_from_path_list(GList *list)
1580 GList *new_list = nullptr;
1588 path = static_cast<gchar *>(work->data);
1591 new_list = g_list_prepend(new_list, file_data_new_group(path));
1594 return g_list_reverse(new_list);
1597 GList *filelist_to_path_list(GList *list)
1599 GList *new_list = nullptr;
1607 fd = static_cast<FileData *>(work->data);
1610 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1613 return g_list_reverse(new_list);
1616 GList *filelist_filter(GList *list, gboolean is_dir_list)
1620 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1625 auto fd = static_cast<FileData *>(work->data);
1626 const gchar *name = fd->name;
1630 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1631 (!is_dir_list && !filter_name_exists(name)) ||
1632 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1633 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1635 list = g_list_remove_link(list, link);
1636 file_data_unref(fd);
1645 *-----------------------------------------------------------------------------
1646 * filelist recursive
1647 *-----------------------------------------------------------------------------
1650 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1652 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1655 GList *filelist_sort_path(GList *list)
1657 return g_list_sort(list, filelist_sort_path_cb);
1660 static void filelist_recursive_append(GList **list, GList *dirs)
1667 auto fd = static_cast<FileData *>(work->data);
1671 if (filelist_read(fd, &f, &d))
1673 f = filelist_filter(f, FALSE);
1674 f = filelist_sort_path(f);
1675 *list = g_list_concat(*list, f);
1677 d = filelist_filter(d, TRUE);
1678 d = filelist_sort_path(d);
1679 filelist_recursive_append(list, d);
1687 static void filelist_recursive_append_full(GList **list, GList *dirs, SortType method, gboolean ascend, gboolean case_sensitive)
1694 auto fd = static_cast<FileData *>(work->data);
1698 if (filelist_read(fd, &f, &d))
1700 f = filelist_filter(f, FALSE);
1701 f = filelist_sort_full(f, method, ascend, case_sensitive, reinterpret_cast<GCompareFunc>(filelist_sort_file_cb));
1702 *list = g_list_concat(*list, f);
1704 d = filelist_filter(d, TRUE);
1705 d = filelist_sort_path(d);
1706 filelist_recursive_append_full(list, d, method, ascend, case_sensitive);
1714 GList *filelist_recursive(FileData *dir_fd)
1719 if (!filelist_read(dir_fd, &list, &d)) return nullptr;
1720 list = filelist_filter(list, FALSE);
1721 list = filelist_sort_path(list);
1723 d = filelist_filter(d, TRUE);
1724 d = filelist_sort_path(d);
1725 filelist_recursive_append(&list, d);
1731 GList *filelist_recursive_full(FileData *dir_fd, SortType method, gboolean ascend, gboolean case_sensitive)
1736 if (!filelist_read(dir_fd, &list, &d)) return nullptr;
1737 list = filelist_filter(list, FALSE);
1738 list = filelist_sort_full(list, method, ascend, case_sensitive, reinterpret_cast<GCompareFunc>(filelist_sort_file_cb));
1740 d = filelist_filter(d, TRUE);
1741 d = filelist_sort_path(d);
1742 filelist_recursive_append_full(&list, d, method, ascend, case_sensitive);
1749 *-----------------------------------------------------------------------------
1750 * file modification support
1751 *-----------------------------------------------------------------------------
1755 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1757 if (!fdci && fd) fdci = fd->change;
1761 g_free(fdci->source);
1766 if (fd) fd->change = nullptr;
1769 static gboolean file_data_can_write_directly(FileData *fd)
1771 return filter_name_is_writable(fd->extension);
1774 static gboolean file_data_can_write_sidecar(FileData *fd)
1776 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1779 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1781 gchar *sidecar_path = nullptr;
1784 if (!file_data_can_write_sidecar(fd)) return nullptr;
1786 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1787 gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1790 auto sfd = static_cast<FileData *>(work->data);
1792 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1794 sidecar_path = g_strdup(sfd->path);
1798 g_free(extended_extension);
1800 if (!existing_only && !sidecar_path)
1802 if (options->metadata.sidecar_extended_name)
1803 sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1806 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1807 sidecar_path = g_strconcat(base, ".xmp", NULL);
1812 return sidecar_path;
1816 * marks and orientation
1819 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1820 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1821 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1822 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1824 gboolean file_data_get_mark(FileData *fd, gint n)
1826 gboolean valid = (fd->valid_marks & (1 << n));
1828 if (file_data_get_mark_func[n] && !valid)
1830 guint old = fd->marks;
1831 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1833 if (!value != !(fd->marks & (1 << n)))
1835 fd->marks = fd->marks ^ (1 << n);
1838 fd->valid_marks |= (1 << n);
1839 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1841 file_data_unref(fd);
1843 else if (!old && fd->marks)
1849 return !!(fd->marks & (1 << n));
1852 guint file_data_get_marks(FileData *fd)
1855 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1859 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1862 if (!value == !file_data_get_mark(fd, n)) return;
1864 if (file_data_set_mark_func[n])
1866 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1871 fd->marks = fd->marks ^ (1 << n);
1873 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1875 file_data_unref(fd);
1877 else if (!old && fd->marks)
1882 file_data_increment_version(fd);
1883 file_data_send_notification(fd, NOTIFY_MARKS);
1886 gboolean file_data_filter_marks(FileData *fd, guint filter)
1889 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1890 return ((fd->marks & filter) == filter);
1893 GList *file_data_filter_marks_list(GList *list, guint filter)
1900 auto fd = static_cast<FileData *>(work->data);
1904 if (!file_data_filter_marks(fd, filter))
1906 list = g_list_remove_link(list, link);
1907 file_data_unref(fd);
1915 gboolean file_data_filter_file_filter(FileData *fd, GRegex *filter)
1917 return g_regex_match(filter, fd->name, static_cast<GRegexMatchFlags>(0), nullptr);
1920 GList *file_data_filter_file_filter_list(GList *list, GRegex *filter)
1927 auto fd = static_cast<FileData *>(work->data);
1931 if (!file_data_filter_file_filter(fd, filter))
1933 list = g_list_remove_link(list, link);
1934 file_data_unref(fd);
1942 static gboolean file_data_filter_class(FileData *fd, guint filter)
1946 for (i = 0; i < FILE_FORMAT_CLASSES; i++)
1948 if (filter & (1 << i))
1950 if (static_cast<FileFormatClass>(i) == filter_file_get_class(fd->path))
1960 GList *file_data_filter_class_list(GList *list, guint filter)
1967 auto fd = static_cast<FileData *>(work->data);
1971 if (!file_data_filter_class(fd, filter))
1973 list = g_list_remove_link(list, link);
1974 file_data_unref(fd);
1982 static void file_data_notify_mark_func(gpointer, gpointer value, gpointer)
1984 auto fd = static_cast<FileData *>(value);
1985 file_data_increment_version(fd);
1986 file_data_send_notification(fd, NOTIFY_MARKS);
1989 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1991 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1993 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1995 file_data_get_mark_func[n] = get_mark_func;
1996 file_data_set_mark_func[n] = set_mark_func;
1997 file_data_mark_func_data[n] = data;
1998 file_data_destroy_mark_func[n] = notify;
2000 if (get_mark_func && file_data_pool)
2002 /* this effectively changes all known files */
2003 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, nullptr);
2009 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
2011 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
2012 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
2013 if (data) *data = file_data_mark_func_data[n];
2016 #pragma GCC diagnostic push
2017 #pragma GCC diagnostic ignored "-Wunused-function"
2018 gint file_data_get_user_orientation_unused(FileData *fd)
2020 return fd->user_orientation;
2023 void file_data_set_user_orientation_unused(FileData *fd, gint value)
2025 if (fd->user_orientation == value) return;
2027 fd->user_orientation = value;
2028 file_data_increment_version(fd);
2029 file_data_send_notification(fd, NOTIFY_ORIENTATION);
2031 #pragma GCC diagnostic pop
2035 * file_data - operates on the given fd
2036 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
2040 /* return list of sidecar file extensions in a string */
2041 gchar *file_data_sc_list_to_string(FileData *fd)
2044 GString *result = g_string_new("");
2046 work = fd->sidecar_files;
2049 auto sfd = static_cast<FileData *>(work->data);
2051 result = g_string_append(result, "+ ");
2052 result = g_string_append(result, sfd->extension);
2054 if (work) result = g_string_append_c(result, ' ');
2057 return g_string_free(result, FALSE);
2063 * add FileDataChangeInfo (see typedefs.h) for the given operation
2064 * uses file_data_add_change_info
2066 * fails if the fd->change already exists - change operations can't run in parallel
2067 * fd->change_info works as a lock
2069 * dest can be NULL - in this case the current name is used for now, it will
2074 FileDataChangeInfo types:
2076 MOVE - path is changed, name may be changed too
2077 RENAME - path remains unchanged, name is changed
2078 extension should remain (FIXME should we allow editing extension? it will make problems with grouping)
2079 sidecar names are changed too, extensions are not changed
2081 UPDATE - file size, date or grouping has been changed
2084 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
2086 FileDataChangeInfo *fdci;
2088 if (fd->change) return FALSE;
2090 fdci = g_new0(FileDataChangeInfo, 1);
2095 fdci->source = g_strdup(src);
2097 fdci->source = g_strdup(fd->path);
2100 fdci->dest = g_strdup(dest);
2107 static void file_data_planned_change_remove(FileData *fd)
2109 if (file_data_planned_change_hash &&
2110 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
2112 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
2114 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
2115 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
2116 file_data_unref(fd);
2117 if (g_hash_table_size(file_data_planned_change_hash) == 0)
2119 g_hash_table_destroy(file_data_planned_change_hash);
2120 file_data_planned_change_hash = nullptr;
2121 DEBUG_1("planned change: empty");
2128 void file_data_free_ci(FileData *fd)
2130 FileDataChangeInfo *fdci = fd->change;
2134 file_data_planned_change_remove(fd);
2136 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
2138 g_free(fdci->source);
2143 fd->change = nullptr;
2146 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
2148 FileDataChangeInfo *fdci = fd->change;
2150 fdci->regroup_when_finished = enable;
2153 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
2157 if (fd->parent) fd = fd->parent;
2159 if (fd->change) return FALSE;
2161 work = fd->sidecar_files;
2164 auto sfd = static_cast<FileData *>(work->data);
2166 if (sfd->change) return FALSE;
2170 file_data_add_ci(fd, type, nullptr, nullptr);
2172 work = fd->sidecar_files;
2175 auto sfd = static_cast<FileData *>(work->data);
2177 file_data_add_ci(sfd, type, nullptr, nullptr);
2184 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
2188 if (fd->parent) fd = fd->parent;
2190 if (!fd->change || fd->change->type != type) return FALSE;
2192 work = fd->sidecar_files;
2195 auto sfd = static_cast<FileData *>(work->data);
2197 if (!sfd->change || sfd->change->type != type) return FALSE;
2205 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
2207 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
2208 file_data_sc_update_ci_copy(fd, dest_path);
2212 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
2214 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
2215 file_data_sc_update_ci_move(fd, dest_path);
2219 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
2221 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
2222 file_data_sc_update_ci_rename(fd, dest_path);
2226 gboolean file_data_sc_add_ci_delete(FileData *fd)
2228 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
2231 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
2233 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
2234 file_data_sc_update_ci_unspecified(fd, dest_path);
2238 gboolean file_data_add_ci_write_metadata(FileData *fd)
2240 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, nullptr, nullptr);
2243 void file_data_sc_free_ci(FileData *fd)
2247 if (fd->parent) fd = fd->parent;
2249 file_data_free_ci(fd);
2251 work = fd->sidecar_files;
2254 auto sfd = static_cast<FileData *>(work->data);
2256 file_data_free_ci(sfd);
2261 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2264 gboolean ret = TRUE;
2269 auto fd = static_cast<FileData *>(work->data);
2271 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2278 static void file_data_sc_revert_ci_list(GList *fd_list)
2285 auto fd = static_cast<FileData *>(work->data);
2287 file_data_sc_free_ci(fd);
2292 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2299 auto fd = static_cast<FileData *>(work->data);
2301 if (!func(fd, dest))
2303 file_data_sc_revert_ci_list(work->prev);
2312 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2314 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2317 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2319 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2322 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2324 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2327 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2329 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2332 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2335 gboolean ret = TRUE;
2340 auto fd = static_cast<FileData *>(work->data);
2342 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2349 void file_data_free_ci_list(GList *fd_list)
2356 auto fd = static_cast<FileData *>(work->data);
2358 file_data_free_ci(fd);
2363 void file_data_sc_free_ci_list(GList *fd_list)
2370 auto fd = static_cast<FileData *>(work->data);
2372 file_data_sc_free_ci(fd);
2378 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2379 * fails if fd->change does not exist or the change type does not match
2382 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2384 FileDataChangeType type = fd->change->type;
2386 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2390 if (!file_data_planned_change_hash)
2391 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2393 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2395 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2396 g_hash_table_remove(file_data_planned_change_hash, old_path);
2397 file_data_unref(fd);
2400 ofd = static_cast<FileData *>(g_hash_table_lookup(file_data_planned_change_hash, new_path));
2405 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2406 g_hash_table_remove(file_data_planned_change_hash, new_path);
2407 file_data_unref(ofd);
2410 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2412 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2417 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2419 gchar *old_path = fd->change->dest;
2421 fd->change->dest = g_strdup(dest_path);
2422 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2426 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2428 const gchar *extension = registered_extension_from_path(fd->change->source);
2429 gchar *base = remove_extension_from_path(dest_path);
2430 gchar *old_path = fd->change->dest;
2432 fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2433 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2439 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2442 gchar *dest_path_full = nullptr;
2444 if (fd->parent) fd = fd->parent;
2448 dest_path = fd->path;
2450 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2452 gchar *dir = remove_level_from_path(fd->path);
2454 dest_path_full = g_build_filename(dir, dest_path, NULL);
2456 dest_path = dest_path_full;
2458 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2460 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2461 dest_path = dest_path_full;
2464 file_data_update_ci_dest(fd, dest_path);
2466 work = fd->sidecar_files;
2469 auto sfd = static_cast<FileData *>(work->data);
2471 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2475 g_free(dest_path_full);
2478 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2480 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2481 file_data_sc_update_ci(fd, dest_path);
2485 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2487 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2490 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2492 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2495 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2497 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2500 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2502 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2505 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2507 gboolean (*func)(FileData *, const gchar *))
2510 gboolean ret = TRUE;
2515 auto fd = static_cast<FileData *>(work->data);
2517 if (!func(fd, dest)) ret = FALSE;
2524 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2526 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2529 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2531 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2534 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2536 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2541 * verify source and dest paths - dest image exists, etc.
2542 * it should detect all possible problems with the planned operation
2545 gint file_data_verify_ci(FileData *fd, GList *list)
2547 gint ret = CHANGE_OK;
2549 GList *work = nullptr;
2550 FileData *fd1 = nullptr;
2554 DEBUG_1("Change checked: no change info: %s", fd->path);
2558 if (!isname(fd->path))
2560 /* this probably should not happen */
2561 ret |= CHANGE_NO_SRC;
2562 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2566 dir = remove_level_from_path(fd->path);
2568 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2569 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2570 fd->change->type != FILEDATA_CHANGE_RENAME &&
2571 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2574 ret |= CHANGE_WARN_UNSAVED_META;
2575 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2578 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2579 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2580 !access_file(fd->path, R_OK))
2582 ret |= CHANGE_NO_READ_PERM;
2583 DEBUG_1("Change checked: no read permission: %s", fd->path);
2585 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2586 !access_file(dir, W_OK))
2588 ret |= CHANGE_NO_WRITE_PERM_DIR;
2589 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2591 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2592 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2593 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2594 !access_file(fd->path, W_OK))
2596 ret |= CHANGE_WARN_NO_WRITE_PERM;
2597 DEBUG_1("Change checked: no write permission: %s", fd->path);
2599 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2600 - that means that there are no hard errors and warnings can be disabled
2601 - the destination is determined during the check
2603 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2605 /* determine destination file */
2606 gboolean have_dest = FALSE;
2607 gchar *dest_dir = nullptr;
2609 if (options->metadata.save_in_image_file)
2611 if (file_data_can_write_directly(fd))
2613 /* we can write the file directly */
2614 if (access_file(fd->path, W_OK))
2620 if (options->metadata.warn_on_write_problems)
2622 ret |= CHANGE_WARN_NO_WRITE_PERM;
2623 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2627 else if (file_data_can_write_sidecar(fd))
2629 /* we can write sidecar */
2630 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2631 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2633 file_data_update_ci_dest(fd, sidecar);
2638 if (options->metadata.warn_on_write_problems)
2640 ret |= CHANGE_WARN_NO_WRITE_PERM;
2641 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2650 /* write private metadata file under ~/.geeqie */
2652 /* If an existing metadata file exists, we will try writing to
2653 * it's location regardless of the user's preference.
2655 gchar *metadata_path = nullptr;
2657 /* but ignore XMP if we are not able to write it */
2658 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2660 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2662 if (metadata_path && !access_file(metadata_path, W_OK))
2664 g_free(metadata_path);
2665 metadata_path = nullptr;
2672 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2673 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2675 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2677 metadata_path = g_build_filename(dest_dir, filename, NULL);
2681 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2683 file_data_update_ci_dest(fd, metadata_path);
2687 ret |= CHANGE_NO_WRITE_PERM_DEST;
2688 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2690 g_free(metadata_path);
2695 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2700 same = (strcmp(fd->path, fd->change->dest) == 0);
2704 const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2705 if (!dest_ext) dest_ext = "";
2706 if (!options->file_filter.disable_file_extension_checks)
2708 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2710 ret |= CHANGE_WARN_CHANGED_EXT;
2711 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2717 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /** @FIXME this is now needed for running editors */
2719 ret |= CHANGE_WARN_SAME;
2720 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2724 dest_dir = remove_level_from_path(fd->change->dest);
2726 if (!isdir(dest_dir))
2728 ret |= CHANGE_NO_DEST_DIR;
2729 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2731 else if (!access_file(dest_dir, W_OK))
2733 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2734 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2738 if (isfile(fd->change->dest))
2740 if (!access_file(fd->change->dest, W_OK))
2742 ret |= CHANGE_NO_WRITE_PERM_DEST;
2743 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2747 ret |= CHANGE_WARN_DEST_EXISTS;
2748 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2751 else if (isdir(fd->change->dest))
2753 ret |= CHANGE_DEST_EXISTS;
2754 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2761 /* During a rename operation, check if another planned destination file has
2764 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2765 fd->change->type == FILEDATA_CHANGE_COPY ||
2766 fd->change->type == FILEDATA_CHANGE_MOVE)
2771 fd1 = static_cast<FileData *>(work->data);
2773 if (fd1 != nullptr && fd != fd1 )
2775 if (!strcmp(fd->change->dest, fd1->change->dest))
2777 ret |= CHANGE_DUPLICATE_DEST;
2783 fd->change->error = ret;
2784 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2791 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2796 ret = file_data_verify_ci(fd, list);
2798 work = fd->sidecar_files;
2801 auto sfd = static_cast<FileData *>(work->data);
2803 ret |= file_data_verify_ci(sfd, list);
2810 gchar *file_data_get_error_string(gint error)
2812 GString *result = g_string_new("");
2814 if (error & CHANGE_NO_SRC)
2816 if (result->len > 0) g_string_append(result, ", ");
2817 g_string_append(result, _("file or directory does not exist"));
2820 if (error & CHANGE_DEST_EXISTS)
2822 if (result->len > 0) g_string_append(result, ", ");
2823 g_string_append(result, _("destination already exists"));
2826 if (error & CHANGE_NO_WRITE_PERM_DEST)
2828 if (result->len > 0) g_string_append(result, ", ");
2829 g_string_append(result, _("destination can't be overwritten"));
2832 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2834 if (result->len > 0) g_string_append(result, ", ");
2835 g_string_append(result, _("destination directory is not writable"));
2838 if (error & CHANGE_NO_DEST_DIR)
2840 if (result->len > 0) g_string_append(result, ", ");
2841 g_string_append(result, _("destination directory does not exist"));
2844 if (error & CHANGE_NO_WRITE_PERM_DIR)
2846 if (result->len > 0) g_string_append(result, ", ");
2847 g_string_append(result, _("source directory is not writable"));
2850 if (error & CHANGE_NO_READ_PERM)
2852 if (result->len > 0) g_string_append(result, ", ");
2853 g_string_append(result, _("no read permission"));
2856 if (error & CHANGE_WARN_NO_WRITE_PERM)
2858 if (result->len > 0) g_string_append(result, ", ");
2859 g_string_append(result, _("file is readonly"));
2862 if (error & CHANGE_WARN_DEST_EXISTS)
2864 if (result->len > 0) g_string_append(result, ", ");
2865 g_string_append(result, _("destination already exists and will be overwritten"));
2868 if (error & CHANGE_WARN_SAME)
2870 if (result->len > 0) g_string_append(result, ", ");
2871 g_string_append(result, _("source and destination are the same"));
2874 if (error & CHANGE_WARN_CHANGED_EXT)
2876 if (result->len > 0) g_string_append(result, ", ");
2877 g_string_append(result, _("source and destination have different extension"));
2880 if (error & CHANGE_WARN_UNSAVED_META)
2882 if (result->len > 0) g_string_append(result, ", ");
2883 g_string_append(result, _("there are unsaved metadata changes for the file"));
2886 if (error & CHANGE_DUPLICATE_DEST)
2888 if (result->len > 0) g_string_append(result, ", ");
2889 g_string_append(result, _("another destination file has the same filename"));
2892 return g_string_free(result, FALSE);
2895 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2898 gint all_errors = 0;
2899 gint common_errors = ~0;
2904 if (!list) return 0;
2906 num = g_list_length(list);
2907 errors = g_new(int, num);
2915 fd = static_cast<FileData *>(work->data);
2918 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2919 all_errors |= error;
2920 common_errors &= error;
2927 if (desc && all_errors)
2930 GString *result = g_string_new("");
2934 gchar *str = file_data_get_error_string(common_errors);
2935 g_string_append(result, str);
2936 g_string_append(result, "\n");
2947 fd = static_cast<FileData *>(work->data);
2950 error = errors[i] & ~common_errors;
2954 gchar *str = file_data_get_error_string(error);
2955 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2960 *desc = g_string_free(result, FALSE);
2969 * perform the change described by FileFataChangeInfo
2970 * it is used for internal operations,
2971 * this function actually operates with files on the filesystem
2972 * it should implement safe delete
2975 static gboolean file_data_perform_move(FileData *fd)
2977 g_assert(!strcmp(fd->change->source, fd->path));
2978 return move_file(fd->change->source, fd->change->dest);
2981 static gboolean file_data_perform_copy(FileData *fd)
2983 g_assert(!strcmp(fd->change->source, fd->path));
2984 return copy_file(fd->change->source, fd->change->dest);
2987 static gboolean file_data_perform_delete(FileData *fd)
2989 if (isdir(fd->path) && !islink(fd->path))
2990 return rmdir_utf8(fd->path);
2992 if (options->file_ops.safe_delete_enable)
2993 return file_util_safe_unlink(fd->path);
2995 return unlink_file(fd->path);
2998 gboolean file_data_perform_ci(FileData *fd)
3000 /** @FIXME When a directory that is a symbolic link is deleted,
3001 * at this point fd->change is null because no FileDataChangeInfo
3002 * has been set up. Therefore there is a seg. fault.
3003 * This code simply aborts the delete.
3010 FileDataChangeType type = fd->change->type;
3014 case FILEDATA_CHANGE_MOVE:
3015 return file_data_perform_move(fd);
3016 case FILEDATA_CHANGE_COPY:
3017 return file_data_perform_copy(fd);
3018 case FILEDATA_CHANGE_RENAME:
3019 return file_data_perform_move(fd); /* the same as move */
3020 case FILEDATA_CHANGE_DELETE:
3021 return file_data_perform_delete(fd);
3022 case FILEDATA_CHANGE_WRITE_METADATA:
3023 return metadata_write_perform(fd);
3024 case FILEDATA_CHANGE_UNSPECIFIED:
3025 /* nothing to do here */
3033 gboolean file_data_sc_perform_ci(FileData *fd)
3036 gboolean ret = TRUE;
3037 FileDataChangeType type = fd->change->type;
3039 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3041 work = fd->sidecar_files;
3044 auto sfd = static_cast<FileData *>(work->data);
3046 if (!file_data_perform_ci(sfd)) ret = FALSE;
3050 if (!file_data_perform_ci(fd)) ret = FALSE;
3056 * updates FileData structure according to FileDataChangeInfo
3059 gboolean file_data_apply_ci(FileData *fd)
3061 FileDataChangeType type = fd->change->type;
3063 /** @FIXME delete ?*/
3064 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
3066 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
3067 file_data_planned_change_remove(fd);
3069 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
3071 /* this change overwrites another file which is already known to other modules
3072 renaming fd would create duplicate FileData structure
3073 the best thing we can do is nothing
3075 /** @FIXME maybe we could copy stuff like marks
3077 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
3081 file_data_set_path(fd, fd->change->dest);
3084 file_data_increment_version(fd);
3085 file_data_send_notification(fd, NOTIFY_CHANGE);
3090 gboolean file_data_sc_apply_ci(FileData *fd)
3093 FileDataChangeType type = fd->change->type;
3095 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3097 work = fd->sidecar_files;
3100 auto sfd = static_cast<FileData *>(work->data);
3102 file_data_apply_ci(sfd);
3106 file_data_apply_ci(fd);
3111 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
3114 if (fd->parent) fd = fd->parent;
3115 if (!g_list_find(list, fd)) return FALSE;
3117 work = fd->sidecar_files;
3120 if (!g_list_find(list, work->data)) return FALSE;
3126 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
3128 GList *out = nullptr;
3131 /* change partial groups to independent files */
3136 auto fd = static_cast<FileData *>(work->data);
3139 if (!file_data_list_contains_whole_group(list, fd))
3141 file_data_disable_grouping(fd, TRUE);
3144 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
3150 /* remove sidecars from the list,
3151 they can be still accessed via main_fd->sidecar_files */
3155 auto fd = static_cast<FileData *>(work->data);
3159 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
3161 out = g_list_prepend(out, file_data_ref(fd));
3165 filelist_free(list);
3166 out = g_list_reverse(out);
3176 * notify other modules about the change described by FileDataChangeInfo
3179 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks */
3180 /** @FIXME do we need the ignore_list? It looks like a workaround for ineffective
3181 implementation in view-file-list.cc */
3184 struct NotifyIdleData {
3191 FileDataNotifyFunc func;
3193 NotifyPriority priority;
3196 static GList *notify_func_list = nullptr;
3198 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
3200 auto nda = static_cast<const NotifyData *>(a);
3201 auto ndb = static_cast<const NotifyData *>(b);
3203 if (nda->priority < ndb->priority) return -1;
3204 if (nda->priority > ndb->priority) return 1;
3208 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
3211 GList *work = notify_func_list;
3215 auto nd = static_cast<NotifyData *>(work->data);
3217 if (nd->func == func && nd->data == data)
3219 g_warning("Notify func already registered");
3225 nd = g_new(NotifyData, 1);
3228 nd->priority = priority;
3230 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
3231 DEBUG_2("Notify func registered: %p", (void *)nd);
3236 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
3238 GList *work = notify_func_list;
3242 auto nd = static_cast<NotifyData *>(work->data);
3244 if (nd->func == func && nd->data == data)
3246 notify_func_list = g_list_delete_link(notify_func_list, work);
3247 DEBUG_2("Notify func unregistered: %p", (void *)nd);
3254 g_warning("Notify func not found");
3258 #pragma GCC diagnostic push
3259 #pragma GCC diagnostic ignored "-Wunused-function"
3260 gboolean file_data_send_notification_idle_cb_unused(gpointer data)
3262 auto *nid = (NotifyIdleData *)data;
3263 GList *work = notify_func_list;
3267 auto *nd = (NotifyData *)work->data;
3269 nd->func(nid->fd, nid->type, nd->data);
3272 file_data_unref(nid->fd);
3276 #pragma GCC diagnostic pop
3278 void file_data_send_notification(FileData *fd, NotifyType type)
3280 GList *work = notify_func_list;
3284 auto nd = static_cast<NotifyData *>(work->data);
3286 nd->func(fd, type, nd->data);
3290 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3291 nid->fd = file_data_ref(fd);
3293 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3297 static GHashTable *file_data_monitor_pool = nullptr;
3298 static guint realtime_monitor_id = 0; /* event source id */
3300 static void realtime_monitor_check_cb(gpointer key, gpointer, gpointer)
3302 auto fd = static_cast<FileData *>(key);
3304 file_data_check_changed_files(fd);
3306 DEBUG_1("monitor %s", fd->path);
3309 static gboolean realtime_monitor_cb(gpointer)
3311 if (!options->update_on_time_change) return TRUE;
3312 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, nullptr);
3316 gboolean file_data_register_real_time_monitor(FileData *fd)
3322 if (!file_data_monitor_pool)
3323 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3325 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3327 DEBUG_1("Register realtime %d %s", count, fd->path);
3330 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3332 if (!realtime_monitor_id)
3334 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, nullptr);
3340 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3344 g_assert(file_data_monitor_pool);
3346 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3348 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3350 g_assert(count > 0);
3355 g_hash_table_remove(file_data_monitor_pool, fd);
3357 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3359 file_data_unref(fd);
3361 if (g_hash_table_size(file_data_monitor_pool) == 0)
3363 g_source_remove(realtime_monitor_id);
3364 realtime_monitor_id = 0;
3372 *-----------------------------------------------------------------------------
3373 * Saving marks list, clearing marks
3374 * Uses file_data_pool
3375 *-----------------------------------------------------------------------------
3378 static void marks_get_files(gpointer key, gpointer value, gpointer userdata)
3380 auto file_name = static_cast<gchar *>(key);
3381 auto result = static_cast<GString *>(userdata);
3384 if (isfile(file_name))
3386 fd = static_cast<FileData *>(value);
3387 if (fd && fd->marks > 0)
3389 g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks);
3394 gboolean marks_list_load(const gchar *path)
3402 pathl = path_from_utf8(path);
3403 f = fopen(pathl, "r");
3405 if (!f) return FALSE;
3407 /* first line must start with Marks comment */
3408 if (!fgets(s_buf, sizeof(s_buf), f) ||
3409 strncmp(s_buf, "#Marks", 6) != 0)
3415 while (fgets(s_buf, sizeof(s_buf), f))
3417 if (s_buf[0]=='#') continue;
3418 file_path = strtok(s_buf, ",");
3419 marks_value = strtok(nullptr, ",");
3420 if (isfile(file_path))
3422 FileData *fd = file_data_new_no_grouping(file_path);
3427 gint mark_no = 1 << n;
3428 if (atoi(marks_value) & mark_no)
3430 file_data_set_mark(fd, n , 1);
3441 gboolean marks_list_save(gchar *path, gboolean save)
3443 SecureSaveInfo *ssi;
3446 pathl = path_from_utf8(path);
3447 ssi = secure_open(pathl);
3451 log_printf(_("Error: Unable to write marks lists to: %s\n"), path);
3455 secure_fprintf(ssi, "#Marks lists\n");
3457 GString *marks = g_string_new("");
3460 g_hash_table_foreach(file_data_pool, marks_get_files, marks);
3462 secure_fprintf(ssi, "%s", marks->str);
3463 g_string_free(marks, TRUE);
3465 secure_fprintf(ssi, "#end\n");
3466 return (secure_close(ssi) == 0);
3469 static void marks_clear(gpointer key, gpointer value, gpointer)
3471 auto file_name = static_cast<gchar *>(key);
3476 if (isfile(file_name))
3478 fd = static_cast<FileData *>(value);
3479 if (fd && fd->marks > 0)
3485 if (fd->marks & mark_no)
3487 file_data_set_mark(fd, n , 0);
3495 void marks_clear_all()
3497 g_hash_table_foreach(file_data_pool, marks_clear, nullptr);
3500 void file_data_set_page_num(FileData *fd, gint page_num)
3502 if (fd->page_total > 1 && page_num < 0)
3504 fd->page_num = fd->page_total - 1;
3506 else if (fd->page_total > 1 && page_num <= fd->page_total)
3508 fd->page_num = page_num - 1;
3514 file_data_send_notification(fd, NOTIFY_REREAD);
3517 void file_data_inc_page_num(FileData *fd)
3519 if (fd->page_total > 0 && fd->page_num < fd->page_total - 1)
3521 fd->page_num = fd->page_num + 1;
3523 else if (fd->page_total == 0)
3525 fd->page_num = fd->page_num + 1;
3527 file_data_send_notification(fd, NOTIFY_REREAD);
3530 void file_data_dec_page_num(FileData *fd)
3532 if (fd->page_num > 0)
3534 fd->page_num = fd->page_num - 1;
3536 file_data_send_notification(fd, NOTIFY_REREAD);
3539 void file_data_set_page_total(FileData *fd, gint page_total)
3541 fd->page_total = page_total;
3544 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */