2 * Copyright (C) 2006 John Ellis
3 * Copyright (C) 2008 - 2016 The Geeqie Team
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 #include "filefilter.h"
27 #include "thumb-standard.h"
28 #include "ui-fileops.h"
31 #include "histogram.h"
32 #include "secure-save.h"
40 gint global_file_data_count = 0;
43 static GHashTable *file_data_pool = nullptr;
44 static GHashTable *file_data_planned_change_hash = nullptr;
46 static gint sidecar_file_priority(const gchar *extension);
47 static void file_data_check_sidecars(const GList *basename_list);
48 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
51 static SortType filelist_sort_method = SORT_NONE;
52 static gboolean filelist_sort_ascend = TRUE;
55 *-----------------------------------------------------------------------------
56 * text conversion utils
57 *-----------------------------------------------------------------------------
60 gchar *text_from_size(gint64 size)
66 /* what I would like to use is printf("%'d", size)
67 * BUT: not supported on every libc :(
71 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
72 a = g_strdup_printf("%d%09d", static_cast<guint>(size / 1000000000), static_cast<guint>(size % 1000000000));
76 a = g_strdup_printf("%d", static_cast<guint>(size));
82 b = g_new(gchar, l + n + 1);
107 gchar *text_from_size_abrev(gint64 size)
109 if (size < static_cast<gint64>(1024))
111 return g_strdup_printf(_("%d bytes"), static_cast<gint>(size));
113 if (size < static_cast<gint64>(1048576))
115 return g_strdup_printf(_("%.1f KiB"), static_cast<gdouble>(size) / 1024.0);
117 if (size < static_cast<gint64>(1073741824))
119 return g_strdup_printf(_("%.1f MiB"), static_cast<gdouble>(size) / 1048576.0);
122 /* to avoid overflowing the gdouble, do division in two steps */
124 return g_strdup_printf(_("%.1f GiB"), static_cast<gdouble>(size) / 1024.0);
127 /* note: returned string is valid until next call to text_from_time() */
128 const gchar *text_from_time(time_t t)
130 static gchar *ret = nullptr;
134 GError *error = nullptr;
136 btime = localtime(&t);
138 /* the %x warning about 2 digit years is not an error */
139 buflen = strftime(buf, sizeof(buf), "%x %X", btime);
140 if (buflen < 1) return "";
143 ret = g_locale_to_utf8(buf, buflen, nullptr, nullptr, &error);
146 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
155 *-----------------------------------------------------------------------------
156 * changed files detection and notification
157 *-----------------------------------------------------------------------------
160 void file_data_increment_version(FileData *fd)
166 fd->parent->version++;
167 fd->parent->valid_marks = 0;
171 static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st)
173 if (fd->size != st->st_size ||
174 fd->date != st->st_mtime)
176 fd->size = st->st_size;
177 fd->date = st->st_mtime;
178 fd->cdate = st->st_ctime;
179 fd->mode = st->st_mode;
180 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
181 fd->thumb_pixbuf = nullptr;
182 file_data_increment_version(fd);
183 file_data_send_notification(fd, NOTIFY_REREAD);
189 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
191 gboolean ret = FALSE;
194 ret = file_data_check_changed_single_file(fd, st);
196 work = fd->sidecar_files;
199 auto sfd = static_cast<FileData *>(work->data);
203 if (!stat_utf8(sfd->path, &st))
208 file_data_disconnect_sidecar_file(fd, sfd);
210 file_data_increment_version(sfd);
211 file_data_send_notification(sfd, NOTIFY_REREAD);
212 file_data_unref(sfd);
216 ret |= file_data_check_changed_files_recursive(sfd, &st);
222 gboolean file_data_check_changed_files(FileData *fd)
224 gboolean ret = FALSE;
227 if (fd->parent) fd = fd->parent;
229 if (!stat_utf8(fd->path, &st))
233 FileData *sfd = nullptr;
235 /* parent is missing, we have to rebuild whole group */
240 /* file_data_disconnect_sidecar_file might delete the file,
241 we have to keep the reference to prevent this */
242 sidecars = filelist_copy(fd->sidecar_files);
247 sfd = static_cast<FileData *>(work->data);
250 file_data_disconnect_sidecar_file(fd, sfd);
252 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
253 /* now we can release the sidecars */
254 filelist_free(sidecars);
255 file_data_increment_version(fd);
256 file_data_send_notification(fd, NOTIFY_REREAD);
261 ret |= file_data_check_changed_files_recursive(fd, &st);
268 *-----------------------------------------------------------------------------
269 * file name, extension, sorting, ...
270 *-----------------------------------------------------------------------------
273 static void file_data_set_collate_keys(FileData *fd)
275 gchar *caseless_name;
278 valid_name = g_filename_display_name(fd->name);
279 caseless_name = g_utf8_casefold(valid_name, -1);
281 g_free(fd->collate_key_name);
282 g_free(fd->collate_key_name_nocase);
284 if (options->file_sort.natural)
286 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
287 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
291 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
292 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
296 g_free(caseless_name);
299 static void file_data_set_path(FileData *fd, const gchar *path)
301 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
302 g_assert(file_data_pool);
306 if (fd->original_path)
308 g_hash_table_remove(file_data_pool, fd->original_path);
309 g_free(fd->original_path);
312 g_assert(!g_hash_table_lookup(file_data_pool, path));
314 fd->original_path = g_strdup(path);
315 g_hash_table_insert(file_data_pool, fd->original_path, fd);
317 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
319 fd->path = g_strdup(path);
321 fd->extension = fd->name + 1;
322 file_data_set_collate_keys(fd);
326 fd->path = g_strdup(path);
327 fd->name = filename_from_path(fd->path);
329 if (strcmp(fd->name, "..") == 0)
331 gchar *dir = remove_level_from_path(path);
333 fd->path = remove_level_from_path(dir);
336 fd->extension = fd->name + 2;
337 file_data_set_collate_keys(fd);
340 else if (strcmp(fd->name, ".") == 0)
343 fd->path = remove_level_from_path(path);
345 fd->extension = fd->name + 1;
346 file_data_set_collate_keys(fd);
350 fd->extension = registered_extension_from_path(fd->path);
351 if (fd->extension == nullptr)
353 fd->extension = fd->name + strlen(fd->name);
356 fd->sidecar_priority = sidecar_file_priority(fd->extension);
357 file_data_set_collate_keys(fd);
361 *-----------------------------------------------------------------------------
362 * create or reuse Filedata
363 *-----------------------------------------------------------------------------
366 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
372 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
374 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
377 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
379 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
385 if (!fd && file_data_planned_change_hash)
387 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_planned_change_hash, path_utf8));
390 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
391 if (!isfile(fd->path))
394 file_data_apply_ci(fd);
407 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
410 changed = file_data_check_changed_single_file(fd, st);
412 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
417 fd = g_new0(FileData, 1);
418 #ifdef DEBUG_FILEDATA
419 global_file_data_count++;
420 DEBUG_2("file data count++: %d", global_file_data_count);
423 fd->size = st->st_size;
424 fd->date = st->st_mtime;
425 fd->cdate = st->st_ctime;
426 fd->mode = st->st_mode;
428 fd->magick = FD_MAGICK;
430 fd->rating = STAR_RATING_NOT_READ;
431 fd->format_class = filter_file_get_class(path_utf8);
435 user = getpwuid(st->st_uid);
438 fd->owner = g_strdup_printf("%u", st->st_uid);
442 fd->owner = g_strdup(user->pw_name);
445 group = getgrgid(st->st_gid);
448 fd->group = g_strdup_printf("%u", st->st_gid);
452 fd->group = g_strdup(group->gr_name);
455 fd->sym_link = get_symbolic_link(path_utf8);
457 if (disable_sidecars) fd->disable_grouping = TRUE;
459 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
464 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
466 gchar *path_utf8 = path_to_utf8(path);
467 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
473 FileData *file_data_new_simple(const gchar *path_utf8)
478 if (!stat_utf8(path_utf8, &st))
484 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
485 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
494 void read_exif_time_data(FileData *file)
496 if (file->exifdate > 0)
498 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
509 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
510 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), (void *)file, file->path);
515 uint year, month, day, hour, min, sec;
517 sscanf(tmp, "%4u:%2u:%2u %2u:%2u:%2u", &year, &month, &day, &hour, &min, &sec);
518 time_str.tm_year = year - 1900;
519 time_str.tm_mon = month - 1;
520 time_str.tm_mday = day;
521 time_str.tm_hour = hour;
522 time_str.tm_min = min;
523 time_str.tm_sec = sec;
524 time_str.tm_isdst = 0;
526 file->exifdate = mktime(&time_str);
532 void read_exif_time_digitized_data(FileData *file)
534 if (file->exifdate_digitized > 0)
536 DEBUG_1("%s set_exif_time_digitized_data: Already exists for %s", get_exec_time(), file->path);
547 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeDigitized");
548 DEBUG_2("%s set_exif_time_digitized_data: reading %p %s", get_exec_time(), (void *)file, file->path);
553 uint year, month, day, hour, min, sec;
555 sscanf(tmp, "%4u:%2u:%2u %2u:%2u:%2u", &year, &month, &day, &hour, &min, &sec);
556 time_str.tm_year = year - 1900;
557 time_str.tm_mon = month - 1;
558 time_str.tm_mday = day;
559 time_str.tm_hour = hour;
560 time_str.tm_min = min;
561 time_str.tm_sec = sec;
562 time_str.tm_isdst = 0;
564 file->exifdate_digitized = mktime(&time_str);
570 void read_rating_data(FileData *file)
574 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
577 file->rating = atoi(rating_str);
586 #pragma GCC diagnostic push
587 #pragma GCC diagnostic ignored "-Wunused-function"
588 void set_exif_time_data_unused(GList *files)
590 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
594 FileData *file = static_cast<FileData *>(files->data);
596 read_exif_time_data(file);
601 void set_exif_time_digitized_data_unused(GList *files)
603 DEBUG_1("%s set_exif_time_digitized_data: ...", get_exec_time());
607 FileData *file = static_cast<FileData *>(files->data);
609 read_exif_time_digitized_data(file);
614 void set_rating_data_unused(GList *files)
617 DEBUG_1("%s set_rating_data: ...", get_exec_time());
621 FileData *file = static_cast<FileData *>(files->data);
622 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
625 file->rating = atoi(rating_str);
631 #pragma GCC diagnostic pop
633 FileData *file_data_new_no_grouping(const gchar *path_utf8)
637 if (!stat_utf8(path_utf8, &st))
643 return file_data_new(path_utf8, &st, TRUE);
646 FileData *file_data_new_dir(const gchar *path_utf8)
650 if (!stat_utf8(path_utf8, &st))
656 /* dir or non-existing yet */
657 g_assert(S_ISDIR(st.st_mode));
659 return file_data_new(path_utf8, &st, TRUE);
663 *-----------------------------------------------------------------------------
665 *-----------------------------------------------------------------------------
668 #ifdef DEBUG_FILEDATA
669 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
671 FileData *file_data_ref(FileData *fd)
674 if (fd == nullptr) return nullptr;
675 if (fd->magick != FD_MAGICK)
676 #ifdef DEBUG_FILEDATA
677 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, (void *)fd);
679 log_printf("Error: fd magick mismatch fd=%p", fd);
681 g_assert(fd->magick == FD_MAGICK);
684 #ifdef DEBUG_FILEDATA
685 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->path, file, line);
687 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
693 * @brief Print ref. count and image name
696 * Print image ref. count and full path name of all images in
697 * the file_data_pool.
699 * Used only by DEBUG_FD()
701 void file_data_dump()
703 #ifdef DEBUG_FILEDATA
709 list = g_hash_table_get_values(file_data_pool);
711 log_printf("%d", global_file_data_count);
712 log_printf("%d", g_list_length(list));
716 fd = static_cast<FileData *>(list->data);
717 log_printf("%-4d %s", fd->ref, fd->path);
726 static void file_data_free(FileData *fd)
728 g_assert(fd->magick == FD_MAGICK);
729 g_assert(fd->ref == 0);
730 g_assert(!fd->locked);
732 #ifdef DEBUG_FILEDATA
733 global_file_data_count--;
734 DEBUG_2("file data count--: %d", global_file_data_count);
737 metadata_cache_free(fd);
738 g_hash_table_remove(file_data_pool, fd->original_path);
741 g_free(fd->original_path);
742 g_free(fd->collate_key_name);
743 g_free(fd->collate_key_name_nocase);
744 g_free(fd->extended_extension);
745 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
746 histmap_free(fd->histmap);
749 g_free(fd->sym_link);
750 g_free(fd->format_name);
751 g_assert(fd->sidecar_files == nullptr); /* sidecar files must be freed before calling this */
753 file_data_change_info_free(nullptr, fd);
758 * @brief Checks if the FileData is referenced
760 * Checks the refcount and whether the FileData is locked.
762 static gboolean file_data_check_has_ref(FileData *fd)
764 return fd->ref > 0 || fd->locked;
768 * @brief Consider freeing a FileData.
770 * This function will free a FileData and its children provided that neither its parent nor it has
771 * a positive refcount, and provided that neither is locked.
773 static void file_data_consider_free(FileData *fd)
776 FileData *parent = fd->parent ? fd->parent : fd;
778 g_assert(fd->magick == FD_MAGICK);
779 if (file_data_check_has_ref(fd)) return;
780 if (file_data_check_has_ref(parent)) return;
782 work = parent->sidecar_files;
785 auto sfd = static_cast<FileData *>(work->data);
786 if (file_data_check_has_ref(sfd)) return;
790 /* Neither the parent nor the siblings are referenced, so we can free everything */
791 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
792 fd->path, fd->parent ? parent->path : "-");
794 work = parent->sidecar_files;
797 auto sfd = static_cast<FileData *>(work->data);
802 g_list_free(parent->sidecar_files);
803 parent->sidecar_files = nullptr;
805 file_data_free(parent);
808 #ifdef DEBUG_FILEDATA
809 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
811 void file_data_unref(FileData *fd)
814 if (fd == nullptr) return;
815 if (fd->magick != FD_MAGICK)
816 #ifdef DEBUG_FILEDATA
817 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, (void *)fd);
819 log_printf("Error: fd magick mismatch fd=%p", fd);
821 g_assert(fd->magick == FD_MAGICK);
824 #ifdef DEBUG_FILEDATA
825 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->locked, fd->path,
828 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
831 // Free FileData if it's no longer ref'd
832 file_data_consider_free(fd);
836 * @brief Lock the FileData in memory.
838 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
839 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
840 * even if the code would continue to function properly even if the FileData were freed. Code that
841 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
843 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
844 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
846 void file_data_lock(FileData *fd)
848 if (fd == nullptr) return;
849 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
851 g_assert(fd->magick == FD_MAGICK);
854 DEBUG_2("file_data_ref fd=%p (%d): '%s'", (void *)fd, fd->ref, fd->path);
858 * @brief Reset the maintain-FileData-in-memory lock
860 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
861 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
862 * keeping it from being freed.
864 void file_data_unlock(FileData *fd)
866 if (fd == nullptr) return;
867 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
869 g_assert(fd->magick == FD_MAGICK);
872 // Free FileData if it's no longer ref'd
873 file_data_consider_free(fd);
877 * @brief Lock all of the FileDatas in the provided list
879 * @see file_data_lock(#FileData)
881 void file_data_lock_list(GList *list)
888 auto fd = static_cast<FileData *>(work->data);
895 * @brief Unlock all of the FileDatas in the provided list
897 * @see #file_data_unlock(#FileData)
899 void file_data_unlock_list(GList *list)
906 auto fd = static_cast<FileData *>(work->data);
908 file_data_unlock(fd);
913 *-----------------------------------------------------------------------------
914 * sidecar file info struct
915 *-----------------------------------------------------------------------------
918 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
920 auto fda = static_cast<const FileData *>(a);
921 auto fdb = static_cast<const FileData *>(b);
923 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
924 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
926 return strcmp(fdb->extension, fda->extension);
930 static gint sidecar_file_priority(const gchar *extension)
935 if (extension == nullptr)
938 work = sidecar_ext_get_list();
941 auto ext = static_cast<gchar *>(work->data);
944 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
950 static void file_data_check_sidecars(const GList *basename_list)
952 /* basename_list contains the new group - first is the parent, then sorted sidecars */
953 /* all files in the list have ref count > 0 */
956 GList *s_work, *new_sidecars;
959 if (!basename_list) return;
962 DEBUG_2("basename start");
963 work = basename_list;
966 auto fd = static_cast<FileData *>(work->data);
968 g_assert(fd->magick == FD_MAGICK);
969 DEBUG_2("basename: %p %s", (void *)fd, fd->name);
972 g_assert(fd->parent->magick == FD_MAGICK);
973 DEBUG_2(" parent: %p", (void *)fd->parent);
975 s_work = fd->sidecar_files;
978 auto sfd = static_cast<FileData *>(s_work->data);
979 s_work = s_work->next;
980 g_assert(sfd->magick == FD_MAGICK);
981 DEBUG_2(" sidecar: %p %s", (void *)sfd, sfd->name);
984 g_assert(fd->parent == nullptr || fd->sidecar_files == nullptr);
987 parent_fd = static_cast<FileData *>(basename_list->data);
989 /* check if the second and next entries of basename_list are already connected
990 as sidecars of the first entry (parent_fd) */
991 work = basename_list->next;
992 s_work = parent_fd->sidecar_files;
994 while (work && s_work)
996 if (work->data != s_work->data) break;
998 s_work = s_work->next;
1001 if (!work && !s_work)
1003 DEBUG_2("basename no change");
1004 return; /* no change in grouping */
1007 /* we have to regroup it */
1009 /* first, disconnect everything and send notification*/
1011 work = basename_list;
1014 auto fd = static_cast<FileData *>(work->data);
1016 g_assert(fd->parent == nullptr || fd->sidecar_files == nullptr);
1020 FileData *old_parent = fd->parent;
1021 g_assert(old_parent->parent == nullptr || old_parent->sidecar_files == nullptr);
1022 file_data_ref(old_parent);
1023 file_data_disconnect_sidecar_file(old_parent, fd);
1024 file_data_send_notification(old_parent, NOTIFY_REREAD);
1025 file_data_unref(old_parent);
1028 while (fd->sidecar_files)
1030 auto sfd = static_cast<FileData *>(fd->sidecar_files->data);
1031 g_assert(sfd->parent == nullptr || sfd->sidecar_files == nullptr);
1033 file_data_disconnect_sidecar_file(fd, sfd);
1034 file_data_send_notification(sfd, NOTIFY_REREAD);
1035 file_data_unref(sfd);
1037 file_data_send_notification(fd, NOTIFY_GROUPING);
1039 g_assert(fd->parent == nullptr && fd->sidecar_files == nullptr);
1042 /* now we can form the new group */
1043 work = basename_list->next;
1044 new_sidecars = nullptr;
1047 auto sfd = static_cast<FileData *>(work->data);
1048 g_assert(sfd->magick == FD_MAGICK);
1049 g_assert(sfd->parent == nullptr && sfd->sidecar_files == nullptr);
1050 sfd->parent = parent_fd;
1051 new_sidecars = g_list_prepend(new_sidecars, sfd);
1054 g_assert(parent_fd->sidecar_files == nullptr);
1055 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
1056 DEBUG_1("basename group changed for %s", parent_fd->path);
1060 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
1062 g_assert(target->magick == FD_MAGICK);
1063 g_assert(sfd->magick == FD_MAGICK);
1064 g_assert(g_list_find(target->sidecar_files, sfd));
1066 file_data_ref(target);
1069 g_assert(sfd->parent == target);
1071 file_data_increment_version(sfd); /* increments both sfd and target */
1073 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
1074 sfd->parent = nullptr;
1075 g_free(sfd->extended_extension);
1076 sfd->extended_extension = nullptr;
1078 file_data_unref(target);
1079 file_data_unref(sfd);
1082 /* disables / enables grouping for particular file, sends UPDATE notification */
1083 void file_data_disable_grouping(FileData *fd, gboolean disable)
1085 if (!fd->disable_grouping == !disable) return;
1087 fd->disable_grouping = !!disable;
1093 FileData *parent = file_data_ref(fd->parent);
1094 file_data_disconnect_sidecar_file(parent, fd);
1095 file_data_send_notification(parent, NOTIFY_GROUPING);
1096 file_data_unref(parent);
1098 else if (fd->sidecar_files)
1100 GList *sidecar_files = filelist_copy(fd->sidecar_files);
1101 GList *work = sidecar_files;
1104 auto sfd = static_cast<FileData *>(work->data);
1106 file_data_disconnect_sidecar_file(fd, sfd);
1107 file_data_send_notification(sfd, NOTIFY_GROUPING);
1109 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
1110 filelist_free(sidecar_files);
1114 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
1119 file_data_increment_version(fd);
1120 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
1122 file_data_send_notification(fd, NOTIFY_GROUPING);
1125 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
1132 auto fd = static_cast<FileData *>(work->data);
1134 file_data_disable_grouping(fd, disable);
1142 *-----------------------------------------------------------------------------
1144 *-----------------------------------------------------------------------------
1148 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
1151 if (!filelist_sort_ascend)
1158 switch (filelist_sort_method)
1163 if (fa->size < fb->size) return -1;
1164 if (fa->size > fb->size) return 1;
1165 /* fall back to name */
1168 if (fa->date < fb->date) return -1;
1169 if (fa->date > fb->date) return 1;
1170 /* fall back to name */
1173 if (fa->cdate < fb->cdate) return -1;
1174 if (fa->cdate > fb->cdate) return 1;
1175 /* fall back to name */
1178 if (fa->exifdate < fb->exifdate) return -1;
1179 if (fa->exifdate > fb->exifdate) return 1;
1180 /* fall back to name */
1182 case SORT_EXIFTIMEDIGITIZED:
1183 if (fa->exifdate_digitized < fb->exifdate_digitized) return -1;
1184 if (fa->exifdate_digitized > fb->exifdate_digitized) return 1;
1185 /* fall back to name */
1188 if (fa->rating < fb->rating) return -1;
1189 if (fa->rating > fb->rating) return 1;
1190 /* fall back to name */
1193 if (fa->format_class < fb->format_class) return -1;
1194 if (fa->format_class > fb->format_class) return 1;
1195 /* fall back to name */
1201 if (options->file_sort.case_sensitive)
1202 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1204 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1206 if (ret != 0) return ret;
1208 /* do not return 0 unless the files are really the same
1209 file_data_pool ensures that original_path is unique
1211 return strcmp(fa->original_path, fb->original_path);
1214 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1216 filelist_sort_method = method;
1217 filelist_sort_ascend = ascend;
1218 return filelist_sort_compare_filedata(fa, fb);
1221 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1223 return filelist_sort_compare_filedata(static_cast<FileData *>(a), static_cast<FileData *>(b));
1226 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1228 filelist_sort_method = method;
1229 filelist_sort_ascend = ascend;
1230 return g_list_sort(list, cb);
1233 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1235 filelist_sort_method = method;
1236 filelist_sort_ascend = ascend;
1237 return g_list_insert_sorted(list, data, cb);
1240 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1242 return filelist_sort_full(list, method, ascend, reinterpret_cast<GCompareFunc>(filelist_sort_file_cb));
1245 #pragma GCC diagnostic push
1246 #pragma GCC diagnostic ignored "-Wunused-function"
1247 GList *filelist_insert_sort_unused(GList *list, FileData *fd, SortType method, gboolean ascend)
1249 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1251 #pragma GCC diagnostic pop
1254 *-----------------------------------------------------------------------------
1255 * basename hash - grouping of sidecars in filelist
1256 *-----------------------------------------------------------------------------
1260 static GHashTable *file_data_basename_hash_new()
1262 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, nullptr);
1265 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1268 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1270 list = static_cast<GList *>(g_hash_table_lookup(basename_hash, basename));
1274 DEBUG_1("TG: basename_hash not found for %s",fd->path);
1275 const gchar *parent_extension = registered_extension_from_path(basename);
1277 if (parent_extension)
1279 DEBUG_1("TG: parent extension %s",parent_extension);
1280 gchar *parent_basename = g_strndup(basename, parent_extension - basename);
1281 DEBUG_1("TG: parent basename %s",parent_basename);
1282 auto parent_fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, basename));
1285 DEBUG_1("TG: parent fd found");
1286 list = static_cast<GList *>(g_hash_table_lookup(basename_hash, parent_basename));
1287 if (!g_list_find(list, parent_fd))
1289 DEBUG_1("TG: parent fd doesn't fit");
1290 g_free(parent_basename);
1296 basename = parent_basename;
1297 fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
1303 if (!g_list_find(list, fd))
1305 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1306 g_hash_table_insert(basename_hash, basename, list);
1315 static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
1317 file_data_basename_hash_insert(static_cast<GHashTable *>(basename_hash), static_cast<FileData *>(fd));
1320 static void file_data_basename_hash_remove_list(gpointer UNUSED(key), gpointer value, gpointer UNUSED(data))
1322 filelist_free(static_cast<GList *>(value));
1325 static void file_data_basename_hash_free(GHashTable *basename_hash)
1327 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, nullptr);
1328 g_hash_table_destroy(basename_hash);
1332 *-----------------------------------------------------------------------------
1333 * handling sidecars in filelist
1334 *-----------------------------------------------------------------------------
1337 static GList *filelist_filter_out_sidecars(GList *flist)
1339 GList *work = flist;
1340 GList *flist_filtered = nullptr;
1344 auto fd = static_cast<FileData *>(work->data);
1347 if (fd->parent) /* remove fd's that are children */
1348 file_data_unref(fd);
1350 flist_filtered = g_list_prepend(flist_filtered, fd);
1354 return flist_filtered;
1357 static void file_data_basename_hash_to_sidecars(gpointer UNUSED(key), gpointer value, gpointer UNUSED(data))
1359 auto basename_list = static_cast<GList *>(value);
1360 file_data_check_sidecars(basename_list);
1364 static gboolean is_hidden_file(const gchar *name)
1366 if (name[0] != '.') return FALSE;
1367 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1372 *-----------------------------------------------------------------------------
1373 * the main filelist function
1374 *-----------------------------------------------------------------------------
1377 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1382 GList *dlist = nullptr;
1383 GList *flist = nullptr;
1384 GList *xmp_files = nullptr;
1385 gint (*stat_func)(const gchar *path, struct stat *buf);
1386 GHashTable *basename_hash = nullptr;
1388 g_assert(files || dirs);
1390 if (files) *files = nullptr;
1391 if (dirs) *dirs = nullptr;
1393 pathl = path_from_utf8(dir_path);
1394 if (!pathl) return FALSE;
1396 dp = opendir(pathl);
1403 if (files) basename_hash = file_data_basename_hash_new();
1405 if (follow_symlinks)
1410 while ((dir = readdir(dp)) != nullptr)
1412 struct stat ent_sbuf;
1413 const gchar *name = dir->d_name;
1416 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1419 filepath = g_build_filename(pathl, name, NULL);
1420 if (stat_func(filepath, &ent_sbuf) >= 0)
1422 if (S_ISDIR(ent_sbuf.st_mode))
1424 /* we ignore the .thumbnails dir for cleanliness */
1426 (name[0] != '.' || (name[1] != '\0' && (name[1] != '.' || name[2] != '\0'))) &&
1427 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1428 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1429 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1431 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1436 if (files && filter_name_exists(name))
1438 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1439 flist = g_list_prepend(flist, fd);
1440 if (fd->sidecar_priority && !fd->disable_grouping)
1442 if (strcmp(fd->extension, ".xmp") != 0)
1443 file_data_basename_hash_insert(basename_hash, fd);
1445 xmp_files = g_list_append(xmp_files, fd);
1452 if (errno == EOVERFLOW)
1454 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1466 g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
1467 g_list_free(xmp_files);
1470 if (dirs) *dirs = dlist;
1474 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, nullptr);
1476 *files = filelist_filter_out_sidecars(flist);
1478 if (basename_hash) file_data_basename_hash_free(basename_hash);
1483 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1485 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1488 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1490 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1493 FileData *file_data_new_group(const gchar *path_utf8)
1500 if (!file_data_pool)
1502 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
1505 if (!stat_utf8(path_utf8, &st))
1511 if (S_ISDIR(st.st_mode))
1512 return file_data_new(path_utf8, &st, TRUE);
1514 dir = remove_level_from_path(path_utf8);
1516 filelist_read_real(dir, &files, nullptr, TRUE);
1518 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
1519 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1525 filelist_free(files);
1531 void filelist_free(GList *list)
1538 file_data_unref((FileData *)work->data);
1546 GList *filelist_copy(GList *list)
1548 GList *new_list = nullptr;
1556 fd = static_cast<FileData *>(work->data);
1559 new_list = g_list_prepend(new_list, file_data_ref(fd));
1562 return g_list_reverse(new_list);
1565 GList *filelist_from_path_list(GList *list)
1567 GList *new_list = nullptr;
1575 path = static_cast<gchar *>(work->data);
1578 new_list = g_list_prepend(new_list, file_data_new_group(path));
1581 return g_list_reverse(new_list);
1584 GList *filelist_to_path_list(GList *list)
1586 GList *new_list = nullptr;
1594 fd = static_cast<FileData *>(work->data);
1597 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1600 return g_list_reverse(new_list);
1603 GList *filelist_filter(GList *list, gboolean is_dir_list)
1607 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1612 auto fd = static_cast<FileData *>(work->data);
1613 const gchar *name = fd->name;
1615 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1616 (!is_dir_list && !filter_name_exists(name)) ||
1617 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1618 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1622 list = g_list_remove_link(list, link);
1623 file_data_unref(fd);
1634 *-----------------------------------------------------------------------------
1635 * filelist recursive
1636 *-----------------------------------------------------------------------------
1639 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1641 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1644 GList *filelist_sort_path(GList *list)
1646 return g_list_sort(list, filelist_sort_path_cb);
1649 static void filelist_recursive_append(GList **list, GList *dirs)
1656 auto fd = static_cast<FileData *>(work->data);
1660 if (filelist_read(fd, &f, &d))
1662 f = filelist_filter(f, FALSE);
1663 f = filelist_sort_path(f);
1664 *list = g_list_concat(*list, f);
1666 d = filelist_filter(d, TRUE);
1667 d = filelist_sort_path(d);
1668 filelist_recursive_append(list, d);
1676 static void filelist_recursive_append_full(GList **list, GList *dirs, SortType method, gboolean ascend)
1683 auto fd = static_cast<FileData *>(work->data);
1687 if (filelist_read(fd, &f, &d))
1689 f = filelist_filter(f, FALSE);
1690 f = filelist_sort_full(f, method, ascend, reinterpret_cast<GCompareFunc>(filelist_sort_file_cb));
1691 *list = g_list_concat(*list, f);
1693 d = filelist_filter(d, TRUE);
1694 d = filelist_sort_path(d);
1695 filelist_recursive_append_full(list, d, method, ascend);
1703 GList *filelist_recursive(FileData *dir_fd)
1708 if (!filelist_read(dir_fd, &list, &d)) return nullptr;
1709 list = filelist_filter(list, FALSE);
1710 list = filelist_sort_path(list);
1712 d = filelist_filter(d, TRUE);
1713 d = filelist_sort_path(d);
1714 filelist_recursive_append(&list, d);
1720 GList *filelist_recursive_full(FileData *dir_fd, SortType method, gboolean ascend)
1725 if (!filelist_read(dir_fd, &list, &d)) return nullptr;
1726 list = filelist_filter(list, FALSE);
1727 list = filelist_sort_full(list, method, ascend, reinterpret_cast<GCompareFunc>(filelist_sort_file_cb));
1729 d = filelist_filter(d, TRUE);
1730 d = filelist_sort_path(d);
1731 filelist_recursive_append_full(&list, d, method, ascend);
1738 *-----------------------------------------------------------------------------
1739 * file modification support
1740 *-----------------------------------------------------------------------------
1744 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1746 if (!fdci && fd) fdci = fd->change;
1750 g_free(fdci->source);
1755 if (fd) fd->change = nullptr;
1758 static gboolean file_data_can_write_directly(FileData *fd)
1760 return filter_name_is_writable(fd->extension);
1763 static gboolean file_data_can_write_sidecar(FileData *fd)
1765 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1768 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1770 gchar *sidecar_path = nullptr;
1773 if (!file_data_can_write_sidecar(fd)) return nullptr;
1775 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1776 gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1779 auto sfd = static_cast<FileData *>(work->data);
1781 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1783 sidecar_path = g_strdup(sfd->path);
1787 g_free(extended_extension);
1789 if (!existing_only && !sidecar_path)
1791 if (options->metadata.sidecar_extended_name)
1792 sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1795 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1796 sidecar_path = g_strconcat(base, ".xmp", NULL);
1801 return sidecar_path;
1805 * marks and orientation
1808 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1809 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1810 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1811 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1813 gboolean file_data_get_mark(FileData *fd, gint n)
1815 gboolean valid = (fd->valid_marks & (1 << n));
1817 if (file_data_get_mark_func[n] && !valid)
1819 guint old = fd->marks;
1820 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1822 if (!value != !(fd->marks & (1 << n)))
1824 fd->marks = fd->marks ^ (1 << n);
1827 fd->valid_marks |= (1 << n);
1828 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1830 file_data_unref(fd);
1832 else if (!old && fd->marks)
1838 return !!(fd->marks & (1 << n));
1841 guint file_data_get_marks(FileData *fd)
1844 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1848 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1851 if (!value == !file_data_get_mark(fd, n)) return;
1853 if (file_data_set_mark_func[n])
1855 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1860 fd->marks = fd->marks ^ (1 << n);
1862 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1864 file_data_unref(fd);
1866 else if (!old && fd->marks)
1871 file_data_increment_version(fd);
1872 file_data_send_notification(fd, NOTIFY_MARKS);
1875 gboolean file_data_filter_marks(FileData *fd, guint filter)
1878 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1879 return ((fd->marks & filter) == filter);
1882 GList *file_data_filter_marks_list(GList *list, guint filter)
1889 auto fd = static_cast<FileData *>(work->data);
1893 if (!file_data_filter_marks(fd, filter))
1895 list = g_list_remove_link(list, link);
1896 file_data_unref(fd);
1904 gboolean file_data_filter_file_filter(FileData *fd, GRegex *filter)
1906 return g_regex_match(filter, fd->name, static_cast<GRegexMatchFlags>(0), nullptr);
1909 GList *file_data_filter_file_filter_list(GList *list, GRegex *filter)
1916 auto fd = static_cast<FileData *>(work->data);
1920 if (!file_data_filter_file_filter(fd, filter))
1922 list = g_list_remove_link(list, link);
1923 file_data_unref(fd);
1931 static gboolean file_data_filter_class(FileData *fd, guint filter)
1935 for (i = 0; i < FILE_FORMAT_CLASSES; i++)
1937 if (filter & (1 << i))
1939 if (static_cast<FileFormatClass>(i) == filter_file_get_class(fd->path))
1949 GList *file_data_filter_class_list(GList *list, guint filter)
1956 auto fd = static_cast<FileData *>(work->data);
1960 if (!file_data_filter_class(fd, filter))
1962 list = g_list_remove_link(list, link);
1963 file_data_unref(fd);
1971 static void file_data_notify_mark_func(gpointer UNUSED(key), gpointer value, gpointer UNUSED(user_data))
1973 auto fd = static_cast<FileData *>(value);
1974 file_data_increment_version(fd);
1975 file_data_send_notification(fd, NOTIFY_MARKS);
1978 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1980 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1982 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1984 file_data_get_mark_func[n] = get_mark_func;
1985 file_data_set_mark_func[n] = set_mark_func;
1986 file_data_mark_func_data[n] = data;
1987 file_data_destroy_mark_func[n] = notify;
1989 if (get_mark_func && file_data_pool)
1991 /* this effectively changes all known files */
1992 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, nullptr);
1998 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
2000 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
2001 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
2002 if (data) *data = file_data_mark_func_data[n];
2005 #pragma GCC diagnostic push
2006 #pragma GCC diagnostic ignored "-Wunused-function"
2007 gint file_data_get_user_orientation_unused(FileData *fd)
2009 return fd->user_orientation;
2012 void file_data_set_user_orientation_unused(FileData *fd, gint value)
2014 if (fd->user_orientation == value) return;
2016 fd->user_orientation = value;
2017 file_data_increment_version(fd);
2018 file_data_send_notification(fd, NOTIFY_ORIENTATION);
2020 #pragma GCC diagnostic pop
2024 * file_data - operates on the given fd
2025 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
2029 /* return list of sidecar file extensions in a string */
2030 gchar *file_data_sc_list_to_string(FileData *fd)
2033 GString *result = g_string_new("");
2035 work = fd->sidecar_files;
2038 auto sfd = static_cast<FileData *>(work->data);
2040 result = g_string_append(result, "+ ");
2041 result = g_string_append(result, sfd->extension);
2043 if (work) result = g_string_append_c(result, ' ');
2046 return g_string_free(result, FALSE);
2052 * add FileDataChangeInfo (see typedefs.h) for the given operation
2053 * uses file_data_add_change_info
2055 * fails if the fd->change already exists - change operations can't run in parallel
2056 * fd->change_info works as a lock
2058 * dest can be NULL - in this case the current name is used for now, it will
2063 FileDataChangeInfo types:
2065 MOVE - path is changed, name may be changed too
2066 RENAME - path remains unchanged, name is changed
2067 extension should remain (FIXME should we allow editing extension? it will make problems with grouping)
2068 sidecar names are changed too, extensions are not changed
2070 UPDATE - file size, date or grouping has been changed
2073 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
2075 FileDataChangeInfo *fdci;
2077 if (fd->change) return FALSE;
2079 fdci = g_new0(FileDataChangeInfo, 1);
2084 fdci->source = g_strdup(src);
2086 fdci->source = g_strdup(fd->path);
2089 fdci->dest = g_strdup(dest);
2096 static void file_data_planned_change_remove(FileData *fd)
2098 if (file_data_planned_change_hash &&
2099 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
2101 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
2103 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
2104 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
2105 file_data_unref(fd);
2106 if (g_hash_table_size(file_data_planned_change_hash) == 0)
2108 g_hash_table_destroy(file_data_planned_change_hash);
2109 file_data_planned_change_hash = nullptr;
2110 DEBUG_1("planned change: empty");
2117 void file_data_free_ci(FileData *fd)
2119 FileDataChangeInfo *fdci = fd->change;
2123 file_data_planned_change_remove(fd);
2125 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
2127 g_free(fdci->source);
2132 fd->change = nullptr;
2135 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
2137 FileDataChangeInfo *fdci = fd->change;
2139 fdci->regroup_when_finished = enable;
2142 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
2146 if (fd->parent) fd = fd->parent;
2148 if (fd->change) return FALSE;
2150 work = fd->sidecar_files;
2153 auto sfd = static_cast<FileData *>(work->data);
2155 if (sfd->change) return FALSE;
2159 file_data_add_ci(fd, type, nullptr, nullptr);
2161 work = fd->sidecar_files;
2164 auto sfd = static_cast<FileData *>(work->data);
2166 file_data_add_ci(sfd, type, nullptr, nullptr);
2173 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
2177 if (fd->parent) fd = fd->parent;
2179 if (!fd->change || fd->change->type != type) return FALSE;
2181 work = fd->sidecar_files;
2184 auto sfd = static_cast<FileData *>(work->data);
2186 if (!sfd->change || sfd->change->type != type) return FALSE;
2194 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
2196 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
2197 file_data_sc_update_ci_copy(fd, dest_path);
2201 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
2203 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
2204 file_data_sc_update_ci_move(fd, dest_path);
2208 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
2210 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
2211 file_data_sc_update_ci_rename(fd, dest_path);
2215 gboolean file_data_sc_add_ci_delete(FileData *fd)
2217 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
2220 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
2222 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
2223 file_data_sc_update_ci_unspecified(fd, dest_path);
2227 gboolean file_data_add_ci_write_metadata(FileData *fd)
2229 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, nullptr, nullptr);
2232 void file_data_sc_free_ci(FileData *fd)
2236 if (fd->parent) fd = fd->parent;
2238 file_data_free_ci(fd);
2240 work = fd->sidecar_files;
2243 auto sfd = static_cast<FileData *>(work->data);
2245 file_data_free_ci(sfd);
2250 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2253 gboolean ret = TRUE;
2258 auto fd = static_cast<FileData *>(work->data);
2260 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2267 static void file_data_sc_revert_ci_list(GList *fd_list)
2274 auto fd = static_cast<FileData *>(work->data);
2276 file_data_sc_free_ci(fd);
2281 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2288 auto fd = static_cast<FileData *>(work->data);
2290 if (!func(fd, dest))
2292 file_data_sc_revert_ci_list(work->prev);
2301 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2303 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2306 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2308 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2311 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2313 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2316 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2318 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2321 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2324 gboolean ret = TRUE;
2329 auto fd = static_cast<FileData *>(work->data);
2331 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2338 void file_data_free_ci_list(GList *fd_list)
2345 auto fd = static_cast<FileData *>(work->data);
2347 file_data_free_ci(fd);
2352 void file_data_sc_free_ci_list(GList *fd_list)
2359 auto fd = static_cast<FileData *>(work->data);
2361 file_data_sc_free_ci(fd);
2367 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2368 * fails if fd->change does not exist or the change type does not match
2371 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2373 FileDataChangeType type = fd->change->type;
2375 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2379 if (!file_data_planned_change_hash)
2380 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2382 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2384 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2385 g_hash_table_remove(file_data_planned_change_hash, old_path);
2386 file_data_unref(fd);
2389 ofd = static_cast<FileData *>(g_hash_table_lookup(file_data_planned_change_hash, new_path));
2394 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2395 g_hash_table_remove(file_data_planned_change_hash, new_path);
2396 file_data_unref(ofd);
2399 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2401 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2406 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2408 gchar *old_path = fd->change->dest;
2410 fd->change->dest = g_strdup(dest_path);
2411 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2415 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2417 const gchar *extension = registered_extension_from_path(fd->change->source);
2418 gchar *base = remove_extension_from_path(dest_path);
2419 gchar *old_path = fd->change->dest;
2421 fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2422 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2428 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2431 gchar *dest_path_full = nullptr;
2433 if (fd->parent) fd = fd->parent;
2437 dest_path = fd->path;
2439 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2441 gchar *dir = remove_level_from_path(fd->path);
2443 dest_path_full = g_build_filename(dir, dest_path, NULL);
2445 dest_path = dest_path_full;
2447 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2449 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2450 dest_path = dest_path_full;
2453 file_data_update_ci_dest(fd, dest_path);
2455 work = fd->sidecar_files;
2458 auto sfd = static_cast<FileData *>(work->data);
2460 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2464 g_free(dest_path_full);
2467 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2469 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2470 file_data_sc_update_ci(fd, dest_path);
2474 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2476 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2479 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2481 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2484 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2486 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2489 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2491 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2494 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2496 gboolean (*func)(FileData *, const gchar *))
2499 gboolean ret = TRUE;
2504 auto fd = static_cast<FileData *>(work->data);
2506 if (!func(fd, dest)) ret = FALSE;
2513 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2515 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2518 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2520 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2523 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2525 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2530 * verify source and dest paths - dest image exists, etc.
2531 * it should detect all possible problems with the planned operation
2534 gint file_data_verify_ci(FileData *fd, GList *list)
2536 gint ret = CHANGE_OK;
2538 GList *work = nullptr;
2539 FileData *fd1 = nullptr;
2543 DEBUG_1("Change checked: no change info: %s", fd->path);
2547 if (!isname(fd->path))
2549 /* this probably should not happen */
2550 ret |= CHANGE_NO_SRC;
2551 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2555 dir = remove_level_from_path(fd->path);
2557 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2558 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2559 fd->change->type != FILEDATA_CHANGE_RENAME &&
2560 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2563 ret |= CHANGE_WARN_UNSAVED_META;
2564 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2567 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2568 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2569 !access_file(fd->path, R_OK))
2571 ret |= CHANGE_NO_READ_PERM;
2572 DEBUG_1("Change checked: no read permission: %s", fd->path);
2574 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2575 !access_file(dir, W_OK))
2577 ret |= CHANGE_NO_WRITE_PERM_DIR;
2578 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2580 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2581 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2582 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2583 !access_file(fd->path, W_OK))
2585 ret |= CHANGE_WARN_NO_WRITE_PERM;
2586 DEBUG_1("Change checked: no write permission: %s", fd->path);
2588 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2589 - that means that there are no hard errors and warnings can be disabled
2590 - the destination is determined during the check
2592 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2594 /* determine destination file */
2595 gboolean have_dest = FALSE;
2596 gchar *dest_dir = nullptr;
2598 if (options->metadata.save_in_image_file)
2600 if (file_data_can_write_directly(fd))
2602 /* we can write the file directly */
2603 if (access_file(fd->path, W_OK))
2609 if (options->metadata.warn_on_write_problems)
2611 ret |= CHANGE_WARN_NO_WRITE_PERM;
2612 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2616 else if (file_data_can_write_sidecar(fd))
2618 /* we can write sidecar */
2619 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2620 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2622 file_data_update_ci_dest(fd, sidecar);
2627 if (options->metadata.warn_on_write_problems)
2629 ret |= CHANGE_WARN_NO_WRITE_PERM;
2630 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2639 /* write private metadata file under ~/.geeqie */
2641 /* If an existing metadata file exists, we will try writing to
2642 * it's location regardless of the user's preference.
2644 gchar *metadata_path = nullptr;
2646 /* but ignore XMP if we are not able to write it */
2647 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2649 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2651 if (metadata_path && !access_file(metadata_path, W_OK))
2653 g_free(metadata_path);
2654 metadata_path = nullptr;
2661 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2662 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2664 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2666 metadata_path = g_build_filename(dest_dir, filename, NULL);
2670 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2672 file_data_update_ci_dest(fd, metadata_path);
2677 ret |= CHANGE_NO_WRITE_PERM_DEST;
2678 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2680 g_free(metadata_path);
2685 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2690 same = (strcmp(fd->path, fd->change->dest) == 0);
2694 const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2695 if (!dest_ext) dest_ext = "";
2696 if (!options->file_filter.disable_file_extension_checks)
2698 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2700 ret |= CHANGE_WARN_CHANGED_EXT;
2701 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2707 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /** @FIXME this is now needed for running editors */
2709 ret |= CHANGE_WARN_SAME;
2710 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2714 dest_dir = remove_level_from_path(fd->change->dest);
2716 if (!isdir(dest_dir))
2718 ret |= CHANGE_NO_DEST_DIR;
2719 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2721 else if (!access_file(dest_dir, W_OK))
2723 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2724 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2728 if (isfile(fd->change->dest))
2730 if (!access_file(fd->change->dest, W_OK))
2732 ret |= CHANGE_NO_WRITE_PERM_DEST;
2733 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2737 ret |= CHANGE_WARN_DEST_EXISTS;
2738 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2741 else if (isdir(fd->change->dest))
2743 ret |= CHANGE_DEST_EXISTS;
2744 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2751 /* During a rename operation, check if another planned destination file has
2754 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2755 fd->change->type == FILEDATA_CHANGE_COPY ||
2756 fd->change->type == FILEDATA_CHANGE_MOVE)
2761 fd1 = static_cast<FileData *>(work->data);
2763 if (fd1 != nullptr && fd != fd1 )
2765 if (!strcmp(fd->change->dest, fd1->change->dest))
2767 ret |= CHANGE_DUPLICATE_DEST;
2773 fd->change->error = ret;
2774 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2781 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2786 ret = file_data_verify_ci(fd, list);
2788 work = fd->sidecar_files;
2791 auto sfd = static_cast<FileData *>(work->data);
2793 ret |= file_data_verify_ci(sfd, list);
2800 gchar *file_data_get_error_string(gint error)
2802 GString *result = g_string_new("");
2804 if (error & CHANGE_NO_SRC)
2806 if (result->len > 0) g_string_append(result, ", ");
2807 g_string_append(result, _("file or directory does not exist"));
2810 if (error & CHANGE_DEST_EXISTS)
2812 if (result->len > 0) g_string_append(result, ", ");
2813 g_string_append(result, _("destination already exists"));
2816 if (error & CHANGE_NO_WRITE_PERM_DEST)
2818 if (result->len > 0) g_string_append(result, ", ");
2819 g_string_append(result, _("destination can't be overwritten"));
2822 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2824 if (result->len > 0) g_string_append(result, ", ");
2825 g_string_append(result, _("destination directory is not writable"));
2828 if (error & CHANGE_NO_DEST_DIR)
2830 if (result->len > 0) g_string_append(result, ", ");
2831 g_string_append(result, _("destination directory does not exist"));
2834 if (error & CHANGE_NO_WRITE_PERM_DIR)
2836 if (result->len > 0) g_string_append(result, ", ");
2837 g_string_append(result, _("source directory is not writable"));
2840 if (error & CHANGE_NO_READ_PERM)
2842 if (result->len > 0) g_string_append(result, ", ");
2843 g_string_append(result, _("no read permission"));
2846 if (error & CHANGE_WARN_NO_WRITE_PERM)
2848 if (result->len > 0) g_string_append(result, ", ");
2849 g_string_append(result, _("file is readonly"));
2852 if (error & CHANGE_WARN_DEST_EXISTS)
2854 if (result->len > 0) g_string_append(result, ", ");
2855 g_string_append(result, _("destination already exists and will be overwritten"));
2858 if (error & CHANGE_WARN_SAME)
2860 if (result->len > 0) g_string_append(result, ", ");
2861 g_string_append(result, _("source and destination are the same"));
2864 if (error & CHANGE_WARN_CHANGED_EXT)
2866 if (result->len > 0) g_string_append(result, ", ");
2867 g_string_append(result, _("source and destination have different extension"));
2870 if (error & CHANGE_WARN_UNSAVED_META)
2872 if (result->len > 0) g_string_append(result, ", ");
2873 g_string_append(result, _("there are unsaved metadata changes for the file"));
2876 if (error & CHANGE_DUPLICATE_DEST)
2878 if (result->len > 0) g_string_append(result, ", ");
2879 g_string_append(result, _("another destination file has the same filename"));
2882 return g_string_free(result, FALSE);
2885 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2888 gint all_errors = 0;
2889 gint common_errors = ~0;
2894 if (!list) return 0;
2896 num = g_list_length(list);
2897 errors = g_new(int, num);
2905 fd = static_cast<FileData *>(work->data);
2908 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2909 all_errors |= error;
2910 common_errors &= error;
2917 if (desc && all_errors)
2920 GString *result = g_string_new("");
2924 gchar *str = file_data_get_error_string(common_errors);
2925 g_string_append(result, str);
2926 g_string_append(result, "\n");
2937 fd = static_cast<FileData *>(work->data);
2940 error = errors[i] & ~common_errors;
2944 gchar *str = file_data_get_error_string(error);
2945 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2950 *desc = g_string_free(result, FALSE);
2959 * perform the change described by FileFataChangeInfo
2960 * it is used for internal operations,
2961 * this function actually operates with files on the filesystem
2962 * it should implement safe delete
2965 static gboolean file_data_perform_move(FileData *fd)
2967 g_assert(!strcmp(fd->change->source, fd->path));
2968 return move_file(fd->change->source, fd->change->dest);
2971 static gboolean file_data_perform_copy(FileData *fd)
2973 g_assert(!strcmp(fd->change->source, fd->path));
2974 return copy_file(fd->change->source, fd->change->dest);
2977 static gboolean file_data_perform_delete(FileData *fd)
2979 if (isdir(fd->path) && !islink(fd->path))
2980 return rmdir_utf8(fd->path);
2982 if (options->file_ops.safe_delete_enable)
2983 return file_util_safe_unlink(fd->path);
2985 return unlink_file(fd->path);
2988 gboolean file_data_perform_ci(FileData *fd)
2990 /** @FIXME When a directory that is a symbolic link is deleted,
2991 * at this point fd->change is null because no FileDataChangeInfo
2992 * has been set up. Therefore there is a seg. fault.
2993 * This code simply aborts the delete.
3000 FileDataChangeType type = fd->change->type;
3004 case FILEDATA_CHANGE_MOVE:
3005 return file_data_perform_move(fd);
3006 case FILEDATA_CHANGE_COPY:
3007 return file_data_perform_copy(fd);
3008 case FILEDATA_CHANGE_RENAME:
3009 return file_data_perform_move(fd); /* the same as move */
3010 case FILEDATA_CHANGE_DELETE:
3011 return file_data_perform_delete(fd);
3012 case FILEDATA_CHANGE_WRITE_METADATA:
3013 return metadata_write_perform(fd);
3014 case FILEDATA_CHANGE_UNSPECIFIED:
3015 /* nothing to do here */
3023 gboolean file_data_sc_perform_ci(FileData *fd)
3026 gboolean ret = TRUE;
3027 FileDataChangeType type = fd->change->type;
3029 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3031 work = fd->sidecar_files;
3034 auto sfd = static_cast<FileData *>(work->data);
3036 if (!file_data_perform_ci(sfd)) ret = FALSE;
3040 if (!file_data_perform_ci(fd)) ret = FALSE;
3046 * updates FileData structure according to FileDataChangeInfo
3049 gboolean file_data_apply_ci(FileData *fd)
3051 FileDataChangeType type = fd->change->type;
3053 /** @FIXME delete ?*/
3054 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
3056 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
3057 file_data_planned_change_remove(fd);
3059 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
3061 /* this change overwrites another file which is already known to other modules
3062 renaming fd would create duplicate FileData structure
3063 the best thing we can do is nothing
3065 /** @FIXME maybe we could copy stuff like marks
3067 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
3071 file_data_set_path(fd, fd->change->dest);
3074 file_data_increment_version(fd);
3075 file_data_send_notification(fd, NOTIFY_CHANGE);
3080 gboolean file_data_sc_apply_ci(FileData *fd)
3083 FileDataChangeType type = fd->change->type;
3085 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3087 work = fd->sidecar_files;
3090 auto sfd = static_cast<FileData *>(work->data);
3092 file_data_apply_ci(sfd);
3096 file_data_apply_ci(fd);
3101 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
3104 if (fd->parent) fd = fd->parent;
3105 if (!g_list_find(list, fd)) return FALSE;
3107 work = fd->sidecar_files;
3110 if (!g_list_find(list, work->data)) return FALSE;
3116 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
3118 GList *out = nullptr;
3121 /* change partial groups to independent files */
3126 auto fd = static_cast<FileData *>(work->data);
3129 if (!file_data_list_contains_whole_group(list, fd))
3131 file_data_disable_grouping(fd, TRUE);
3134 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
3140 /* remove sidecars from the list,
3141 they can be still accessed via main_fd->sidecar_files */
3145 auto fd = static_cast<FileData *>(work->data);
3149 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
3151 out = g_list_prepend(out, file_data_ref(fd));
3155 filelist_free(list);
3156 out = g_list_reverse(out);
3166 * notify other modules about the change described by FileDataChangeInfo
3169 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks */
3170 /** @FIXME do we need the ignore_list? It looks like a workaround for ineffective
3171 implementation in view-file-list.cc */
3174 struct NotifyIdleData {
3181 FileDataNotifyFunc func;
3183 NotifyPriority priority;
3186 static GList *notify_func_list = nullptr;
3188 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
3190 auto nda = static_cast<const NotifyData *>(a);
3191 auto ndb = static_cast<const NotifyData *>(b);
3193 if (nda->priority < ndb->priority) return -1;
3194 if (nda->priority > ndb->priority) return 1;
3198 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
3201 GList *work = notify_func_list;
3205 auto nd = static_cast<NotifyData *>(work->data);
3207 if (nd->func == func && nd->data == data)
3209 g_warning("Notify func already registered");
3215 nd = g_new(NotifyData, 1);
3218 nd->priority = priority;
3220 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
3221 DEBUG_2("Notify func registered: %p", (void *)nd);
3226 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
3228 GList *work = notify_func_list;
3232 auto nd = static_cast<NotifyData *>(work->data);
3234 if (nd->func == func && nd->data == data)
3236 notify_func_list = g_list_delete_link(notify_func_list, work);
3237 DEBUG_2("Notify func unregistered: %p", (void *)nd);
3244 g_warning("Notify func not found");
3248 #pragma GCC diagnostic push
3249 #pragma GCC diagnostic ignored "-Wunused-function"
3250 gboolean file_data_send_notification_idle_cb_unused(gpointer data)
3252 NotifyIdleData *nid = (NotifyIdleData *)data;
3253 GList *work = notify_func_list;
3257 NotifyData *nd = (NotifyData *)work->data;
3259 nd->func(nid->fd, nid->type, nd->data);
3262 file_data_unref(nid->fd);
3266 #pragma GCC diagnostic pop
3268 void file_data_send_notification(FileData *fd, NotifyType type)
3270 GList *work = notify_func_list;
3274 auto nd = static_cast<NotifyData *>(work->data);
3276 nd->func(fd, type, nd->data);
3280 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3281 nid->fd = file_data_ref(fd);
3283 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3287 static GHashTable *file_data_monitor_pool = nullptr;
3288 static guint realtime_monitor_id = 0; /* event source id */
3290 static void realtime_monitor_check_cb(gpointer key, gpointer UNUSED(value), gpointer UNUSED(data))
3292 auto fd = static_cast<FileData *>(key);
3294 file_data_check_changed_files(fd);
3296 DEBUG_1("monitor %s", fd->path);
3299 static gboolean realtime_monitor_cb(gpointer UNUSED(data))
3301 if (!options->update_on_time_change) return TRUE;
3302 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, nullptr);
3306 gboolean file_data_register_real_time_monitor(FileData *fd)
3312 if (!file_data_monitor_pool)
3313 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3315 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3317 DEBUG_1("Register realtime %d %s", count, fd->path);
3320 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3322 if (!realtime_monitor_id)
3324 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, nullptr);
3330 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3334 g_assert(file_data_monitor_pool);
3336 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3338 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3340 g_assert(count > 0);
3345 g_hash_table_remove(file_data_monitor_pool, fd);
3347 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3349 file_data_unref(fd);
3351 if (g_hash_table_size(file_data_monitor_pool) == 0)
3353 g_source_remove(realtime_monitor_id);
3354 realtime_monitor_id = 0;
3362 *-----------------------------------------------------------------------------
3363 * Saving marks list, clearing marks
3364 * Uses file_data_pool
3365 *-----------------------------------------------------------------------------
3368 static void marks_get_files(gpointer key, gpointer value, gpointer userdata)
3370 auto file_name = static_cast<gchar *>(key);
3371 auto result = static_cast<GString *>(userdata);
3374 if (isfile(file_name))
3376 fd = static_cast<FileData *>(value);
3377 if (fd && fd->marks > 0)
3379 g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks);
3384 gboolean marks_list_load(const gchar *path)
3392 pathl = path_from_utf8(path);
3393 f = fopen(pathl, "r");
3395 if (!f) return FALSE;
3397 /* first line must start with Marks comment */
3398 if (!fgets(s_buf, sizeof(s_buf), f) ||
3399 strncmp(s_buf, "#Marks", 6) != 0)
3405 while (fgets(s_buf, sizeof(s_buf), f))
3407 if (s_buf[0]=='#') continue;
3408 file_path = strtok(s_buf, ",");
3409 marks_value = strtok(nullptr, ",");
3410 if (isfile(file_path))
3412 FileData *fd = file_data_new_no_grouping(file_path);
3417 gint mark_no = 1 << n;
3418 if (atoi(marks_value) & mark_no)
3420 file_data_set_mark(fd, n , 1);
3431 gboolean marks_list_save(gchar *path, gboolean save)
3433 SecureSaveInfo *ssi;
3435 GString *marks = g_string_new("");
3437 pathl = path_from_utf8(path);
3438 ssi = secure_open(pathl);
3442 log_printf(_("Error: Unable to write marks lists to: %s\n"), path);
3446 secure_fprintf(ssi, "#Marks lists\n");
3450 g_hash_table_foreach(file_data_pool, marks_get_files, marks);
3452 secure_fprintf(ssi, "%s", marks->str);
3453 g_string_free(marks, TRUE);
3455 secure_fprintf(ssi, "#end\n");
3456 return (secure_close(ssi) == 0);
3459 static void marks_clear(gpointer key, gpointer value, gpointer UNUSED(userdata))
3461 auto file_name = static_cast<gchar *>(key);
3466 if (isfile(file_name))
3468 fd = static_cast<FileData *>(value);
3469 if (fd && fd->marks > 0)
3475 if (fd->marks & mark_no)
3477 file_data_set_mark(fd, n , 0);
3485 void marks_clear_all()
3487 g_hash_table_foreach(file_data_pool, marks_clear, nullptr);
3490 void file_data_set_page_num(FileData *fd, gint page_num)
3492 if (fd->page_total > 1 && page_num < 0)
3494 fd->page_num = fd->page_total - 1;
3496 else if (fd->page_total > 1 && page_num <= fd->page_total)
3498 fd->page_num = page_num - 1;
3504 file_data_send_notification(fd, NOTIFY_REREAD);
3507 void file_data_inc_page_num(FileData *fd)
3509 if (fd->page_total > 0 && fd->page_num < fd->page_total - 1)
3511 fd->page_num = fd->page_num + 1;
3513 else if (fd->page_total == 0)
3515 fd->page_num = fd->page_num + 1;
3517 file_data_send_notification(fd, NOTIFY_REREAD);
3520 void file_data_dec_page_num(FileData *fd)
3522 if (fd->page_num > 0)
3524 fd->page_num = fd->page_num - 1;
3526 file_data_send_notification(fd, NOTIFY_REREAD);
3529 void file_data_set_page_total(FileData *fd, gint page_total)
3531 fd->page_total = page_total;
3534 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */