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));
717 fd = static_cast<FileData *>(work->data);
718 log_printf("%-4d %s", fd->ref, fd->path);
727 static void file_data_free(FileData *fd)
729 g_assert(fd->magick == FD_MAGICK);
730 g_assert(fd->ref == 0);
731 g_assert(!fd->locked);
733 #ifdef DEBUG_FILEDATA
734 global_file_data_count--;
735 DEBUG_2("file data count--: %d", global_file_data_count);
738 metadata_cache_free(fd);
739 g_hash_table_remove(file_data_pool, fd->original_path);
742 g_free(fd->original_path);
743 g_free(fd->collate_key_name);
744 g_free(fd->collate_key_name_nocase);
745 g_free(fd->extended_extension);
746 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
747 histmap_free(fd->histmap);
750 g_free(fd->sym_link);
751 g_free(fd->format_name);
752 g_assert(fd->sidecar_files == nullptr); /* sidecar files must be freed before calling this */
754 file_data_change_info_free(nullptr, fd);
759 * @brief Checks if the FileData is referenced
761 * Checks the refcount and whether the FileData is locked.
763 static gboolean file_data_check_has_ref(FileData *fd)
765 return fd->ref > 0 || fd->locked;
769 * @brief Consider freeing a FileData.
771 * This function will free a FileData and its children provided that neither its parent nor it has
772 * a positive refcount, and provided that neither is locked.
774 static void file_data_consider_free(FileData *fd)
777 FileData *parent = fd->parent ? fd->parent : fd;
779 g_assert(fd->magick == FD_MAGICK);
780 if (file_data_check_has_ref(fd)) return;
781 if (file_data_check_has_ref(parent)) return;
783 work = parent->sidecar_files;
786 auto sfd = static_cast<FileData *>(work->data);
787 if (file_data_check_has_ref(sfd)) return;
791 /* Neither the parent nor the siblings are referenced, so we can free everything */
792 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
793 fd->path, fd->parent ? parent->path : "-");
795 g_list_free_full(parent->sidecar_files, reinterpret_cast<GDestroyNotify>(file_data_free));
796 parent->sidecar_files = nullptr;
798 file_data_free(parent);
801 #ifdef DEBUG_FILEDATA
802 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
804 void file_data_unref(FileData *fd)
807 if (fd == nullptr) return;
808 if (fd->magick != FD_MAGICK)
809 #ifdef DEBUG_FILEDATA
810 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, (void *)fd);
812 log_printf("Error: fd magick mismatch fd=%p", fd);
814 g_assert(fd->magick == FD_MAGICK);
817 #ifdef DEBUG_FILEDATA
818 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->locked, fd->path,
821 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
824 // Free FileData if it's no longer ref'd
825 file_data_consider_free(fd);
829 * @brief Lock the FileData in memory.
831 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
832 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
833 * even if the code would continue to function properly even if the FileData were freed. Code that
834 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
836 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
837 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
839 void file_data_lock(FileData *fd)
841 if (fd == nullptr) return;
842 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
844 g_assert(fd->magick == FD_MAGICK);
847 DEBUG_2("file_data_ref fd=%p (%d): '%s'", (void *)fd, fd->ref, fd->path);
851 * @brief Reset the maintain-FileData-in-memory lock
853 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
854 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
855 * keeping it from being freed.
857 void file_data_unlock(FileData *fd)
859 if (fd == nullptr) return;
860 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
862 g_assert(fd->magick == FD_MAGICK);
865 // Free FileData if it's no longer ref'd
866 file_data_consider_free(fd);
870 * @brief Lock all of the FileDatas in the provided list
872 * @see file_data_lock(#FileData)
874 void file_data_lock_list(GList *list)
881 auto fd = static_cast<FileData *>(work->data);
888 * @brief Unlock all of the FileDatas in the provided list
890 * @see #file_data_unlock(#FileData)
892 void file_data_unlock_list(GList *list)
899 auto fd = static_cast<FileData *>(work->data);
901 file_data_unlock(fd);
906 *-----------------------------------------------------------------------------
907 * sidecar file info struct
908 *-----------------------------------------------------------------------------
911 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
913 auto fda = static_cast<const FileData *>(a);
914 auto fdb = static_cast<const FileData *>(b);
916 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
917 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
919 return strcmp(fdb->extension, fda->extension);
923 static gint sidecar_file_priority(const gchar *extension)
928 if (extension == nullptr)
931 work = sidecar_ext_get_list();
934 auto ext = static_cast<gchar *>(work->data);
937 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
943 static void file_data_check_sidecars(const GList *basename_list)
945 /* basename_list contains the new group - first is the parent, then sorted sidecars */
946 /* all files in the list have ref count > 0 */
949 GList *s_work, *new_sidecars;
952 if (!basename_list) return;
955 DEBUG_2("basename start");
956 work = basename_list;
959 auto fd = static_cast<FileData *>(work->data);
961 g_assert(fd->magick == FD_MAGICK);
962 DEBUG_2("basename: %p %s", (void *)fd, fd->name);
965 g_assert(fd->parent->magick == FD_MAGICK);
966 DEBUG_2(" parent: %p", (void *)fd->parent);
968 s_work = fd->sidecar_files;
971 auto sfd = static_cast<FileData *>(s_work->data);
972 s_work = s_work->next;
973 g_assert(sfd->magick == FD_MAGICK);
974 DEBUG_2(" sidecar: %p %s", (void *)sfd, sfd->name);
977 g_assert(fd->parent == nullptr || fd->sidecar_files == nullptr);
980 parent_fd = static_cast<FileData *>(basename_list->data);
982 /* check if the second and next entries of basename_list are already connected
983 as sidecars of the first entry (parent_fd) */
984 work = basename_list->next;
985 s_work = parent_fd->sidecar_files;
987 while (work && s_work)
989 if (work->data != s_work->data) break;
991 s_work = s_work->next;
994 if (!work && !s_work)
996 DEBUG_2("basename no change");
997 return; /* no change in grouping */
1000 /* we have to regroup it */
1002 /* first, disconnect everything and send notification*/
1004 work = basename_list;
1007 auto fd = static_cast<FileData *>(work->data);
1009 g_assert(fd->parent == nullptr || fd->sidecar_files == nullptr);
1013 FileData *old_parent = fd->parent;
1014 g_assert(old_parent->parent == nullptr || old_parent->sidecar_files == nullptr);
1015 file_data_ref(old_parent);
1016 file_data_disconnect_sidecar_file(old_parent, fd);
1017 file_data_send_notification(old_parent, NOTIFY_REREAD);
1018 file_data_unref(old_parent);
1021 while (fd->sidecar_files)
1023 auto sfd = static_cast<FileData *>(fd->sidecar_files->data);
1024 g_assert(sfd->parent == nullptr || sfd->sidecar_files == nullptr);
1026 file_data_disconnect_sidecar_file(fd, sfd);
1027 file_data_send_notification(sfd, NOTIFY_REREAD);
1028 file_data_unref(sfd);
1030 file_data_send_notification(fd, NOTIFY_GROUPING);
1032 g_assert(fd->parent == nullptr && fd->sidecar_files == nullptr);
1035 /* now we can form the new group */
1036 work = basename_list->next;
1037 new_sidecars = nullptr;
1040 auto sfd = static_cast<FileData *>(work->data);
1041 g_assert(sfd->magick == FD_MAGICK);
1042 g_assert(sfd->parent == nullptr && sfd->sidecar_files == nullptr);
1043 sfd->parent = parent_fd;
1044 new_sidecars = g_list_prepend(new_sidecars, sfd);
1047 g_assert(parent_fd->sidecar_files == nullptr);
1048 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
1049 DEBUG_1("basename group changed for %s", parent_fd->path);
1053 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
1055 g_assert(target->magick == FD_MAGICK);
1056 g_assert(sfd->magick == FD_MAGICK);
1057 g_assert(g_list_find(target->sidecar_files, sfd));
1059 file_data_ref(target);
1062 g_assert(sfd->parent == target);
1064 file_data_increment_version(sfd); /* increments both sfd and target */
1066 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
1067 sfd->parent = nullptr;
1068 g_free(sfd->extended_extension);
1069 sfd->extended_extension = nullptr;
1071 file_data_unref(target);
1072 file_data_unref(sfd);
1075 /* disables / enables grouping for particular file, sends UPDATE notification */
1076 void file_data_disable_grouping(FileData *fd, gboolean disable)
1078 if (!fd->disable_grouping == !disable) return;
1080 fd->disable_grouping = !!disable;
1086 FileData *parent = file_data_ref(fd->parent);
1087 file_data_disconnect_sidecar_file(parent, fd);
1088 file_data_send_notification(parent, NOTIFY_GROUPING);
1089 file_data_unref(parent);
1091 else if (fd->sidecar_files)
1093 GList *sidecar_files = filelist_copy(fd->sidecar_files);
1094 GList *work = sidecar_files;
1097 auto sfd = static_cast<FileData *>(work->data);
1099 file_data_disconnect_sidecar_file(fd, sfd);
1100 file_data_send_notification(sfd, NOTIFY_GROUPING);
1102 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
1103 filelist_free(sidecar_files);
1107 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
1112 file_data_increment_version(fd);
1113 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
1115 file_data_send_notification(fd, NOTIFY_GROUPING);
1118 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
1125 auto fd = static_cast<FileData *>(work->data);
1127 file_data_disable_grouping(fd, disable);
1135 *-----------------------------------------------------------------------------
1137 *-----------------------------------------------------------------------------
1141 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
1144 if (!filelist_sort_ascend)
1151 switch (filelist_sort_method)
1156 if (fa->size < fb->size) return -1;
1157 if (fa->size > fb->size) return 1;
1158 /* fall back to name */
1161 if (fa->date < fb->date) return -1;
1162 if (fa->date > fb->date) return 1;
1163 /* fall back to name */
1166 if (fa->cdate < fb->cdate) return -1;
1167 if (fa->cdate > fb->cdate) return 1;
1168 /* fall back to name */
1171 if (fa->exifdate < fb->exifdate) return -1;
1172 if (fa->exifdate > fb->exifdate) return 1;
1173 /* fall back to name */
1175 case SORT_EXIFTIMEDIGITIZED:
1176 if (fa->exifdate_digitized < fb->exifdate_digitized) return -1;
1177 if (fa->exifdate_digitized > fb->exifdate_digitized) return 1;
1178 /* fall back to name */
1181 if (fa->rating < fb->rating) return -1;
1182 if (fa->rating > fb->rating) return 1;
1183 /* fall back to name */
1186 if (fa->format_class < fb->format_class) return -1;
1187 if (fa->format_class > fb->format_class) return 1;
1188 /* fall back to name */
1194 if (options->file_sort.case_sensitive)
1195 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1197 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1199 if (ret != 0) return ret;
1201 /* do not return 0 unless the files are really the same
1202 file_data_pool ensures that original_path is unique
1204 return strcmp(fa->original_path, fb->original_path);
1207 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1209 filelist_sort_method = method;
1210 filelist_sort_ascend = ascend;
1211 return filelist_sort_compare_filedata(fa, fb);
1214 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1216 return filelist_sort_compare_filedata(static_cast<FileData *>(a), static_cast<FileData *>(b));
1219 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1221 filelist_sort_method = method;
1222 filelist_sort_ascend = ascend;
1223 return g_list_sort(list, cb);
1226 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1228 filelist_sort_method = method;
1229 filelist_sort_ascend = ascend;
1230 return g_list_insert_sorted(list, data, cb);
1233 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1235 return filelist_sort_full(list, method, ascend, reinterpret_cast<GCompareFunc>(filelist_sort_file_cb));
1238 #pragma GCC diagnostic push
1239 #pragma GCC diagnostic ignored "-Wunused-function"
1240 GList *filelist_insert_sort_unused(GList *list, FileData *fd, SortType method, gboolean ascend)
1242 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1244 #pragma GCC diagnostic pop
1247 *-----------------------------------------------------------------------------
1248 * basename hash - grouping of sidecars in filelist
1249 *-----------------------------------------------------------------------------
1253 static GHashTable *file_data_basename_hash_new()
1255 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, nullptr);
1258 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1261 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1263 list = static_cast<GList *>(g_hash_table_lookup(basename_hash, basename));
1267 DEBUG_1("TG: basename_hash not found for %s",fd->path);
1268 const gchar *parent_extension = registered_extension_from_path(basename);
1270 if (parent_extension)
1272 DEBUG_1("TG: parent extension %s",parent_extension);
1273 gchar *parent_basename = g_strndup(basename, parent_extension - basename);
1274 DEBUG_1("TG: parent basename %s",parent_basename);
1275 auto parent_fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, basename));
1278 DEBUG_1("TG: parent fd found");
1279 list = static_cast<GList *>(g_hash_table_lookup(basename_hash, parent_basename));
1280 if (!g_list_find(list, parent_fd))
1282 DEBUG_1("TG: parent fd doesn't fit");
1283 g_free(parent_basename);
1289 basename = parent_basename;
1290 fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
1296 if (!g_list_find(list, fd))
1298 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1299 g_hash_table_insert(basename_hash, basename, list);
1308 static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
1310 file_data_basename_hash_insert(static_cast<GHashTable *>(basename_hash), static_cast<FileData *>(fd));
1313 static void file_data_basename_hash_remove_list(gpointer, gpointer value, gpointer)
1315 filelist_free(static_cast<GList *>(value));
1318 static void file_data_basename_hash_free(GHashTable *basename_hash)
1320 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, nullptr);
1321 g_hash_table_destroy(basename_hash);
1325 *-----------------------------------------------------------------------------
1326 * handling sidecars in filelist
1327 *-----------------------------------------------------------------------------
1330 static GList *filelist_filter_out_sidecars(GList *flist)
1332 GList *work = flist;
1333 GList *flist_filtered = nullptr;
1337 auto fd = static_cast<FileData *>(work->data);
1340 if (fd->parent) /* remove fd's that are children */
1341 file_data_unref(fd);
1343 flist_filtered = g_list_prepend(flist_filtered, fd);
1347 return flist_filtered;
1350 static void file_data_basename_hash_to_sidecars(gpointer, gpointer value, gpointer)
1352 auto basename_list = static_cast<GList *>(value);
1353 file_data_check_sidecars(basename_list);
1357 static gboolean is_hidden_file(const gchar *name)
1359 if (name[0] != '.') return FALSE;
1360 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1365 *-----------------------------------------------------------------------------
1366 * the main filelist function
1367 *-----------------------------------------------------------------------------
1370 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1375 GList *dlist = nullptr;
1376 GList *flist = nullptr;
1377 GList *xmp_files = nullptr;
1378 gint (*stat_func)(const gchar *path, struct stat *buf);
1379 GHashTable *basename_hash = nullptr;
1381 g_assert(files || dirs);
1383 if (files) *files = nullptr;
1384 if (dirs) *dirs = nullptr;
1386 pathl = path_from_utf8(dir_path);
1387 if (!pathl) return FALSE;
1389 dp = opendir(pathl);
1396 if (files) basename_hash = file_data_basename_hash_new();
1398 if (follow_symlinks)
1403 while ((dir = readdir(dp)) != nullptr)
1405 struct stat ent_sbuf;
1406 const gchar *name = dir->d_name;
1409 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1412 filepath = g_build_filename(pathl, name, NULL);
1413 if (stat_func(filepath, &ent_sbuf) >= 0)
1415 if (S_ISDIR(ent_sbuf.st_mode))
1417 /* we ignore the .thumbnails dir for cleanliness */
1419 (name[0] != '.' || (name[1] != '\0' && (name[1] != '.' || name[2] != '\0'))) &&
1420 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1421 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1422 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1424 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1429 if (files && filter_name_exists(name))
1431 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1432 flist = g_list_prepend(flist, fd);
1433 if (fd->sidecar_priority && !fd->disable_grouping)
1435 if (strcmp(fd->extension, ".xmp") != 0)
1436 file_data_basename_hash_insert(basename_hash, fd);
1438 xmp_files = g_list_append(xmp_files, fd);
1445 if (errno == EOVERFLOW)
1447 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1459 g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
1460 g_list_free(xmp_files);
1463 if (dirs) *dirs = dlist;
1467 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, nullptr);
1469 *files = filelist_filter_out_sidecars(flist);
1471 if (basename_hash) file_data_basename_hash_free(basename_hash);
1476 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1478 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1481 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1483 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1486 FileData *file_data_new_group(const gchar *path_utf8)
1493 if (!file_data_pool)
1495 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
1498 if (!stat_utf8(path_utf8, &st))
1504 if (S_ISDIR(st.st_mode))
1505 return file_data_new(path_utf8, &st, TRUE);
1507 dir = remove_level_from_path(path_utf8);
1509 filelist_read_real(dir, &files, nullptr, TRUE);
1511 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
1512 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1518 filelist_free(files);
1524 void filelist_free(GList *list)
1531 file_data_unref((FileData *)work->data);
1539 GList *filelist_copy(GList *list)
1541 GList *new_list = nullptr;
1549 fd = static_cast<FileData *>(work->data);
1552 new_list = g_list_prepend(new_list, file_data_ref(fd));
1555 return g_list_reverse(new_list);
1558 GList *filelist_from_path_list(GList *list)
1560 GList *new_list = nullptr;
1568 path = static_cast<gchar *>(work->data);
1571 new_list = g_list_prepend(new_list, file_data_new_group(path));
1574 return g_list_reverse(new_list);
1577 GList *filelist_to_path_list(GList *list)
1579 GList *new_list = nullptr;
1587 fd = static_cast<FileData *>(work->data);
1590 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1593 return g_list_reverse(new_list);
1596 GList *filelist_filter(GList *list, gboolean is_dir_list)
1600 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1605 auto fd = static_cast<FileData *>(work->data);
1606 const gchar *name = fd->name;
1610 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1611 (!is_dir_list && !filter_name_exists(name)) ||
1612 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1613 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1615 list = g_list_remove_link(list, link);
1616 file_data_unref(fd);
1625 *-----------------------------------------------------------------------------
1626 * filelist recursive
1627 *-----------------------------------------------------------------------------
1630 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1632 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1635 GList *filelist_sort_path(GList *list)
1637 return g_list_sort(list, filelist_sort_path_cb);
1640 static void filelist_recursive_append(GList **list, GList *dirs)
1647 auto fd = static_cast<FileData *>(work->data);
1651 if (filelist_read(fd, &f, &d))
1653 f = filelist_filter(f, FALSE);
1654 f = filelist_sort_path(f);
1655 *list = g_list_concat(*list, f);
1657 d = filelist_filter(d, TRUE);
1658 d = filelist_sort_path(d);
1659 filelist_recursive_append(list, d);
1667 static void filelist_recursive_append_full(GList **list, GList *dirs, SortType method, gboolean ascend)
1674 auto fd = static_cast<FileData *>(work->data);
1678 if (filelist_read(fd, &f, &d))
1680 f = filelist_filter(f, FALSE);
1681 f = filelist_sort_full(f, method, ascend, reinterpret_cast<GCompareFunc>(filelist_sort_file_cb));
1682 *list = g_list_concat(*list, f);
1684 d = filelist_filter(d, TRUE);
1685 d = filelist_sort_path(d);
1686 filelist_recursive_append_full(list, d, method, ascend);
1694 GList *filelist_recursive(FileData *dir_fd)
1699 if (!filelist_read(dir_fd, &list, &d)) return nullptr;
1700 list = filelist_filter(list, FALSE);
1701 list = filelist_sort_path(list);
1703 d = filelist_filter(d, TRUE);
1704 d = filelist_sort_path(d);
1705 filelist_recursive_append(&list, d);
1711 GList *filelist_recursive_full(FileData *dir_fd, SortType method, gboolean ascend)
1716 if (!filelist_read(dir_fd, &list, &d)) return nullptr;
1717 list = filelist_filter(list, FALSE);
1718 list = filelist_sort_full(list, method, ascend, reinterpret_cast<GCompareFunc>(filelist_sort_file_cb));
1720 d = filelist_filter(d, TRUE);
1721 d = filelist_sort_path(d);
1722 filelist_recursive_append_full(&list, d, method, ascend);
1729 *-----------------------------------------------------------------------------
1730 * file modification support
1731 *-----------------------------------------------------------------------------
1735 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1737 if (!fdci && fd) fdci = fd->change;
1741 g_free(fdci->source);
1746 if (fd) fd->change = nullptr;
1749 static gboolean file_data_can_write_directly(FileData *fd)
1751 return filter_name_is_writable(fd->extension);
1754 static gboolean file_data_can_write_sidecar(FileData *fd)
1756 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1759 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1761 gchar *sidecar_path = nullptr;
1764 if (!file_data_can_write_sidecar(fd)) return nullptr;
1766 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1767 gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1770 auto sfd = static_cast<FileData *>(work->data);
1772 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1774 sidecar_path = g_strdup(sfd->path);
1778 g_free(extended_extension);
1780 if (!existing_only && !sidecar_path)
1782 if (options->metadata.sidecar_extended_name)
1783 sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1786 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1787 sidecar_path = g_strconcat(base, ".xmp", NULL);
1792 return sidecar_path;
1796 * marks and orientation
1799 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1800 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1801 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1802 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1804 gboolean file_data_get_mark(FileData *fd, gint n)
1806 gboolean valid = (fd->valid_marks & (1 << n));
1808 if (file_data_get_mark_func[n] && !valid)
1810 guint old = fd->marks;
1811 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1813 if (!value != !(fd->marks & (1 << n)))
1815 fd->marks = fd->marks ^ (1 << n);
1818 fd->valid_marks |= (1 << n);
1819 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1821 file_data_unref(fd);
1823 else if (!old && fd->marks)
1829 return !!(fd->marks & (1 << n));
1832 guint file_data_get_marks(FileData *fd)
1835 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1839 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1842 if (!value == !file_data_get_mark(fd, n)) return;
1844 if (file_data_set_mark_func[n])
1846 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1851 fd->marks = fd->marks ^ (1 << n);
1853 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1855 file_data_unref(fd);
1857 else if (!old && fd->marks)
1862 file_data_increment_version(fd);
1863 file_data_send_notification(fd, NOTIFY_MARKS);
1866 gboolean file_data_filter_marks(FileData *fd, guint filter)
1869 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1870 return ((fd->marks & filter) == filter);
1873 GList *file_data_filter_marks_list(GList *list, guint filter)
1880 auto fd = static_cast<FileData *>(work->data);
1884 if (!file_data_filter_marks(fd, filter))
1886 list = g_list_remove_link(list, link);
1887 file_data_unref(fd);
1895 gboolean file_data_filter_file_filter(FileData *fd, GRegex *filter)
1897 return g_regex_match(filter, fd->name, static_cast<GRegexMatchFlags>(0), nullptr);
1900 GList *file_data_filter_file_filter_list(GList *list, GRegex *filter)
1907 auto fd = static_cast<FileData *>(work->data);
1911 if (!file_data_filter_file_filter(fd, filter))
1913 list = g_list_remove_link(list, link);
1914 file_data_unref(fd);
1922 static gboolean file_data_filter_class(FileData *fd, guint filter)
1926 for (i = 0; i < FILE_FORMAT_CLASSES; i++)
1928 if (filter & (1 << i))
1930 if (static_cast<FileFormatClass>(i) == filter_file_get_class(fd->path))
1940 GList *file_data_filter_class_list(GList *list, guint filter)
1947 auto fd = static_cast<FileData *>(work->data);
1951 if (!file_data_filter_class(fd, filter))
1953 list = g_list_remove_link(list, link);
1954 file_data_unref(fd);
1962 static void file_data_notify_mark_func(gpointer, gpointer value, gpointer)
1964 auto fd = static_cast<FileData *>(value);
1965 file_data_increment_version(fd);
1966 file_data_send_notification(fd, NOTIFY_MARKS);
1969 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1971 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1973 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1975 file_data_get_mark_func[n] = get_mark_func;
1976 file_data_set_mark_func[n] = set_mark_func;
1977 file_data_mark_func_data[n] = data;
1978 file_data_destroy_mark_func[n] = notify;
1980 if (get_mark_func && file_data_pool)
1982 /* this effectively changes all known files */
1983 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, nullptr);
1989 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1991 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1992 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1993 if (data) *data = file_data_mark_func_data[n];
1996 #pragma GCC diagnostic push
1997 #pragma GCC diagnostic ignored "-Wunused-function"
1998 gint file_data_get_user_orientation_unused(FileData *fd)
2000 return fd->user_orientation;
2003 void file_data_set_user_orientation_unused(FileData *fd, gint value)
2005 if (fd->user_orientation == value) return;
2007 fd->user_orientation = value;
2008 file_data_increment_version(fd);
2009 file_data_send_notification(fd, NOTIFY_ORIENTATION);
2011 #pragma GCC diagnostic pop
2015 * file_data - operates on the given fd
2016 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
2020 /* return list of sidecar file extensions in a string */
2021 gchar *file_data_sc_list_to_string(FileData *fd)
2024 GString *result = g_string_new("");
2026 work = fd->sidecar_files;
2029 auto sfd = static_cast<FileData *>(work->data);
2031 result = g_string_append(result, "+ ");
2032 result = g_string_append(result, sfd->extension);
2034 if (work) result = g_string_append_c(result, ' ');
2037 return g_string_free(result, FALSE);
2043 * add FileDataChangeInfo (see typedefs.h) for the given operation
2044 * uses file_data_add_change_info
2046 * fails if the fd->change already exists - change operations can't run in parallel
2047 * fd->change_info works as a lock
2049 * dest can be NULL - in this case the current name is used for now, it will
2054 FileDataChangeInfo types:
2056 MOVE - path is changed, name may be changed too
2057 RENAME - path remains unchanged, name is changed
2058 extension should remain (FIXME should we allow editing extension? it will make problems with grouping)
2059 sidecar names are changed too, extensions are not changed
2061 UPDATE - file size, date or grouping has been changed
2064 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
2066 FileDataChangeInfo *fdci;
2068 if (fd->change) return FALSE;
2070 fdci = g_new0(FileDataChangeInfo, 1);
2075 fdci->source = g_strdup(src);
2077 fdci->source = g_strdup(fd->path);
2080 fdci->dest = g_strdup(dest);
2087 static void file_data_planned_change_remove(FileData *fd)
2089 if (file_data_planned_change_hash &&
2090 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
2092 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
2094 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
2095 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
2096 file_data_unref(fd);
2097 if (g_hash_table_size(file_data_planned_change_hash) == 0)
2099 g_hash_table_destroy(file_data_planned_change_hash);
2100 file_data_planned_change_hash = nullptr;
2101 DEBUG_1("planned change: empty");
2108 void file_data_free_ci(FileData *fd)
2110 FileDataChangeInfo *fdci = fd->change;
2114 file_data_planned_change_remove(fd);
2116 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
2118 g_free(fdci->source);
2123 fd->change = nullptr;
2126 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
2128 FileDataChangeInfo *fdci = fd->change;
2130 fdci->regroup_when_finished = enable;
2133 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
2137 if (fd->parent) fd = fd->parent;
2139 if (fd->change) return FALSE;
2141 work = fd->sidecar_files;
2144 auto sfd = static_cast<FileData *>(work->data);
2146 if (sfd->change) return FALSE;
2150 file_data_add_ci(fd, type, nullptr, nullptr);
2152 work = fd->sidecar_files;
2155 auto sfd = static_cast<FileData *>(work->data);
2157 file_data_add_ci(sfd, type, nullptr, nullptr);
2164 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
2168 if (fd->parent) fd = fd->parent;
2170 if (!fd->change || fd->change->type != type) return FALSE;
2172 work = fd->sidecar_files;
2175 auto sfd = static_cast<FileData *>(work->data);
2177 if (!sfd->change || sfd->change->type != type) return FALSE;
2185 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
2187 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
2188 file_data_sc_update_ci_copy(fd, dest_path);
2192 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
2194 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
2195 file_data_sc_update_ci_move(fd, dest_path);
2199 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
2201 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
2202 file_data_sc_update_ci_rename(fd, dest_path);
2206 gboolean file_data_sc_add_ci_delete(FileData *fd)
2208 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
2211 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
2213 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
2214 file_data_sc_update_ci_unspecified(fd, dest_path);
2218 gboolean file_data_add_ci_write_metadata(FileData *fd)
2220 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, nullptr, nullptr);
2223 void file_data_sc_free_ci(FileData *fd)
2227 if (fd->parent) fd = fd->parent;
2229 file_data_free_ci(fd);
2231 work = fd->sidecar_files;
2234 auto sfd = static_cast<FileData *>(work->data);
2236 file_data_free_ci(sfd);
2241 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2244 gboolean ret = TRUE;
2249 auto fd = static_cast<FileData *>(work->data);
2251 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2258 static void file_data_sc_revert_ci_list(GList *fd_list)
2265 auto fd = static_cast<FileData *>(work->data);
2267 file_data_sc_free_ci(fd);
2272 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2279 auto fd = static_cast<FileData *>(work->data);
2281 if (!func(fd, dest))
2283 file_data_sc_revert_ci_list(work->prev);
2292 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2294 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2297 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2299 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2302 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2304 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2307 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2309 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2312 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2315 gboolean ret = TRUE;
2320 auto fd = static_cast<FileData *>(work->data);
2322 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2329 void file_data_free_ci_list(GList *fd_list)
2336 auto fd = static_cast<FileData *>(work->data);
2338 file_data_free_ci(fd);
2343 void file_data_sc_free_ci_list(GList *fd_list)
2350 auto fd = static_cast<FileData *>(work->data);
2352 file_data_sc_free_ci(fd);
2358 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2359 * fails if fd->change does not exist or the change type does not match
2362 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2364 FileDataChangeType type = fd->change->type;
2366 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2370 if (!file_data_planned_change_hash)
2371 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2373 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2375 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2376 g_hash_table_remove(file_data_planned_change_hash, old_path);
2377 file_data_unref(fd);
2380 ofd = static_cast<FileData *>(g_hash_table_lookup(file_data_planned_change_hash, new_path));
2385 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2386 g_hash_table_remove(file_data_planned_change_hash, new_path);
2387 file_data_unref(ofd);
2390 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2392 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2397 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2399 gchar *old_path = fd->change->dest;
2401 fd->change->dest = g_strdup(dest_path);
2402 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2406 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2408 const gchar *extension = registered_extension_from_path(fd->change->source);
2409 gchar *base = remove_extension_from_path(dest_path);
2410 gchar *old_path = fd->change->dest;
2412 fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2413 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2419 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2422 gchar *dest_path_full = nullptr;
2424 if (fd->parent) fd = fd->parent;
2428 dest_path = fd->path;
2430 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2432 gchar *dir = remove_level_from_path(fd->path);
2434 dest_path_full = g_build_filename(dir, dest_path, NULL);
2436 dest_path = dest_path_full;
2438 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2440 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2441 dest_path = dest_path_full;
2444 file_data_update_ci_dest(fd, dest_path);
2446 work = fd->sidecar_files;
2449 auto sfd = static_cast<FileData *>(work->data);
2451 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2455 g_free(dest_path_full);
2458 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2460 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2461 file_data_sc_update_ci(fd, dest_path);
2465 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2467 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2470 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2472 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2475 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2477 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2480 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2482 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2485 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2487 gboolean (*func)(FileData *, const gchar *))
2490 gboolean ret = TRUE;
2495 auto fd = static_cast<FileData *>(work->data);
2497 if (!func(fd, dest)) ret = FALSE;
2504 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2506 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2509 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2511 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2514 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2516 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2521 * verify source and dest paths - dest image exists, etc.
2522 * it should detect all possible problems with the planned operation
2525 gint file_data_verify_ci(FileData *fd, GList *list)
2527 gint ret = CHANGE_OK;
2529 GList *work = nullptr;
2530 FileData *fd1 = nullptr;
2534 DEBUG_1("Change checked: no change info: %s", fd->path);
2538 if (!isname(fd->path))
2540 /* this probably should not happen */
2541 ret |= CHANGE_NO_SRC;
2542 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2546 dir = remove_level_from_path(fd->path);
2548 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2549 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2550 fd->change->type != FILEDATA_CHANGE_RENAME &&
2551 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2554 ret |= CHANGE_WARN_UNSAVED_META;
2555 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2558 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2559 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2560 !access_file(fd->path, R_OK))
2562 ret |= CHANGE_NO_READ_PERM;
2563 DEBUG_1("Change checked: no read permission: %s", fd->path);
2565 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2566 !access_file(dir, W_OK))
2568 ret |= CHANGE_NO_WRITE_PERM_DIR;
2569 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2571 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2572 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2573 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2574 !access_file(fd->path, W_OK))
2576 ret |= CHANGE_WARN_NO_WRITE_PERM;
2577 DEBUG_1("Change checked: no write permission: %s", fd->path);
2579 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2580 - that means that there are no hard errors and warnings can be disabled
2581 - the destination is determined during the check
2583 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2585 /* determine destination file */
2586 gboolean have_dest = FALSE;
2587 gchar *dest_dir = nullptr;
2589 if (options->metadata.save_in_image_file)
2591 if (file_data_can_write_directly(fd))
2593 /* we can write the file directly */
2594 if (access_file(fd->path, W_OK))
2600 if (options->metadata.warn_on_write_problems)
2602 ret |= CHANGE_WARN_NO_WRITE_PERM;
2603 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2607 else if (file_data_can_write_sidecar(fd))
2609 /* we can write sidecar */
2610 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2611 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2613 file_data_update_ci_dest(fd, sidecar);
2618 if (options->metadata.warn_on_write_problems)
2620 ret |= CHANGE_WARN_NO_WRITE_PERM;
2621 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2630 /* write private metadata file under ~/.geeqie */
2632 /* If an existing metadata file exists, we will try writing to
2633 * it's location regardless of the user's preference.
2635 gchar *metadata_path = nullptr;
2637 /* but ignore XMP if we are not able to write it */
2638 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2640 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2642 if (metadata_path && !access_file(metadata_path, W_OK))
2644 g_free(metadata_path);
2645 metadata_path = nullptr;
2652 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2653 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2655 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2657 metadata_path = g_build_filename(dest_dir, filename, NULL);
2661 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2663 file_data_update_ci_dest(fd, metadata_path);
2668 ret |= CHANGE_NO_WRITE_PERM_DEST;
2669 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2671 g_free(metadata_path);
2676 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2681 same = (strcmp(fd->path, fd->change->dest) == 0);
2685 const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2686 if (!dest_ext) dest_ext = "";
2687 if (!options->file_filter.disable_file_extension_checks)
2689 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2691 ret |= CHANGE_WARN_CHANGED_EXT;
2692 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2698 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /** @FIXME this is now needed for running editors */
2700 ret |= CHANGE_WARN_SAME;
2701 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2705 dest_dir = remove_level_from_path(fd->change->dest);
2707 if (!isdir(dest_dir))
2709 ret |= CHANGE_NO_DEST_DIR;
2710 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2712 else if (!access_file(dest_dir, W_OK))
2714 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2715 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2719 if (isfile(fd->change->dest))
2721 if (!access_file(fd->change->dest, W_OK))
2723 ret |= CHANGE_NO_WRITE_PERM_DEST;
2724 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2728 ret |= CHANGE_WARN_DEST_EXISTS;
2729 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2732 else if (isdir(fd->change->dest))
2734 ret |= CHANGE_DEST_EXISTS;
2735 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2742 /* During a rename operation, check if another planned destination file has
2745 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2746 fd->change->type == FILEDATA_CHANGE_COPY ||
2747 fd->change->type == FILEDATA_CHANGE_MOVE)
2752 fd1 = static_cast<FileData *>(work->data);
2754 if (fd1 != nullptr && fd != fd1 )
2756 if (!strcmp(fd->change->dest, fd1->change->dest))
2758 ret |= CHANGE_DUPLICATE_DEST;
2764 fd->change->error = ret;
2765 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2772 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2777 ret = file_data_verify_ci(fd, list);
2779 work = fd->sidecar_files;
2782 auto sfd = static_cast<FileData *>(work->data);
2784 ret |= file_data_verify_ci(sfd, list);
2791 gchar *file_data_get_error_string(gint error)
2793 GString *result = g_string_new("");
2795 if (error & CHANGE_NO_SRC)
2797 if (result->len > 0) g_string_append(result, ", ");
2798 g_string_append(result, _("file or directory does not exist"));
2801 if (error & CHANGE_DEST_EXISTS)
2803 if (result->len > 0) g_string_append(result, ", ");
2804 g_string_append(result, _("destination already exists"));
2807 if (error & CHANGE_NO_WRITE_PERM_DEST)
2809 if (result->len > 0) g_string_append(result, ", ");
2810 g_string_append(result, _("destination can't be overwritten"));
2813 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2815 if (result->len > 0) g_string_append(result, ", ");
2816 g_string_append(result, _("destination directory is not writable"));
2819 if (error & CHANGE_NO_DEST_DIR)
2821 if (result->len > 0) g_string_append(result, ", ");
2822 g_string_append(result, _("destination directory does not exist"));
2825 if (error & CHANGE_NO_WRITE_PERM_DIR)
2827 if (result->len > 0) g_string_append(result, ", ");
2828 g_string_append(result, _("source directory is not writable"));
2831 if (error & CHANGE_NO_READ_PERM)
2833 if (result->len > 0) g_string_append(result, ", ");
2834 g_string_append(result, _("no read permission"));
2837 if (error & CHANGE_WARN_NO_WRITE_PERM)
2839 if (result->len > 0) g_string_append(result, ", ");
2840 g_string_append(result, _("file is readonly"));
2843 if (error & CHANGE_WARN_DEST_EXISTS)
2845 if (result->len > 0) g_string_append(result, ", ");
2846 g_string_append(result, _("destination already exists and will be overwritten"));
2849 if (error & CHANGE_WARN_SAME)
2851 if (result->len > 0) g_string_append(result, ", ");
2852 g_string_append(result, _("source and destination are the same"));
2855 if (error & CHANGE_WARN_CHANGED_EXT)
2857 if (result->len > 0) g_string_append(result, ", ");
2858 g_string_append(result, _("source and destination have different extension"));
2861 if (error & CHANGE_WARN_UNSAVED_META)
2863 if (result->len > 0) g_string_append(result, ", ");
2864 g_string_append(result, _("there are unsaved metadata changes for the file"));
2867 if (error & CHANGE_DUPLICATE_DEST)
2869 if (result->len > 0) g_string_append(result, ", ");
2870 g_string_append(result, _("another destination file has the same filename"));
2873 return g_string_free(result, FALSE);
2876 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2879 gint all_errors = 0;
2880 gint common_errors = ~0;
2885 if (!list) return 0;
2887 num = g_list_length(list);
2888 errors = g_new(int, num);
2896 fd = static_cast<FileData *>(work->data);
2899 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2900 all_errors |= error;
2901 common_errors &= error;
2908 if (desc && all_errors)
2911 GString *result = g_string_new("");
2915 gchar *str = file_data_get_error_string(common_errors);
2916 g_string_append(result, str);
2917 g_string_append(result, "\n");
2928 fd = static_cast<FileData *>(work->data);
2931 error = errors[i] & ~common_errors;
2935 gchar *str = file_data_get_error_string(error);
2936 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2941 *desc = g_string_free(result, FALSE);
2950 * perform the change described by FileFataChangeInfo
2951 * it is used for internal operations,
2952 * this function actually operates with files on the filesystem
2953 * it should implement safe delete
2956 static gboolean file_data_perform_move(FileData *fd)
2958 g_assert(!strcmp(fd->change->source, fd->path));
2959 return move_file(fd->change->source, fd->change->dest);
2962 static gboolean file_data_perform_copy(FileData *fd)
2964 g_assert(!strcmp(fd->change->source, fd->path));
2965 return copy_file(fd->change->source, fd->change->dest);
2968 static gboolean file_data_perform_delete(FileData *fd)
2970 if (isdir(fd->path) && !islink(fd->path))
2971 return rmdir_utf8(fd->path);
2973 if (options->file_ops.safe_delete_enable)
2974 return file_util_safe_unlink(fd->path);
2976 return unlink_file(fd->path);
2979 gboolean file_data_perform_ci(FileData *fd)
2981 /** @FIXME When a directory that is a symbolic link is deleted,
2982 * at this point fd->change is null because no FileDataChangeInfo
2983 * has been set up. Therefore there is a seg. fault.
2984 * This code simply aborts the delete.
2991 FileDataChangeType type = fd->change->type;
2995 case FILEDATA_CHANGE_MOVE:
2996 return file_data_perform_move(fd);
2997 case FILEDATA_CHANGE_COPY:
2998 return file_data_perform_copy(fd);
2999 case FILEDATA_CHANGE_RENAME:
3000 return file_data_perform_move(fd); /* the same as move */
3001 case FILEDATA_CHANGE_DELETE:
3002 return file_data_perform_delete(fd);
3003 case FILEDATA_CHANGE_WRITE_METADATA:
3004 return metadata_write_perform(fd);
3005 case FILEDATA_CHANGE_UNSPECIFIED:
3006 /* nothing to do here */
3014 gboolean file_data_sc_perform_ci(FileData *fd)
3017 gboolean ret = TRUE;
3018 FileDataChangeType type = fd->change->type;
3020 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3022 work = fd->sidecar_files;
3025 auto sfd = static_cast<FileData *>(work->data);
3027 if (!file_data_perform_ci(sfd)) ret = FALSE;
3031 if (!file_data_perform_ci(fd)) ret = FALSE;
3037 * updates FileData structure according to FileDataChangeInfo
3040 gboolean file_data_apply_ci(FileData *fd)
3042 FileDataChangeType type = fd->change->type;
3044 /** @FIXME delete ?*/
3045 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
3047 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
3048 file_data_planned_change_remove(fd);
3050 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
3052 /* this change overwrites another file which is already known to other modules
3053 renaming fd would create duplicate FileData structure
3054 the best thing we can do is nothing
3056 /** @FIXME maybe we could copy stuff like marks
3058 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
3062 file_data_set_path(fd, fd->change->dest);
3065 file_data_increment_version(fd);
3066 file_data_send_notification(fd, NOTIFY_CHANGE);
3071 gboolean file_data_sc_apply_ci(FileData *fd)
3074 FileDataChangeType type = fd->change->type;
3076 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3078 work = fd->sidecar_files;
3081 auto sfd = static_cast<FileData *>(work->data);
3083 file_data_apply_ci(sfd);
3087 file_data_apply_ci(fd);
3092 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
3095 if (fd->parent) fd = fd->parent;
3096 if (!g_list_find(list, fd)) return FALSE;
3098 work = fd->sidecar_files;
3101 if (!g_list_find(list, work->data)) return FALSE;
3107 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
3109 GList *out = nullptr;
3112 /* change partial groups to independent files */
3117 auto fd = static_cast<FileData *>(work->data);
3120 if (!file_data_list_contains_whole_group(list, fd))
3122 file_data_disable_grouping(fd, TRUE);
3125 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
3131 /* remove sidecars from the list,
3132 they can be still accessed via main_fd->sidecar_files */
3136 auto fd = static_cast<FileData *>(work->data);
3140 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
3142 out = g_list_prepend(out, file_data_ref(fd));
3146 filelist_free(list);
3147 out = g_list_reverse(out);
3157 * notify other modules about the change described by FileDataChangeInfo
3160 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks */
3161 /** @FIXME do we need the ignore_list? It looks like a workaround for ineffective
3162 implementation in view-file-list.cc */
3165 struct NotifyIdleData {
3172 FileDataNotifyFunc func;
3174 NotifyPriority priority;
3177 static GList *notify_func_list = nullptr;
3179 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
3181 auto nda = static_cast<const NotifyData *>(a);
3182 auto ndb = static_cast<const NotifyData *>(b);
3184 if (nda->priority < ndb->priority) return -1;
3185 if (nda->priority > ndb->priority) return 1;
3189 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
3192 GList *work = notify_func_list;
3196 auto nd = static_cast<NotifyData *>(work->data);
3198 if (nd->func == func && nd->data == data)
3200 g_warning("Notify func already registered");
3206 nd = g_new(NotifyData, 1);
3209 nd->priority = priority;
3211 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
3212 DEBUG_2("Notify func registered: %p", (void *)nd);
3217 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
3219 GList *work = notify_func_list;
3223 auto nd = static_cast<NotifyData *>(work->data);
3225 if (nd->func == func && nd->data == data)
3227 notify_func_list = g_list_delete_link(notify_func_list, work);
3228 DEBUG_2("Notify func unregistered: %p", (void *)nd);
3235 g_warning("Notify func not found");
3239 #pragma GCC diagnostic push
3240 #pragma GCC diagnostic ignored "-Wunused-function"
3241 gboolean file_data_send_notification_idle_cb_unused(gpointer data)
3243 NotifyIdleData *nid = (NotifyIdleData *)data;
3244 GList *work = notify_func_list;
3248 NotifyData *nd = (NotifyData *)work->data;
3250 nd->func(nid->fd, nid->type, nd->data);
3253 file_data_unref(nid->fd);
3257 #pragma GCC diagnostic pop
3259 void file_data_send_notification(FileData *fd, NotifyType type)
3261 GList *work = notify_func_list;
3265 auto nd = static_cast<NotifyData *>(work->data);
3267 nd->func(fd, type, nd->data);
3271 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3272 nid->fd = file_data_ref(fd);
3274 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3278 static GHashTable *file_data_monitor_pool = nullptr;
3279 static guint realtime_monitor_id = 0; /* event source id */
3281 static void realtime_monitor_check_cb(gpointer key, gpointer, gpointer)
3283 auto fd = static_cast<FileData *>(key);
3285 file_data_check_changed_files(fd);
3287 DEBUG_1("monitor %s", fd->path);
3290 static gboolean realtime_monitor_cb(gpointer)
3292 if (!options->update_on_time_change) return TRUE;
3293 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, nullptr);
3297 gboolean file_data_register_real_time_monitor(FileData *fd)
3303 if (!file_data_monitor_pool)
3304 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3306 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3308 DEBUG_1("Register realtime %d %s", count, fd->path);
3311 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3313 if (!realtime_monitor_id)
3315 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, nullptr);
3321 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3325 g_assert(file_data_monitor_pool);
3327 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3329 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3331 g_assert(count > 0);
3336 g_hash_table_remove(file_data_monitor_pool, fd);
3338 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3340 file_data_unref(fd);
3342 if (g_hash_table_size(file_data_monitor_pool) == 0)
3344 g_source_remove(realtime_monitor_id);
3345 realtime_monitor_id = 0;
3353 *-----------------------------------------------------------------------------
3354 * Saving marks list, clearing marks
3355 * Uses file_data_pool
3356 *-----------------------------------------------------------------------------
3359 static void marks_get_files(gpointer key, gpointer value, gpointer userdata)
3361 auto file_name = static_cast<gchar *>(key);
3362 auto result = static_cast<GString *>(userdata);
3365 if (isfile(file_name))
3367 fd = static_cast<FileData *>(value);
3368 if (fd && fd->marks > 0)
3370 g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks);
3375 gboolean marks_list_load(const gchar *path)
3383 pathl = path_from_utf8(path);
3384 f = fopen(pathl, "r");
3386 if (!f) return FALSE;
3388 /* first line must start with Marks comment */
3389 if (!fgets(s_buf, sizeof(s_buf), f) ||
3390 strncmp(s_buf, "#Marks", 6) != 0)
3396 while (fgets(s_buf, sizeof(s_buf), f))
3398 if (s_buf[0]=='#') continue;
3399 file_path = strtok(s_buf, ",");
3400 marks_value = strtok(nullptr, ",");
3401 if (isfile(file_path))
3403 FileData *fd = file_data_new_no_grouping(file_path);
3408 gint mark_no = 1 << n;
3409 if (atoi(marks_value) & mark_no)
3411 file_data_set_mark(fd, n , 1);
3422 gboolean marks_list_save(gchar *path, gboolean save)
3424 SecureSaveInfo *ssi;
3427 pathl = path_from_utf8(path);
3428 ssi = secure_open(pathl);
3432 log_printf(_("Error: Unable to write marks lists to: %s\n"), path);
3436 secure_fprintf(ssi, "#Marks lists\n");
3438 GString *marks = g_string_new("");
3441 g_hash_table_foreach(file_data_pool, marks_get_files, marks);
3443 secure_fprintf(ssi, "%s", marks->str);
3444 g_string_free(marks, TRUE);
3446 secure_fprintf(ssi, "#end\n");
3447 return (secure_close(ssi) == 0);
3450 static void marks_clear(gpointer key, gpointer value, gpointer)
3452 auto file_name = static_cast<gchar *>(key);
3457 if (isfile(file_name))
3459 fd = static_cast<FileData *>(value);
3460 if (fd && fd->marks > 0)
3466 if (fd->marks & mark_no)
3468 file_data_set_mark(fd, n , 0);
3476 void marks_clear_all()
3478 g_hash_table_foreach(file_data_pool, marks_clear, nullptr);
3481 void file_data_set_page_num(FileData *fd, gint page_num)
3483 if (fd->page_total > 1 && page_num < 0)
3485 fd->page_num = fd->page_total - 1;
3487 else if (fd->page_total > 1 && page_num <= fd->page_total)
3489 fd->page_num = page_num - 1;
3495 file_data_send_notification(fd, NOTIFY_REREAD);
3498 void file_data_inc_page_num(FileData *fd)
3500 if (fd->page_total > 0 && fd->page_num < fd->page_total - 1)
3502 fd->page_num = fd->page_num + 1;
3504 else if (fd->page_total == 0)
3506 fd->page_num = fd->page_num + 1;
3508 file_data_send_notification(fd, NOTIFY_REREAD);
3511 void file_data_dec_page_num(FileData *fd)
3513 if (fd->page_num > 0)
3515 fd->page_num = fd->page_num - 1;
3517 file_data_send_notification(fd, NOTIFY_REREAD);
3520 void file_data_set_page_total(FileData *fd, gint page_total)
3522 fd->page_total = page_total;
3525 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */