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;
53 static gboolean filelist_sort_case = TRUE;
56 *-----------------------------------------------------------------------------
57 * text conversion utils
58 *-----------------------------------------------------------------------------
61 gchar *text_from_size(gint64 size)
67 /* what I would like to use is printf("%'d", size)
68 * BUT: not supported on every libc :(
72 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
73 a = g_strdup_printf("%d%09d", static_cast<guint>(size / 1000000000), static_cast<guint>(size % 1000000000));
77 a = g_strdup_printf("%d", static_cast<guint>(size));
83 b = g_new(gchar, l + n + 1);
108 gchar *text_from_size_abrev(gint64 size)
110 if (size < static_cast<gint64>(1024))
112 return g_strdup_printf(_("%d bytes"), static_cast<gint>(size));
114 if (size < static_cast<gint64>(1048576))
116 return g_strdup_printf(_("%.1f KiB"), static_cast<gdouble>(size) / 1024.0);
118 if (size < static_cast<gint64>(1073741824))
120 return g_strdup_printf(_("%.1f MiB"), static_cast<gdouble>(size) / 1048576.0);
123 /* to avoid overflowing the gdouble, do division in two steps */
125 return g_strdup_printf(_("%.1f GiB"), static_cast<gdouble>(size) / 1024.0);
128 /* note: returned string is valid until next call to text_from_time() */
129 const gchar *text_from_time(time_t t)
131 static gchar *ret = nullptr;
135 GError *error = nullptr;
137 btime = localtime(&t);
139 /* the %x warning about 2 digit years is not an error */
140 buflen = strftime(buf, sizeof(buf), "%x %X", btime);
141 if (buflen < 1) return "";
144 ret = g_locale_to_utf8(buf, buflen, nullptr, nullptr, &error);
147 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
156 *-----------------------------------------------------------------------------
157 * changed files detection and notification
158 *-----------------------------------------------------------------------------
161 void file_data_increment_version(FileData *fd)
167 fd->parent->version++;
168 fd->parent->valid_marks = 0;
172 static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st)
174 if (fd->size != st->st_size ||
175 fd->date != st->st_mtime)
177 fd->size = st->st_size;
178 fd->date = st->st_mtime;
179 fd->cdate = st->st_ctime;
180 fd->mode = st->st_mode;
181 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
182 fd->thumb_pixbuf = nullptr;
183 file_data_increment_version(fd);
184 file_data_send_notification(fd, NOTIFY_REREAD);
190 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
192 gboolean ret = FALSE;
195 ret = file_data_check_changed_single_file(fd, st);
197 work = fd->sidecar_files;
200 auto sfd = static_cast<FileData *>(work->data);
204 if (!stat_utf8(sfd->path, &st))
209 file_data_disconnect_sidecar_file(fd, sfd);
211 file_data_increment_version(sfd);
212 file_data_send_notification(sfd, NOTIFY_REREAD);
213 file_data_unref(sfd);
217 ret |= file_data_check_changed_files_recursive(sfd, &st);
223 gboolean file_data_check_changed_files(FileData *fd)
225 gboolean ret = FALSE;
228 if (fd->parent) fd = fd->parent;
230 if (!stat_utf8(fd->path, &st))
234 FileData *sfd = nullptr;
236 /* parent is missing, we have to rebuild whole group */
241 /* file_data_disconnect_sidecar_file might delete the file,
242 we have to keep the reference to prevent this */
243 sidecars = filelist_copy(fd->sidecar_files);
248 sfd = static_cast<FileData *>(work->data);
251 file_data_disconnect_sidecar_file(fd, sfd);
253 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
254 /* now we can release the sidecars */
255 filelist_free(sidecars);
256 file_data_increment_version(fd);
257 file_data_send_notification(fd, NOTIFY_REREAD);
262 ret |= file_data_check_changed_files_recursive(fd, &st);
269 *-----------------------------------------------------------------------------
270 * file name, extension, sorting, ...
271 *-----------------------------------------------------------------------------
274 static void file_data_set_collate_keys(FileData *fd)
276 gchar *caseless_name;
279 valid_name = g_filename_display_name(fd->name);
280 caseless_name = g_utf8_casefold(valid_name, -1);
282 g_free(fd->collate_key_name);
283 g_free(fd->collate_key_name_nocase);
285 fd->collate_key_name_natural = g_utf8_collate_key_for_filename(fd->name, -1);
286 fd->collate_key_name_nocase_natural = g_utf8_collate_key_for_filename(caseless_name, -1);
287 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
288 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
291 g_free(caseless_name);
294 static void file_data_set_path(FileData *fd, const gchar *path)
296 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
297 g_assert(file_data_pool);
301 if (fd->original_path)
303 g_hash_table_remove(file_data_pool, fd->original_path);
304 g_free(fd->original_path);
307 g_assert(!g_hash_table_lookup(file_data_pool, path));
309 fd->original_path = g_strdup(path);
310 g_hash_table_insert(file_data_pool, fd->original_path, fd);
312 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
314 fd->path = g_strdup(path);
316 fd->extension = fd->name + 1;
317 file_data_set_collate_keys(fd);
321 fd->path = g_strdup(path);
322 fd->name = filename_from_path(fd->path);
324 if (strcmp(fd->name, "..") == 0)
326 gchar *dir = remove_level_from_path(path);
328 fd->path = remove_level_from_path(dir);
331 fd->extension = fd->name + 2;
332 file_data_set_collate_keys(fd);
335 else if (strcmp(fd->name, ".") == 0)
338 fd->path = remove_level_from_path(path);
340 fd->extension = fd->name + 1;
341 file_data_set_collate_keys(fd);
345 fd->extension = registered_extension_from_path(fd->path);
346 if (fd->extension == nullptr)
348 fd->extension = fd->name + strlen(fd->name);
351 fd->sidecar_priority = sidecar_file_priority(fd->extension);
352 file_data_set_collate_keys(fd);
356 *-----------------------------------------------------------------------------
357 * create or reuse Filedata
358 *-----------------------------------------------------------------------------
361 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
367 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
369 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
372 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
374 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
380 if (!fd && file_data_planned_change_hash)
382 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_planned_change_hash, path_utf8));
385 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
386 if (!isfile(fd->path))
389 file_data_apply_ci(fd);
400 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
402 #ifdef DEBUG_FILEDATA
405 file_data_check_changed_single_file(fd, st);
407 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
412 fd = g_new0(FileData, 1);
413 #ifdef DEBUG_FILEDATA
414 global_file_data_count++;
415 DEBUG_2("file data count++: %d", global_file_data_count);
418 fd->size = st->st_size;
419 fd->date = st->st_mtime;
420 fd->cdate = st->st_ctime;
421 fd->mode = st->st_mode;
423 fd->magick = FD_MAGICK;
425 fd->rating = STAR_RATING_NOT_READ;
426 fd->format_class = filter_file_get_class(path_utf8);
430 user = getpwuid(st->st_uid);
433 fd->owner = g_strdup_printf("%u", st->st_uid);
437 fd->owner = g_strdup(user->pw_name);
440 group = getgrgid(st->st_gid);
443 fd->group = g_strdup_printf("%u", st->st_gid);
447 fd->group = g_strdup(group->gr_name);
450 fd->sym_link = get_symbolic_link(path_utf8);
452 if (disable_sidecars) fd->disable_grouping = TRUE;
454 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
459 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
461 gchar *path_utf8 = path_to_utf8(path);
462 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
468 FileData *file_data_new_simple(const gchar *path_utf8)
473 if (!stat_utf8(path_utf8, &st))
479 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
480 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
489 void read_exif_time_data(FileData *file)
491 if (file->exifdate > 0)
493 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
504 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
505 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), (void *)file, file->path);
510 uint year, month, day, hour, min, sec;
512 sscanf(tmp, "%4u:%2u:%2u %2u:%2u:%2u", &year, &month, &day, &hour, &min, &sec);
513 time_str.tm_year = year - 1900;
514 time_str.tm_mon = month - 1;
515 time_str.tm_mday = day;
516 time_str.tm_hour = hour;
517 time_str.tm_min = min;
518 time_str.tm_sec = sec;
519 time_str.tm_isdst = 0;
521 file->exifdate = mktime(&time_str);
527 void read_exif_time_digitized_data(FileData *file)
529 if (file->exifdate_digitized > 0)
531 DEBUG_1("%s set_exif_time_digitized_data: Already exists for %s", get_exec_time(), file->path);
542 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeDigitized");
543 DEBUG_2("%s set_exif_time_digitized_data: reading %p %s", get_exec_time(), (void *)file, file->path);
548 uint year, month, day, hour, min, sec;
550 sscanf(tmp, "%4u:%2u:%2u %2u:%2u:%2u", &year, &month, &day, &hour, &min, &sec);
551 time_str.tm_year = year - 1900;
552 time_str.tm_mon = month - 1;
553 time_str.tm_mday = day;
554 time_str.tm_hour = hour;
555 time_str.tm_min = min;
556 time_str.tm_sec = sec;
557 time_str.tm_isdst = 0;
559 file->exifdate_digitized = mktime(&time_str);
565 void read_rating_data(FileData *file)
569 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
572 file->rating = atoi(rating_str);
581 #pragma GCC diagnostic push
582 #pragma GCC diagnostic ignored "-Wunused-function"
583 void set_exif_time_data_unused(GList *files)
585 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
589 FileData *file = static_cast<FileData *>(files->data);
591 read_exif_time_data(file);
596 void set_exif_time_digitized_data_unused(GList *files)
598 DEBUG_1("%s set_exif_time_digitized_data: ...", get_exec_time());
602 FileData *file = static_cast<FileData *>(files->data);
604 read_exif_time_digitized_data(file);
609 void set_rating_data_unused(GList *files)
612 DEBUG_1("%s set_rating_data: ...", get_exec_time());
616 FileData *file = static_cast<FileData *>(files->data);
617 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
620 file->rating = atoi(rating_str);
626 #pragma GCC diagnostic pop
628 FileData *file_data_new_no_grouping(const gchar *path_utf8)
632 if (!stat_utf8(path_utf8, &st))
638 return file_data_new(path_utf8, &st, TRUE);
641 FileData *file_data_new_dir(const gchar *path_utf8)
645 if (!stat_utf8(path_utf8, &st))
651 /* dir or non-existing yet */
652 g_assert(S_ISDIR(st.st_mode));
654 return file_data_new(path_utf8, &st, TRUE);
658 *-----------------------------------------------------------------------------
660 *-----------------------------------------------------------------------------
663 #ifdef DEBUG_FILEDATA
664 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
666 FileData *file_data_ref(FileData *fd)
669 if (fd == nullptr) return nullptr;
670 if (fd->magick != FD_MAGICK)
671 #ifdef DEBUG_FILEDATA
672 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, (void *)fd);
674 log_printf("Error: fd magick mismatch fd=%p", fd);
676 g_assert(fd->magick == FD_MAGICK);
679 #ifdef DEBUG_FILEDATA
680 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->path, file, line);
682 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
688 * @brief Print ref. count and image name
691 * Print image ref. count and full path name of all images in
692 * the file_data_pool.
694 * Used only by DEBUG_FD()
696 void file_data_dump()
698 #ifdef DEBUG_FILEDATA
704 list = g_hash_table_get_values(file_data_pool);
706 log_printf("%d", global_file_data_count);
707 log_printf("%d", g_list_length(list));
712 fd = static_cast<FileData *>(work->data);
713 log_printf("%-4d %s", fd->ref, fd->path);
722 static void file_data_free(FileData *fd)
724 g_assert(fd->magick == FD_MAGICK);
725 g_assert(fd->ref == 0);
726 g_assert(!fd->locked);
728 #ifdef DEBUG_FILEDATA
729 global_file_data_count--;
730 DEBUG_2("file data count--: %d", global_file_data_count);
733 metadata_cache_free(fd);
734 g_hash_table_remove(file_data_pool, fd->original_path);
737 g_free(fd->original_path);
738 g_free(fd->collate_key_name);
739 g_free(fd->collate_key_name_nocase);
740 g_free(fd->extended_extension);
741 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
742 histmap_free(fd->histmap);
745 g_free(fd->sym_link);
746 g_free(fd->format_name);
747 g_assert(fd->sidecar_files == nullptr); /* sidecar files must be freed before calling this */
749 file_data_change_info_free(nullptr, fd);
754 * @brief Checks if the FileData is referenced
756 * Checks the refcount and whether the FileData is locked.
758 static gboolean file_data_check_has_ref(FileData *fd)
760 return fd->ref > 0 || fd->locked;
764 * @brief Consider freeing a FileData.
766 * This function will free a FileData and its children provided that neither its parent nor it has
767 * a positive refcount, and provided that neither is locked.
769 static void file_data_consider_free(FileData *fd)
772 FileData *parent = fd->parent ? fd->parent : fd;
774 g_assert(fd->magick == FD_MAGICK);
775 if (file_data_check_has_ref(fd)) return;
776 if (file_data_check_has_ref(parent)) return;
778 work = parent->sidecar_files;
781 auto sfd = static_cast<FileData *>(work->data);
782 if (file_data_check_has_ref(sfd)) return;
786 /* Neither the parent nor the siblings are referenced, so we can free everything */
787 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
788 fd->path, fd->parent ? parent->path : "-");
790 g_list_free_full(parent->sidecar_files, reinterpret_cast<GDestroyNotify>(file_data_free));
791 parent->sidecar_files = nullptr;
793 file_data_free(parent);
796 #ifdef DEBUG_FILEDATA
797 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
799 void file_data_unref(FileData *fd)
802 if (fd == nullptr) return;
803 if (fd->magick != FD_MAGICK)
804 #ifdef DEBUG_FILEDATA
805 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, (void *)fd);
807 log_printf("Error: fd magick mismatch fd=%p", fd);
809 g_assert(fd->magick == FD_MAGICK);
812 #ifdef DEBUG_FILEDATA
813 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->locked, fd->path,
816 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
819 // Free FileData if it's no longer ref'd
820 file_data_consider_free(fd);
824 * @brief Lock the FileData in memory.
826 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
827 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
828 * even if the code would continue to function properly even if the FileData were freed. Code that
829 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
831 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
832 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
834 void file_data_lock(FileData *fd)
836 if (fd == nullptr) return;
837 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
839 g_assert(fd->magick == FD_MAGICK);
842 DEBUG_2("file_data_ref fd=%p (%d): '%s'", (void *)fd, fd->ref, fd->path);
846 * @brief Reset the maintain-FileData-in-memory lock
848 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
849 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
850 * keeping it from being freed.
852 void file_data_unlock(FileData *fd)
854 if (fd == nullptr) return;
855 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
857 g_assert(fd->magick == FD_MAGICK);
860 // Free FileData if it's no longer ref'd
861 file_data_consider_free(fd);
865 * @brief Lock all of the FileDatas in the provided list
867 * @see file_data_lock(#FileData)
869 void file_data_lock_list(GList *list)
876 auto fd = static_cast<FileData *>(work->data);
883 * @brief Unlock all of the FileDatas in the provided list
885 * @see #file_data_unlock(#FileData)
887 void file_data_unlock_list(GList *list)
894 auto fd = static_cast<FileData *>(work->data);
896 file_data_unlock(fd);
901 *-----------------------------------------------------------------------------
902 * sidecar file info struct
903 *-----------------------------------------------------------------------------
906 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
908 auto fda = static_cast<const FileData *>(a);
909 auto fdb = static_cast<const FileData *>(b);
911 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
912 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
914 return strcmp(fdb->extension, fda->extension);
918 static gint sidecar_file_priority(const gchar *extension)
923 if (extension == nullptr)
926 work = sidecar_ext_get_list();
929 auto ext = static_cast<gchar *>(work->data);
932 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
938 static void file_data_check_sidecars(const GList *basename_list)
940 /* basename_list contains the new group - first is the parent, then sorted sidecars */
941 /* all files in the list have ref count > 0 */
944 GList *s_work, *new_sidecars;
947 if (!basename_list) return;
950 DEBUG_2("basename start");
951 work = basename_list;
954 auto fd = static_cast<FileData *>(work->data);
956 g_assert(fd->magick == FD_MAGICK);
957 DEBUG_2("basename: %p %s", (void *)fd, fd->name);
960 g_assert(fd->parent->magick == FD_MAGICK);
961 DEBUG_2(" parent: %p", (void *)fd->parent);
963 s_work = fd->sidecar_files;
966 auto sfd = static_cast<FileData *>(s_work->data);
967 s_work = s_work->next;
968 g_assert(sfd->magick == FD_MAGICK);
969 DEBUG_2(" sidecar: %p %s", (void *)sfd, sfd->name);
972 g_assert(fd->parent == nullptr || fd->sidecar_files == nullptr);
975 parent_fd = static_cast<FileData *>(basename_list->data);
977 /* check if the second and next entries of basename_list are already connected
978 as sidecars of the first entry (parent_fd) */
979 work = basename_list->next;
980 s_work = parent_fd->sidecar_files;
982 while (work && s_work)
984 if (work->data != s_work->data) break;
986 s_work = s_work->next;
989 if (!work && !s_work)
991 DEBUG_2("basename no change");
992 return; /* no change in grouping */
995 /* we have to regroup it */
997 /* first, disconnect everything and send notification*/
999 work = basename_list;
1002 auto fd = static_cast<FileData *>(work->data);
1004 g_assert(fd->parent == nullptr || fd->sidecar_files == nullptr);
1008 FileData *old_parent = fd->parent;
1009 g_assert(old_parent->parent == nullptr || old_parent->sidecar_files == nullptr);
1010 file_data_ref(old_parent);
1011 file_data_disconnect_sidecar_file(old_parent, fd);
1012 file_data_send_notification(old_parent, NOTIFY_REREAD);
1013 file_data_unref(old_parent);
1016 while (fd->sidecar_files)
1018 auto sfd = static_cast<FileData *>(fd->sidecar_files->data);
1019 g_assert(sfd->parent == nullptr || sfd->sidecar_files == nullptr);
1021 file_data_disconnect_sidecar_file(fd, sfd);
1022 file_data_send_notification(sfd, NOTIFY_REREAD);
1023 file_data_unref(sfd);
1025 file_data_send_notification(fd, NOTIFY_GROUPING);
1027 g_assert(fd->parent == nullptr && fd->sidecar_files == nullptr);
1030 /* now we can form the new group */
1031 work = basename_list->next;
1032 new_sidecars = nullptr;
1035 auto sfd = static_cast<FileData *>(work->data);
1036 g_assert(sfd->magick == FD_MAGICK);
1037 g_assert(sfd->parent == nullptr && sfd->sidecar_files == nullptr);
1038 sfd->parent = parent_fd;
1039 new_sidecars = g_list_prepend(new_sidecars, sfd);
1042 g_assert(parent_fd->sidecar_files == nullptr);
1043 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
1044 DEBUG_1("basename group changed for %s", parent_fd->path);
1048 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
1050 g_assert(target->magick == FD_MAGICK);
1051 g_assert(sfd->magick == FD_MAGICK);
1052 g_assert(g_list_find(target->sidecar_files, sfd));
1054 file_data_ref(target);
1057 g_assert(sfd->parent == target);
1059 file_data_increment_version(sfd); /* increments both sfd and target */
1061 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
1062 sfd->parent = nullptr;
1063 g_free(sfd->extended_extension);
1064 sfd->extended_extension = nullptr;
1066 file_data_unref(target);
1067 file_data_unref(sfd);
1070 /* disables / enables grouping for particular file, sends UPDATE notification */
1071 void file_data_disable_grouping(FileData *fd, gboolean disable)
1073 if (!fd->disable_grouping == !disable) return;
1075 fd->disable_grouping = !!disable;
1081 FileData *parent = file_data_ref(fd->parent);
1082 file_data_disconnect_sidecar_file(parent, fd);
1083 file_data_send_notification(parent, NOTIFY_GROUPING);
1084 file_data_unref(parent);
1086 else if (fd->sidecar_files)
1088 GList *sidecar_files = filelist_copy(fd->sidecar_files);
1089 GList *work = sidecar_files;
1092 auto sfd = static_cast<FileData *>(work->data);
1094 file_data_disconnect_sidecar_file(fd, sfd);
1095 file_data_send_notification(sfd, NOTIFY_GROUPING);
1097 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
1098 filelist_free(sidecar_files);
1102 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
1107 file_data_increment_version(fd);
1108 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
1110 file_data_send_notification(fd, NOTIFY_GROUPING);
1113 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
1120 auto fd = static_cast<FileData *>(work->data);
1122 file_data_disable_grouping(fd, disable);
1130 *-----------------------------------------------------------------------------
1132 *-----------------------------------------------------------------------------
1136 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
1139 if (!filelist_sort_ascend)
1146 switch (filelist_sort_method)
1151 if (fa->size < fb->size) return -1;
1152 if (fa->size > fb->size) return 1;
1153 /* fall back to name */
1156 if (fa->date < fb->date) return -1;
1157 if (fa->date > fb->date) return 1;
1158 /* fall back to name */
1161 if (fa->cdate < fb->cdate) return -1;
1162 if (fa->cdate > fb->cdate) return 1;
1163 /* fall back to name */
1166 if (fa->exifdate < fb->exifdate) return -1;
1167 if (fa->exifdate > fb->exifdate) return 1;
1168 /* fall back to name */
1170 case SORT_EXIFTIMEDIGITIZED:
1171 if (fa->exifdate_digitized < fb->exifdate_digitized) return -1;
1172 if (fa->exifdate_digitized > fb->exifdate_digitized) return 1;
1173 /* fall back to name */
1176 if (fa->rating < fb->rating) return -1;
1177 if (fa->rating > fb->rating) return 1;
1178 /* fall back to name */
1181 if (fa->format_class < fb->format_class) return -1;
1182 if (fa->format_class > fb->format_class) return 1;
1183 /* fall back to name */
1186 ret = strcmp(fa->collate_key_name_natural, fb->collate_key_name_natural);
1187 if (ret != 0) return ret;
1188 /* fall back to name */
1194 if (filelist_sort_case)
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, gboolean case_sensitive, GCompareFunc cb)
1221 filelist_sort_method = method;
1222 filelist_sort_ascend = ascend;
1223 filelist_sort_case = case_sensitive;
1224 return g_list_sort(list, cb);
1227 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, gboolean case_sensitive, GCompareFunc cb)
1229 filelist_sort_method = method;
1230 filelist_sort_ascend = ascend;
1231 filelist_sort_case = case_sensitive;
1232 return g_list_insert_sorted(list, data, cb);
1235 GList *filelist_sort(GList *list, SortType method, gboolean ascend, gboolean case_sensitive)
1237 return filelist_sort_full(list, method, ascend, case_sensitive, reinterpret_cast<GCompareFunc>(filelist_sort_file_cb));
1240 #pragma GCC diagnostic push
1241 #pragma GCC diagnostic ignored "-Wunused-function"
1242 GList *filelist_insert_sort_unused(GList *list, FileData *fd, SortType method, gboolean ascend)
1244 return filelist_insert_sort_full(list, fd, method, ascend, ascend, (GCompareFunc) filelist_sort_file_cb);
1246 #pragma GCC diagnostic pop
1249 *-----------------------------------------------------------------------------
1250 * basename hash - grouping of sidecars in filelist
1251 *-----------------------------------------------------------------------------
1255 static GHashTable *file_data_basename_hash_new()
1257 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, nullptr);
1260 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1263 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1265 list = static_cast<GList *>(g_hash_table_lookup(basename_hash, basename));
1269 DEBUG_1("TG: basename_hash not found for %s",fd->path);
1270 const gchar *parent_extension = registered_extension_from_path(basename);
1272 if (parent_extension)
1274 DEBUG_1("TG: parent extension %s",parent_extension);
1275 gchar *parent_basename = g_strndup(basename, parent_extension - basename);
1276 DEBUG_1("TG: parent basename %s",parent_basename);
1277 auto parent_fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, basename));
1280 DEBUG_1("TG: parent fd found");
1281 list = static_cast<GList *>(g_hash_table_lookup(basename_hash, parent_basename));
1282 if (!g_list_find(list, parent_fd))
1284 DEBUG_1("TG: parent fd doesn't fit");
1285 g_free(parent_basename);
1291 basename = parent_basename;
1292 fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
1298 if (!g_list_find(list, fd))
1300 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1301 g_hash_table_insert(basename_hash, basename, list);
1310 static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
1312 file_data_basename_hash_insert(static_cast<GHashTable *>(basename_hash), static_cast<FileData *>(fd));
1315 static void file_data_basename_hash_remove_list(gpointer, gpointer value, gpointer)
1317 filelist_free(static_cast<GList *>(value));
1320 static void file_data_basename_hash_free(GHashTable *basename_hash)
1322 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, nullptr);
1323 g_hash_table_destroy(basename_hash);
1327 *-----------------------------------------------------------------------------
1328 * handling sidecars in filelist
1329 *-----------------------------------------------------------------------------
1332 static GList *filelist_filter_out_sidecars(GList *flist)
1334 GList *work = flist;
1335 GList *flist_filtered = nullptr;
1339 auto fd = static_cast<FileData *>(work->data);
1342 if (fd->parent) /* remove fd's that are children */
1343 file_data_unref(fd);
1345 flist_filtered = g_list_prepend(flist_filtered, fd);
1349 return flist_filtered;
1352 static void file_data_basename_hash_to_sidecars(gpointer, gpointer value, gpointer)
1354 auto basename_list = static_cast<GList *>(value);
1355 file_data_check_sidecars(basename_list);
1359 static gboolean is_hidden_file(const gchar *name)
1361 if (name[0] != '.') return FALSE;
1362 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1367 *-----------------------------------------------------------------------------
1368 * the main filelist function
1369 *-----------------------------------------------------------------------------
1372 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1377 GList *dlist = nullptr;
1378 GList *flist = nullptr;
1379 GList *xmp_files = nullptr;
1380 gint (*stat_func)(const gchar *path, struct stat *buf);
1381 GHashTable *basename_hash = nullptr;
1383 g_assert(files || dirs);
1385 if (files) *files = nullptr;
1386 if (dirs) *dirs = nullptr;
1388 pathl = path_from_utf8(dir_path);
1389 if (!pathl) return FALSE;
1391 dp = opendir(pathl);
1398 if (files) basename_hash = file_data_basename_hash_new();
1400 if (follow_symlinks)
1405 while ((dir = readdir(dp)) != nullptr)
1407 struct stat ent_sbuf;
1408 const gchar *name = dir->d_name;
1411 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1414 filepath = g_build_filename(pathl, name, NULL);
1415 if (stat_func(filepath, &ent_sbuf) >= 0)
1417 if (S_ISDIR(ent_sbuf.st_mode))
1419 /* we ignore the .thumbnails dir for cleanliness */
1421 (name[0] != '.' || (name[1] != '\0' && (name[1] != '.' || name[2] != '\0'))) &&
1422 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1423 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1424 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1426 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1431 if (files && filter_name_exists(name))
1433 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1434 flist = g_list_prepend(flist, fd);
1435 if (fd->sidecar_priority && !fd->disable_grouping)
1437 if (strcmp(fd->extension, ".xmp") != 0)
1438 file_data_basename_hash_insert(basename_hash, fd);
1440 xmp_files = g_list_append(xmp_files, fd);
1447 if (errno == EOVERFLOW)
1449 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1461 g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
1462 g_list_free(xmp_files);
1465 if (dirs) *dirs = dlist;
1469 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, nullptr);
1471 *files = filelist_filter_out_sidecars(flist);
1473 if (basename_hash) file_data_basename_hash_free(basename_hash);
1478 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1480 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1483 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1485 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1488 FileData *file_data_new_group(const gchar *path_utf8)
1495 if (!file_data_pool)
1497 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
1500 if (!stat_utf8(path_utf8, &st))
1506 if (S_ISDIR(st.st_mode))
1507 return file_data_new(path_utf8, &st, TRUE);
1509 dir = remove_level_from_path(path_utf8);
1511 filelist_read_real(dir, &files, nullptr, TRUE);
1513 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
1514 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1520 filelist_free(files);
1526 void filelist_free(GList *list)
1533 file_data_unref((FileData *)work->data);
1541 GList *filelist_copy(GList *list)
1543 GList *new_list = nullptr;
1545 for (GList *work = list; work; work = work->next)
1547 auto fd = static_cast<FileData *>(work->data);
1549 new_list = g_list_prepend(new_list, file_data_ref(fd));
1552 return g_list_reverse(new_list);
1555 GList *filelist_from_path_list(GList *list)
1557 GList *new_list = nullptr;
1565 path = static_cast<gchar *>(work->data);
1568 new_list = g_list_prepend(new_list, file_data_new_group(path));
1571 return g_list_reverse(new_list);
1574 GList *filelist_to_path_list(GList *list)
1576 GList *new_list = nullptr;
1584 fd = static_cast<FileData *>(work->data);
1587 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1590 return g_list_reverse(new_list);
1593 GList *filelist_filter(GList *list, gboolean is_dir_list)
1597 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1602 auto fd = static_cast<FileData *>(work->data);
1603 const gchar *name = fd->name;
1607 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1608 (!is_dir_list && !filter_name_exists(name)) ||
1609 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1610 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1612 list = g_list_remove_link(list, link);
1613 file_data_unref(fd);
1622 *-----------------------------------------------------------------------------
1623 * filelist recursive
1624 *-----------------------------------------------------------------------------
1627 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1629 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1632 GList *filelist_sort_path(GList *list)
1634 return g_list_sort(list, filelist_sort_path_cb);
1637 static void filelist_recursive_append(GList **list, GList *dirs)
1644 auto fd = static_cast<FileData *>(work->data);
1648 if (filelist_read(fd, &f, &d))
1650 f = filelist_filter(f, FALSE);
1651 f = filelist_sort_path(f);
1652 *list = g_list_concat(*list, f);
1654 d = filelist_filter(d, TRUE);
1655 d = filelist_sort_path(d);
1656 filelist_recursive_append(list, d);
1664 static void filelist_recursive_append_full(GList **list, GList *dirs, SortType method, gboolean ascend, gboolean case_sensitive)
1671 auto fd = static_cast<FileData *>(work->data);
1675 if (filelist_read(fd, &f, &d))
1677 f = filelist_filter(f, FALSE);
1678 f = filelist_sort_full(f, method, ascend, case_sensitive, reinterpret_cast<GCompareFunc>(filelist_sort_file_cb));
1679 *list = g_list_concat(*list, f);
1681 d = filelist_filter(d, TRUE);
1682 d = filelist_sort_path(d);
1683 filelist_recursive_append_full(list, d, method, ascend, case_sensitive);
1691 GList *filelist_recursive(FileData *dir_fd)
1696 if (!filelist_read(dir_fd, &list, &d)) return nullptr;
1697 list = filelist_filter(list, FALSE);
1698 list = filelist_sort_path(list);
1700 d = filelist_filter(d, TRUE);
1701 d = filelist_sort_path(d);
1702 filelist_recursive_append(&list, d);
1708 GList *filelist_recursive_full(FileData *dir_fd, SortType method, gboolean ascend, gboolean case_sensitive)
1713 if (!filelist_read(dir_fd, &list, &d)) return nullptr;
1714 list = filelist_filter(list, FALSE);
1715 list = filelist_sort_full(list, method, ascend, case_sensitive, reinterpret_cast<GCompareFunc>(filelist_sort_file_cb));
1717 d = filelist_filter(d, TRUE);
1718 d = filelist_sort_path(d);
1719 filelist_recursive_append_full(&list, d, method, ascend, case_sensitive);
1726 *-----------------------------------------------------------------------------
1727 * file modification support
1728 *-----------------------------------------------------------------------------
1732 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1734 if (!fdci && fd) fdci = fd->change;
1738 g_free(fdci->source);
1743 if (fd) fd->change = nullptr;
1746 static gboolean file_data_can_write_directly(FileData *fd)
1748 return filter_name_is_writable(fd->extension);
1751 static gboolean file_data_can_write_sidecar(FileData *fd)
1753 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1756 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1758 gchar *sidecar_path = nullptr;
1761 if (!file_data_can_write_sidecar(fd)) return nullptr;
1763 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1764 gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1767 auto sfd = static_cast<FileData *>(work->data);
1769 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1771 sidecar_path = g_strdup(sfd->path);
1775 g_free(extended_extension);
1777 if (!existing_only && !sidecar_path)
1779 if (options->metadata.sidecar_extended_name)
1780 sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1783 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1784 sidecar_path = g_strconcat(base, ".xmp", NULL);
1789 return sidecar_path;
1793 * marks and orientation
1796 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1797 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1798 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1799 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1801 gboolean file_data_get_mark(FileData *fd, gint n)
1803 gboolean valid = (fd->valid_marks & (1 << n));
1805 if (file_data_get_mark_func[n] && !valid)
1807 guint old = fd->marks;
1808 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1810 if (!value != !(fd->marks & (1 << n)))
1812 fd->marks = fd->marks ^ (1 << n);
1815 fd->valid_marks |= (1 << n);
1816 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1818 file_data_unref(fd);
1820 else if (!old && fd->marks)
1826 return !!(fd->marks & (1 << n));
1829 guint file_data_get_marks(FileData *fd)
1832 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1836 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1839 if (!value == !file_data_get_mark(fd, n)) return;
1841 if (file_data_set_mark_func[n])
1843 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1848 fd->marks = fd->marks ^ (1 << n);
1850 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1852 file_data_unref(fd);
1854 else if (!old && fd->marks)
1859 file_data_increment_version(fd);
1860 file_data_send_notification(fd, NOTIFY_MARKS);
1863 gboolean file_data_filter_marks(FileData *fd, guint filter)
1866 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1867 return ((fd->marks & filter) == filter);
1870 GList *file_data_filter_marks_list(GList *list, guint filter)
1877 auto fd = static_cast<FileData *>(work->data);
1881 if (!file_data_filter_marks(fd, filter))
1883 list = g_list_remove_link(list, link);
1884 file_data_unref(fd);
1892 gboolean file_data_filter_file_filter(FileData *fd, GRegex *filter)
1894 return g_regex_match(filter, fd->name, static_cast<GRegexMatchFlags>(0), nullptr);
1897 GList *file_data_filter_file_filter_list(GList *list, GRegex *filter)
1904 auto fd = static_cast<FileData *>(work->data);
1908 if (!file_data_filter_file_filter(fd, filter))
1910 list = g_list_remove_link(list, link);
1911 file_data_unref(fd);
1919 static gboolean file_data_filter_class(FileData *fd, guint filter)
1923 for (i = 0; i < FILE_FORMAT_CLASSES; i++)
1925 if (filter & (1 << i))
1927 if (static_cast<FileFormatClass>(i) == filter_file_get_class(fd->path))
1937 GList *file_data_filter_class_list(GList *list, guint filter)
1944 auto fd = static_cast<FileData *>(work->data);
1948 if (!file_data_filter_class(fd, filter))
1950 list = g_list_remove_link(list, link);
1951 file_data_unref(fd);
1959 static void file_data_notify_mark_func(gpointer, gpointer value, gpointer)
1961 auto fd = static_cast<FileData *>(value);
1962 file_data_increment_version(fd);
1963 file_data_send_notification(fd, NOTIFY_MARKS);
1966 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1968 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1970 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1972 file_data_get_mark_func[n] = get_mark_func;
1973 file_data_set_mark_func[n] = set_mark_func;
1974 file_data_mark_func_data[n] = data;
1975 file_data_destroy_mark_func[n] = notify;
1977 if (get_mark_func && file_data_pool)
1979 /* this effectively changes all known files */
1980 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, nullptr);
1986 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1988 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1989 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1990 if (data) *data = file_data_mark_func_data[n];
1993 #pragma GCC diagnostic push
1994 #pragma GCC diagnostic ignored "-Wunused-function"
1995 gint file_data_get_user_orientation_unused(FileData *fd)
1997 return fd->user_orientation;
2000 void file_data_set_user_orientation_unused(FileData *fd, gint value)
2002 if (fd->user_orientation == value) return;
2004 fd->user_orientation = value;
2005 file_data_increment_version(fd);
2006 file_data_send_notification(fd, NOTIFY_ORIENTATION);
2008 #pragma GCC diagnostic pop
2012 * file_data - operates on the given fd
2013 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
2017 /* return list of sidecar file extensions in a string */
2018 gchar *file_data_sc_list_to_string(FileData *fd)
2021 GString *result = g_string_new("");
2023 work = fd->sidecar_files;
2026 auto sfd = static_cast<FileData *>(work->data);
2028 result = g_string_append(result, "+ ");
2029 result = g_string_append(result, sfd->extension);
2031 if (work) result = g_string_append_c(result, ' ');
2034 return g_string_free(result, FALSE);
2040 * add FileDataChangeInfo (see typedefs.h) for the given operation
2041 * uses file_data_add_change_info
2043 * fails if the fd->change already exists - change operations can't run in parallel
2044 * fd->change_info works as a lock
2046 * dest can be NULL - in this case the current name is used for now, it will
2051 FileDataChangeInfo types:
2053 MOVE - path is changed, name may be changed too
2054 RENAME - path remains unchanged, name is changed
2055 extension should remain (FIXME should we allow editing extension? it will make problems with grouping)
2056 sidecar names are changed too, extensions are not changed
2058 UPDATE - file size, date or grouping has been changed
2061 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
2063 FileDataChangeInfo *fdci;
2065 if (fd->change) return FALSE;
2067 fdci = g_new0(FileDataChangeInfo, 1);
2072 fdci->source = g_strdup(src);
2074 fdci->source = g_strdup(fd->path);
2077 fdci->dest = g_strdup(dest);
2084 static void file_data_planned_change_remove(FileData *fd)
2086 if (file_data_planned_change_hash &&
2087 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
2089 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
2091 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
2092 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
2093 file_data_unref(fd);
2094 if (g_hash_table_size(file_data_planned_change_hash) == 0)
2096 g_hash_table_destroy(file_data_planned_change_hash);
2097 file_data_planned_change_hash = nullptr;
2098 DEBUG_1("planned change: empty");
2105 void file_data_free_ci(FileData *fd)
2107 FileDataChangeInfo *fdci = fd->change;
2111 file_data_planned_change_remove(fd);
2113 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
2115 g_free(fdci->source);
2120 fd->change = nullptr;
2123 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
2125 FileDataChangeInfo *fdci = fd->change;
2127 fdci->regroup_when_finished = enable;
2130 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
2134 if (fd->parent) fd = fd->parent;
2136 if (fd->change) return FALSE;
2138 work = fd->sidecar_files;
2141 auto sfd = static_cast<FileData *>(work->data);
2143 if (sfd->change) return FALSE;
2147 file_data_add_ci(fd, type, nullptr, nullptr);
2149 work = fd->sidecar_files;
2152 auto sfd = static_cast<FileData *>(work->data);
2154 file_data_add_ci(sfd, type, nullptr, nullptr);
2161 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
2165 if (fd->parent) fd = fd->parent;
2167 if (!fd->change || fd->change->type != type) return FALSE;
2169 work = fd->sidecar_files;
2172 auto sfd = static_cast<FileData *>(work->data);
2174 if (!sfd->change || sfd->change->type != type) return FALSE;
2182 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
2184 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
2185 file_data_sc_update_ci_copy(fd, dest_path);
2189 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
2191 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
2192 file_data_sc_update_ci_move(fd, dest_path);
2196 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
2198 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
2199 file_data_sc_update_ci_rename(fd, dest_path);
2203 gboolean file_data_sc_add_ci_delete(FileData *fd)
2205 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
2208 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
2210 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
2211 file_data_sc_update_ci_unspecified(fd, dest_path);
2215 gboolean file_data_add_ci_write_metadata(FileData *fd)
2217 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, nullptr, nullptr);
2220 void file_data_sc_free_ci(FileData *fd)
2224 if (fd->parent) fd = fd->parent;
2226 file_data_free_ci(fd);
2228 work = fd->sidecar_files;
2231 auto sfd = static_cast<FileData *>(work->data);
2233 file_data_free_ci(sfd);
2238 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2241 gboolean ret = TRUE;
2246 auto fd = static_cast<FileData *>(work->data);
2248 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2255 static void file_data_sc_revert_ci_list(GList *fd_list)
2262 auto fd = static_cast<FileData *>(work->data);
2264 file_data_sc_free_ci(fd);
2269 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2276 auto fd = static_cast<FileData *>(work->data);
2278 if (!func(fd, dest))
2280 file_data_sc_revert_ci_list(work->prev);
2289 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2291 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2294 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2296 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2299 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2301 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2304 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2306 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2309 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2312 gboolean ret = TRUE;
2317 auto fd = static_cast<FileData *>(work->data);
2319 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2326 void file_data_free_ci_list(GList *fd_list)
2333 auto fd = static_cast<FileData *>(work->data);
2335 file_data_free_ci(fd);
2340 void file_data_sc_free_ci_list(GList *fd_list)
2347 auto fd = static_cast<FileData *>(work->data);
2349 file_data_sc_free_ci(fd);
2355 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2356 * fails if fd->change does not exist or the change type does not match
2359 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2361 FileDataChangeType type = fd->change->type;
2363 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2367 if (!file_data_planned_change_hash)
2368 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2370 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2372 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2373 g_hash_table_remove(file_data_planned_change_hash, old_path);
2374 file_data_unref(fd);
2377 ofd = static_cast<FileData *>(g_hash_table_lookup(file_data_planned_change_hash, new_path));
2382 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2383 g_hash_table_remove(file_data_planned_change_hash, new_path);
2384 file_data_unref(ofd);
2387 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2389 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2394 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2396 gchar *old_path = fd->change->dest;
2398 fd->change->dest = g_strdup(dest_path);
2399 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2403 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2405 const gchar *extension = registered_extension_from_path(fd->change->source);
2406 gchar *base = remove_extension_from_path(dest_path);
2407 gchar *old_path = fd->change->dest;
2409 fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2410 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2416 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2419 gchar *dest_path_full = nullptr;
2421 if (fd->parent) fd = fd->parent;
2425 dest_path = fd->path;
2427 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2429 gchar *dir = remove_level_from_path(fd->path);
2431 dest_path_full = g_build_filename(dir, dest_path, NULL);
2433 dest_path = dest_path_full;
2435 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2437 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2438 dest_path = dest_path_full;
2441 file_data_update_ci_dest(fd, dest_path);
2443 work = fd->sidecar_files;
2446 auto sfd = static_cast<FileData *>(work->data);
2448 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2452 g_free(dest_path_full);
2455 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2457 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2458 file_data_sc_update_ci(fd, dest_path);
2462 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2464 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2467 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2469 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2472 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2474 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2477 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2479 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2482 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2484 gboolean (*func)(FileData *, const gchar *))
2487 gboolean ret = TRUE;
2492 auto fd = static_cast<FileData *>(work->data);
2494 if (!func(fd, dest)) ret = FALSE;
2501 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2503 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2506 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2508 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2511 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2513 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2518 * verify source and dest paths - dest image exists, etc.
2519 * it should detect all possible problems with the planned operation
2522 gint file_data_verify_ci(FileData *fd, GList *list)
2524 gint ret = CHANGE_OK;
2526 GList *work = nullptr;
2527 FileData *fd1 = nullptr;
2531 DEBUG_1("Change checked: no change info: %s", fd->path);
2535 if (!isname(fd->path))
2537 /* this probably should not happen */
2538 ret |= CHANGE_NO_SRC;
2539 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2543 dir = remove_level_from_path(fd->path);
2545 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2546 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2547 fd->change->type != FILEDATA_CHANGE_RENAME &&
2548 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2551 ret |= CHANGE_WARN_UNSAVED_META;
2552 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2555 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2556 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2557 !access_file(fd->path, R_OK))
2559 ret |= CHANGE_NO_READ_PERM;
2560 DEBUG_1("Change checked: no read permission: %s", fd->path);
2562 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2563 !access_file(dir, W_OK))
2565 ret |= CHANGE_NO_WRITE_PERM_DIR;
2566 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2568 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2569 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2570 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2571 !access_file(fd->path, W_OK))
2573 ret |= CHANGE_WARN_NO_WRITE_PERM;
2574 DEBUG_1("Change checked: no write permission: %s", fd->path);
2576 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2577 - that means that there are no hard errors and warnings can be disabled
2578 - the destination is determined during the check
2580 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2582 /* determine destination file */
2583 gboolean have_dest = FALSE;
2584 gchar *dest_dir = nullptr;
2586 if (options->metadata.save_in_image_file)
2588 if (file_data_can_write_directly(fd))
2590 /* we can write the file directly */
2591 if (access_file(fd->path, W_OK))
2597 if (options->metadata.warn_on_write_problems)
2599 ret |= CHANGE_WARN_NO_WRITE_PERM;
2600 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2604 else if (file_data_can_write_sidecar(fd))
2606 /* we can write sidecar */
2607 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2608 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2610 file_data_update_ci_dest(fd, sidecar);
2615 if (options->metadata.warn_on_write_problems)
2617 ret |= CHANGE_WARN_NO_WRITE_PERM;
2618 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2627 /* write private metadata file under ~/.geeqie */
2629 /* If an existing metadata file exists, we will try writing to
2630 * it's location regardless of the user's preference.
2632 gchar *metadata_path = nullptr;
2634 /* but ignore XMP if we are not able to write it */
2635 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2637 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2639 if (metadata_path && !access_file(metadata_path, W_OK))
2641 g_free(metadata_path);
2642 metadata_path = nullptr;
2649 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2650 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2652 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2654 metadata_path = g_build_filename(dest_dir, filename, NULL);
2658 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2660 file_data_update_ci_dest(fd, metadata_path);
2664 ret |= CHANGE_NO_WRITE_PERM_DEST;
2665 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2667 g_free(metadata_path);
2672 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2677 same = (strcmp(fd->path, fd->change->dest) == 0);
2681 const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2682 if (!dest_ext) dest_ext = "";
2683 if (!options->file_filter.disable_file_extension_checks)
2685 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2687 ret |= CHANGE_WARN_CHANGED_EXT;
2688 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2694 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /** @FIXME this is now needed for running editors */
2696 ret |= CHANGE_WARN_SAME;
2697 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2701 dest_dir = remove_level_from_path(fd->change->dest);
2703 if (!isdir(dest_dir))
2705 ret |= CHANGE_NO_DEST_DIR;
2706 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2708 else if (!access_file(dest_dir, W_OK))
2710 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2711 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2715 if (isfile(fd->change->dest))
2717 if (!access_file(fd->change->dest, W_OK))
2719 ret |= CHANGE_NO_WRITE_PERM_DEST;
2720 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2724 ret |= CHANGE_WARN_DEST_EXISTS;
2725 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2728 else if (isdir(fd->change->dest))
2730 ret |= CHANGE_DEST_EXISTS;
2731 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2738 /* During a rename operation, check if another planned destination file has
2741 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2742 fd->change->type == FILEDATA_CHANGE_COPY ||
2743 fd->change->type == FILEDATA_CHANGE_MOVE)
2748 fd1 = static_cast<FileData *>(work->data);
2750 if (fd1 != nullptr && fd != fd1 )
2752 if (!strcmp(fd->change->dest, fd1->change->dest))
2754 ret |= CHANGE_DUPLICATE_DEST;
2760 fd->change->error = ret;
2761 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2768 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2773 ret = file_data_verify_ci(fd, list);
2775 work = fd->sidecar_files;
2778 auto sfd = static_cast<FileData *>(work->data);
2780 ret |= file_data_verify_ci(sfd, list);
2787 gchar *file_data_get_error_string(gint error)
2789 GString *result = g_string_new("");
2791 if (error & CHANGE_NO_SRC)
2793 if (result->len > 0) g_string_append(result, ", ");
2794 g_string_append(result, _("file or directory does not exist"));
2797 if (error & CHANGE_DEST_EXISTS)
2799 if (result->len > 0) g_string_append(result, ", ");
2800 g_string_append(result, _("destination already exists"));
2803 if (error & CHANGE_NO_WRITE_PERM_DEST)
2805 if (result->len > 0) g_string_append(result, ", ");
2806 g_string_append(result, _("destination can't be overwritten"));
2809 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2811 if (result->len > 0) g_string_append(result, ", ");
2812 g_string_append(result, _("destination directory is not writable"));
2815 if (error & CHANGE_NO_DEST_DIR)
2817 if (result->len > 0) g_string_append(result, ", ");
2818 g_string_append(result, _("destination directory does not exist"));
2821 if (error & CHANGE_NO_WRITE_PERM_DIR)
2823 if (result->len > 0) g_string_append(result, ", ");
2824 g_string_append(result, _("source directory is not writable"));
2827 if (error & CHANGE_NO_READ_PERM)
2829 if (result->len > 0) g_string_append(result, ", ");
2830 g_string_append(result, _("no read permission"));
2833 if (error & CHANGE_WARN_NO_WRITE_PERM)
2835 if (result->len > 0) g_string_append(result, ", ");
2836 g_string_append(result, _("file is readonly"));
2839 if (error & CHANGE_WARN_DEST_EXISTS)
2841 if (result->len > 0) g_string_append(result, ", ");
2842 g_string_append(result, _("destination already exists and will be overwritten"));
2845 if (error & CHANGE_WARN_SAME)
2847 if (result->len > 0) g_string_append(result, ", ");
2848 g_string_append(result, _("source and destination are the same"));
2851 if (error & CHANGE_WARN_CHANGED_EXT)
2853 if (result->len > 0) g_string_append(result, ", ");
2854 g_string_append(result, _("source and destination have different extension"));
2857 if (error & CHANGE_WARN_UNSAVED_META)
2859 if (result->len > 0) g_string_append(result, ", ");
2860 g_string_append(result, _("there are unsaved metadata changes for the file"));
2863 if (error & CHANGE_DUPLICATE_DEST)
2865 if (result->len > 0) g_string_append(result, ", ");
2866 g_string_append(result, _("another destination file has the same filename"));
2869 return g_string_free(result, FALSE);
2872 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2875 gint all_errors = 0;
2876 gint common_errors = ~0;
2881 if (!list) return 0;
2883 num = g_list_length(list);
2884 errors = g_new(int, num);
2892 fd = static_cast<FileData *>(work->data);
2895 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2896 all_errors |= error;
2897 common_errors &= error;
2904 if (desc && all_errors)
2907 GString *result = g_string_new("");
2911 gchar *str = file_data_get_error_string(common_errors);
2912 g_string_append(result, str);
2913 g_string_append(result, "\n");
2924 fd = static_cast<FileData *>(work->data);
2927 error = errors[i] & ~common_errors;
2931 gchar *str = file_data_get_error_string(error);
2932 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2937 *desc = g_string_free(result, FALSE);
2946 * perform the change described by FileFataChangeInfo
2947 * it is used for internal operations,
2948 * this function actually operates with files on the filesystem
2949 * it should implement safe delete
2952 static gboolean file_data_perform_move(FileData *fd)
2954 g_assert(!strcmp(fd->change->source, fd->path));
2955 return move_file(fd->change->source, fd->change->dest);
2958 static gboolean file_data_perform_copy(FileData *fd)
2960 g_assert(!strcmp(fd->change->source, fd->path));
2961 return copy_file(fd->change->source, fd->change->dest);
2964 static gboolean file_data_perform_delete(FileData *fd)
2966 if (isdir(fd->path) && !islink(fd->path))
2967 return rmdir_utf8(fd->path);
2969 if (options->file_ops.safe_delete_enable)
2970 return file_util_safe_unlink(fd->path);
2972 return unlink_file(fd->path);
2975 gboolean file_data_perform_ci(FileData *fd)
2977 /** @FIXME When a directory that is a symbolic link is deleted,
2978 * at this point fd->change is null because no FileDataChangeInfo
2979 * has been set up. Therefore there is a seg. fault.
2980 * This code simply aborts the delete.
2987 FileDataChangeType type = fd->change->type;
2991 case FILEDATA_CHANGE_MOVE:
2992 return file_data_perform_move(fd);
2993 case FILEDATA_CHANGE_COPY:
2994 return file_data_perform_copy(fd);
2995 case FILEDATA_CHANGE_RENAME:
2996 return file_data_perform_move(fd); /* the same as move */
2997 case FILEDATA_CHANGE_DELETE:
2998 return file_data_perform_delete(fd);
2999 case FILEDATA_CHANGE_WRITE_METADATA:
3000 return metadata_write_perform(fd);
3001 case FILEDATA_CHANGE_UNSPECIFIED:
3002 /* nothing to do here */
3010 gboolean file_data_sc_perform_ci(FileData *fd)
3013 gboolean ret = TRUE;
3014 FileDataChangeType type = fd->change->type;
3016 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3018 work = fd->sidecar_files;
3021 auto sfd = static_cast<FileData *>(work->data);
3023 if (!file_data_perform_ci(sfd)) ret = FALSE;
3027 if (!file_data_perform_ci(fd)) ret = FALSE;
3033 * updates FileData structure according to FileDataChangeInfo
3036 gboolean file_data_apply_ci(FileData *fd)
3038 FileDataChangeType type = fd->change->type;
3040 /** @FIXME delete ?*/
3041 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
3043 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
3044 file_data_planned_change_remove(fd);
3046 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
3048 /* this change overwrites another file which is already known to other modules
3049 renaming fd would create duplicate FileData structure
3050 the best thing we can do is nothing
3052 /** @FIXME maybe we could copy stuff like marks
3054 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
3058 file_data_set_path(fd, fd->change->dest);
3061 file_data_increment_version(fd);
3062 file_data_send_notification(fd, NOTIFY_CHANGE);
3067 gboolean file_data_sc_apply_ci(FileData *fd)
3070 FileDataChangeType type = fd->change->type;
3072 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3074 work = fd->sidecar_files;
3077 auto sfd = static_cast<FileData *>(work->data);
3079 file_data_apply_ci(sfd);
3083 file_data_apply_ci(fd);
3088 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
3091 if (fd->parent) fd = fd->parent;
3092 if (!g_list_find(list, fd)) return FALSE;
3094 work = fd->sidecar_files;
3097 if (!g_list_find(list, work->data)) return FALSE;
3103 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
3105 GList *out = nullptr;
3108 /* change partial groups to independent files */
3113 auto fd = static_cast<FileData *>(work->data);
3116 if (!file_data_list_contains_whole_group(list, fd))
3118 file_data_disable_grouping(fd, TRUE);
3121 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
3127 /* remove sidecars from the list,
3128 they can be still accessed via main_fd->sidecar_files */
3132 auto fd = static_cast<FileData *>(work->data);
3136 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
3138 out = g_list_prepend(out, file_data_ref(fd));
3142 filelist_free(list);
3143 out = g_list_reverse(out);
3153 * notify other modules about the change described by FileDataChangeInfo
3156 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks */
3157 /** @FIXME do we need the ignore_list? It looks like a workaround for ineffective
3158 implementation in view-file-list.cc */
3161 struct NotifyIdleData {
3168 FileDataNotifyFunc func;
3170 NotifyPriority priority;
3173 static GList *notify_func_list = nullptr;
3175 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
3177 auto nda = static_cast<const NotifyData *>(a);
3178 auto ndb = static_cast<const NotifyData *>(b);
3180 if (nda->priority < ndb->priority) return -1;
3181 if (nda->priority > ndb->priority) return 1;
3185 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
3188 GList *work = notify_func_list;
3192 auto nd = static_cast<NotifyData *>(work->data);
3194 if (nd->func == func && nd->data == data)
3196 g_warning("Notify func already registered");
3202 nd = g_new(NotifyData, 1);
3205 nd->priority = priority;
3207 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
3208 DEBUG_2("Notify func registered: %p", (void *)nd);
3213 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
3215 GList *work = notify_func_list;
3219 auto nd = static_cast<NotifyData *>(work->data);
3221 if (nd->func == func && nd->data == data)
3223 notify_func_list = g_list_delete_link(notify_func_list, work);
3224 DEBUG_2("Notify func unregistered: %p", (void *)nd);
3231 g_warning("Notify func not found");
3235 #pragma GCC diagnostic push
3236 #pragma GCC diagnostic ignored "-Wunused-function"
3237 gboolean file_data_send_notification_idle_cb_unused(gpointer data)
3239 NotifyIdleData *nid = (NotifyIdleData *)data;
3240 GList *work = notify_func_list;
3244 NotifyData *nd = (NotifyData *)work->data;
3246 nd->func(nid->fd, nid->type, nd->data);
3249 file_data_unref(nid->fd);
3253 #pragma GCC diagnostic pop
3255 void file_data_send_notification(FileData *fd, NotifyType type)
3257 GList *work = notify_func_list;
3261 auto nd = static_cast<NotifyData *>(work->data);
3263 nd->func(fd, type, nd->data);
3267 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3268 nid->fd = file_data_ref(fd);
3270 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3274 static GHashTable *file_data_monitor_pool = nullptr;
3275 static guint realtime_monitor_id = 0; /* event source id */
3277 static void realtime_monitor_check_cb(gpointer key, gpointer, gpointer)
3279 auto fd = static_cast<FileData *>(key);
3281 file_data_check_changed_files(fd);
3283 DEBUG_1("monitor %s", fd->path);
3286 static gboolean realtime_monitor_cb(gpointer)
3288 if (!options->update_on_time_change) return TRUE;
3289 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, nullptr);
3293 gboolean file_data_register_real_time_monitor(FileData *fd)
3299 if (!file_data_monitor_pool)
3300 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3302 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3304 DEBUG_1("Register realtime %d %s", count, fd->path);
3307 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3309 if (!realtime_monitor_id)
3311 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, nullptr);
3317 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3321 g_assert(file_data_monitor_pool);
3323 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3325 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3327 g_assert(count > 0);
3332 g_hash_table_remove(file_data_monitor_pool, fd);
3334 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3336 file_data_unref(fd);
3338 if (g_hash_table_size(file_data_monitor_pool) == 0)
3340 g_source_remove(realtime_monitor_id);
3341 realtime_monitor_id = 0;
3349 *-----------------------------------------------------------------------------
3350 * Saving marks list, clearing marks
3351 * Uses file_data_pool
3352 *-----------------------------------------------------------------------------
3355 static void marks_get_files(gpointer key, gpointer value, gpointer userdata)
3357 auto file_name = static_cast<gchar *>(key);
3358 auto result = static_cast<GString *>(userdata);
3361 if (isfile(file_name))
3363 fd = static_cast<FileData *>(value);
3364 if (fd && fd->marks > 0)
3366 g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks);
3371 gboolean marks_list_load(const gchar *path)
3379 pathl = path_from_utf8(path);
3380 f = fopen(pathl, "r");
3382 if (!f) return FALSE;
3384 /* first line must start with Marks comment */
3385 if (!fgets(s_buf, sizeof(s_buf), f) ||
3386 strncmp(s_buf, "#Marks", 6) != 0)
3392 while (fgets(s_buf, sizeof(s_buf), f))
3394 if (s_buf[0]=='#') continue;
3395 file_path = strtok(s_buf, ",");
3396 marks_value = strtok(nullptr, ",");
3397 if (isfile(file_path))
3399 FileData *fd = file_data_new_no_grouping(file_path);
3404 gint mark_no = 1 << n;
3405 if (atoi(marks_value) & mark_no)
3407 file_data_set_mark(fd, n , 1);
3418 gboolean marks_list_save(gchar *path, gboolean save)
3420 SecureSaveInfo *ssi;
3423 pathl = path_from_utf8(path);
3424 ssi = secure_open(pathl);
3428 log_printf(_("Error: Unable to write marks lists to: %s\n"), path);
3432 secure_fprintf(ssi, "#Marks lists\n");
3434 GString *marks = g_string_new("");
3437 g_hash_table_foreach(file_data_pool, marks_get_files, marks);
3439 secure_fprintf(ssi, "%s", marks->str);
3440 g_string_free(marks, TRUE);
3442 secure_fprintf(ssi, "#end\n");
3443 return (secure_close(ssi) == 0);
3446 static void marks_clear(gpointer key, gpointer value, gpointer)
3448 auto file_name = static_cast<gchar *>(key);
3453 if (isfile(file_name))
3455 fd = static_cast<FileData *>(value);
3456 if (fd && fd->marks > 0)
3462 if (fd->marks & mark_no)
3464 file_data_set_mark(fd, n , 0);
3472 void marks_clear_all()
3474 g_hash_table_foreach(file_data_pool, marks_clear, nullptr);
3477 void file_data_set_page_num(FileData *fd, gint page_num)
3479 if (fd->page_total > 1 && page_num < 0)
3481 fd->page_num = fd->page_total - 1;
3483 else if (fd->page_total > 1 && page_num <= fd->page_total)
3485 fd->page_num = page_num - 1;
3491 file_data_send_notification(fd, NOTIFY_REREAD);
3494 void file_data_inc_page_num(FileData *fd)
3496 if (fd->page_total > 0 && fd->page_num < fd->page_total - 1)
3498 fd->page_num = fd->page_num + 1;
3500 else if (fd->page_total == 0)
3502 fd->page_num = fd->page_num + 1;
3504 file_data_send_notification(fd, NOTIFY_REREAD);
3507 void file_data_dec_page_num(FileData *fd)
3509 if (fd->page_num > 0)
3511 fd->page_num = fd->page_num - 1;
3513 file_data_send_notification(fd, NOTIFY_REREAD);
3516 void file_data_set_page_total(FileData *fd, gint page_total)
3518 fd->page_total = page_total;
3521 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */