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.
34 #include <glib-object.h>
43 #include "filefilter.h"
44 #include "histogram.h"
46 #include "main-defines.h"
51 #include "secure-save.h"
52 #include "thumb-standard.h"
54 #include "ui-fileops.h"
57 gint global_file_data_count = 0;
60 static GHashTable *file_data_pool = nullptr;
61 static GHashTable *file_data_planned_change_hash = nullptr;
63 static gint sidecar_file_priority(const gchar *extension);
64 static void file_data_check_sidecars(const GList *basename_list);
65 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
68 static SortType filelist_sort_method = SORT_NONE;
69 static gboolean filelist_sort_ascend = TRUE;
70 static gboolean filelist_sort_case = TRUE;
73 *-----------------------------------------------------------------------------
74 * text conversion utils
75 *-----------------------------------------------------------------------------
78 gchar *text_from_size(gint64 size)
88 /* what I would like to use is printf("%'d", size)
89 * BUT: not supported on every libc :(
93 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
94 a = g_strdup_printf("%d%09d", static_cast<guint>(size / 1000000000), static_cast<guint>(size % 1000000000));
98 a = g_strdup_printf("%d", static_cast<guint>(size));
104 b = g_new(gchar, l + n + 1);
129 gchar *text_from_size_abrev(gint64 size)
131 if (size < static_cast<gint64>(1024))
133 return g_strdup_printf(_("%d bytes"), static_cast<gint>(size));
135 if (size < static_cast<gint64>(1048576))
137 return g_strdup_printf(_("%.1f KiB"), static_cast<gdouble>(size) / 1024.0);
139 if (size < static_cast<gint64>(1073741824))
141 return g_strdup_printf(_("%.1f MiB"), static_cast<gdouble>(size) / 1048576.0);
144 /* to avoid overflowing the gdouble, do division in two steps */
146 return g_strdup_printf(_("%.1f GiB"), static_cast<gdouble>(size) / 1024.0);
149 /* note: returned string is valid until next call to text_from_time() */
150 const gchar *text_from_time(time_t t)
152 static gchar *ret = nullptr;
156 GError *error = nullptr;
158 btime = localtime(&t);
160 /* the %x warning about 2 digit years is not an error */
161 buflen = strftime(buf, sizeof(buf), "%x %X", btime);
162 if (buflen < 1) return "";
165 ret = g_locale_to_utf8(buf, buflen, nullptr, nullptr, &error);
168 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
177 *-----------------------------------------------------------------------------
178 * changed files detection and notification
179 *-----------------------------------------------------------------------------
182 void file_data_increment_version(FileData *fd)
188 fd->parent->version++;
189 fd->parent->valid_marks = 0;
193 static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st)
195 if (fd->size != st->st_size ||
196 fd->date != st->st_mtime)
198 fd->size = st->st_size;
199 fd->date = st->st_mtime;
200 fd->cdate = st->st_ctime;
201 fd->mode = st->st_mode;
202 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
203 fd->thumb_pixbuf = nullptr;
204 file_data_increment_version(fd);
205 file_data_send_notification(fd, NOTIFY_REREAD);
211 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
213 gboolean ret = FALSE;
216 ret = file_data_check_changed_single_file(fd, st);
218 work = fd->sidecar_files;
221 auto sfd = static_cast<FileData *>(work->data);
225 if (!stat_utf8(sfd->path, &st))
230 file_data_disconnect_sidecar_file(fd, sfd);
232 file_data_increment_version(sfd);
233 file_data_send_notification(sfd, NOTIFY_REREAD);
234 file_data_unref(sfd);
238 ret |= file_data_check_changed_files_recursive(sfd, &st);
244 gboolean file_data_check_changed_files(FileData *fd)
246 gboolean ret = FALSE;
249 if (fd->parent) fd = fd->parent;
251 if (!stat_utf8(fd->path, &st))
255 FileData *sfd = nullptr;
257 /* parent is missing, we have to rebuild whole group */
262 /* file_data_disconnect_sidecar_file might delete the file,
263 we have to keep the reference to prevent this */
264 sidecars = filelist_copy(fd->sidecar_files);
269 sfd = static_cast<FileData *>(work->data);
272 file_data_disconnect_sidecar_file(fd, sfd);
274 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
275 /* now we can release the sidecars */
276 filelist_free(sidecars);
277 file_data_increment_version(fd);
278 file_data_send_notification(fd, NOTIFY_REREAD);
283 ret |= file_data_check_changed_files_recursive(fd, &st);
290 *-----------------------------------------------------------------------------
291 * file name, extension, sorting, ...
292 *-----------------------------------------------------------------------------
295 static void file_data_set_collate_keys(FileData *fd)
297 gchar *caseless_name;
300 valid_name = g_filename_display_name(fd->name);
301 caseless_name = g_utf8_casefold(valid_name, -1);
303 g_free(fd->collate_key_name);
304 g_free(fd->collate_key_name_nocase);
306 fd->collate_key_name_natural = g_utf8_collate_key_for_filename(fd->name, -1);
307 fd->collate_key_name_nocase_natural = g_utf8_collate_key_for_filename(caseless_name, -1);
308 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
309 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
312 g_free(caseless_name);
315 static void file_data_set_path(FileData *fd, const gchar *path)
317 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
318 g_assert(file_data_pool);
322 if (fd->original_path)
324 g_hash_table_remove(file_data_pool, fd->original_path);
325 g_free(fd->original_path);
328 g_assert(!g_hash_table_lookup(file_data_pool, path));
330 fd->original_path = g_strdup(path);
331 g_hash_table_insert(file_data_pool, fd->original_path, fd);
333 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
335 fd->path = g_strdup(path);
337 fd->extension = fd->name + 1;
338 file_data_set_collate_keys(fd);
342 fd->path = g_strdup(path);
343 fd->name = filename_from_path(fd->path);
345 if (strcmp(fd->name, "..") == 0)
347 gchar *dir = remove_level_from_path(path);
349 fd->path = remove_level_from_path(dir);
352 fd->extension = fd->name + 2;
353 file_data_set_collate_keys(fd);
357 if (strcmp(fd->name, ".") == 0)
360 fd->path = remove_level_from_path(path);
362 fd->extension = fd->name + 1;
363 file_data_set_collate_keys(fd);
367 fd->extension = registered_extension_from_path(fd->path);
368 if (fd->extension == nullptr)
370 fd->extension = fd->name + strlen(fd->name);
373 fd->sidecar_priority = sidecar_file_priority(fd->extension);
374 file_data_set_collate_keys(fd);
378 *-----------------------------------------------------------------------------
379 * create or reuse Filedata
380 *-----------------------------------------------------------------------------
383 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
389 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
391 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
394 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
396 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
402 if (!fd && file_data_planned_change_hash)
404 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_planned_change_hash, path_utf8));
407 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
408 if (!isfile(fd->path))
411 file_data_apply_ci(fd);
422 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
424 #ifdef DEBUG_FILEDATA
427 file_data_check_changed_single_file(fd, st);
429 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
434 fd = g_new0(FileData, 1);
435 #ifdef DEBUG_FILEDATA
436 global_file_data_count++;
437 DEBUG_2("file data count++: %d", global_file_data_count);
440 fd->size = st->st_size;
441 fd->date = st->st_mtime;
442 fd->cdate = st->st_ctime;
443 fd->mode = st->st_mode;
445 fd->magick = FD_MAGICK;
447 fd->rating = STAR_RATING_NOT_READ;
448 fd->format_class = filter_file_get_class(path_utf8);
452 user = getpwuid(st->st_uid);
455 fd->owner = g_strdup_printf("%u", st->st_uid);
459 fd->owner = g_strdup(user->pw_name);
462 group = getgrgid(st->st_gid);
465 fd->group = g_strdup_printf("%u", st->st_gid);
469 fd->group = g_strdup(group->gr_name);
472 fd->sym_link = get_symbolic_link(path_utf8);
474 if (disable_sidecars) fd->disable_grouping = TRUE;
476 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
481 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
483 gchar *path_utf8 = path_to_utf8(path);
484 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
490 FileData *file_data_new_simple(const gchar *path_utf8)
495 if (!stat_utf8(path_utf8, &st))
501 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
502 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
511 void read_exif_time_data(FileData *file)
513 if (file->exifdate > 0)
515 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
526 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
527 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), (void *)file, file->path);
539 sscanf(tmp, "%4u:%2u:%2u %2u:%2u:%2u", &year, &month, &day, &hour, &min, &sec);
540 time_str.tm_year = year - 1900;
541 time_str.tm_mon = month - 1;
542 time_str.tm_mday = day;
543 time_str.tm_hour = hour;
544 time_str.tm_min = min;
545 time_str.tm_sec = sec;
546 time_str.tm_isdst = 0;
548 file->exifdate = mktime(&time_str);
554 void read_exif_time_digitized_data(FileData *file)
556 if (file->exifdate_digitized > 0)
558 DEBUG_1("%s set_exif_time_digitized_data: Already exists for %s", get_exec_time(), file->path);
569 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeDigitized");
570 DEBUG_2("%s set_exif_time_digitized_data: reading %p %s", get_exec_time(), (void *)file, file->path);
582 sscanf(tmp, "%4u:%2u:%2u %2u:%2u:%2u", &year, &month, &day, &hour, &min, &sec);
583 time_str.tm_year = year - 1900;
584 time_str.tm_mon = month - 1;
585 time_str.tm_mday = day;
586 time_str.tm_hour = hour;
587 time_str.tm_min = min;
588 time_str.tm_sec = sec;
589 time_str.tm_isdst = 0;
591 file->exifdate_digitized = mktime(&time_str);
597 void read_rating_data(FileData *file)
601 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
604 file->rating = atoi(rating_str);
613 #pragma GCC diagnostic push
614 #pragma GCC diagnostic ignored "-Wunused-function"
615 void set_exif_time_data_unused(GList *files)
617 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
621 auto *file = static_cast<FileData *>(files->data);
623 read_exif_time_data(file);
628 void set_exif_time_digitized_data_unused(GList *files)
630 DEBUG_1("%s set_exif_time_digitized_data: ...", get_exec_time());
634 auto *file = static_cast<FileData *>(files->data);
636 read_exif_time_digitized_data(file);
641 void set_rating_data_unused(GList *files)
644 DEBUG_1("%s set_rating_data: ...", get_exec_time());
648 auto *file = static_cast<FileData *>(files->data);
649 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
652 file->rating = atoi(rating_str);
658 #pragma GCC diagnostic pop
660 FileData *file_data_new_no_grouping(const gchar *path_utf8)
664 if (!stat_utf8(path_utf8, &st))
670 return file_data_new(path_utf8, &st, TRUE);
673 FileData *file_data_new_dir(const gchar *path_utf8)
677 if (!stat_utf8(path_utf8, &st))
683 /* dir or non-existing yet */
684 g_assert(S_ISDIR(st.st_mode));
686 return file_data_new(path_utf8, &st, TRUE);
690 *-----------------------------------------------------------------------------
692 *-----------------------------------------------------------------------------
695 #ifdef DEBUG_FILEDATA
696 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
698 FileData *file_data_ref(FileData *fd)
701 if (fd == nullptr) return nullptr;
702 if (fd->magick != FD_MAGICK)
703 #ifdef DEBUG_FILEDATA
704 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, (void *)fd);
706 log_printf("Error: fd magick mismatch fd=%p", fd);
708 g_assert(fd->magick == FD_MAGICK);
711 #ifdef DEBUG_FILEDATA
712 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->path, file, line);
714 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
720 * @brief Print ref. count and image name
723 * Print image ref. count and full path name of all images in
724 * the file_data_pool.
726 * Used only by DEBUG_FD()
728 void file_data_dump()
730 #ifdef DEBUG_FILEDATA
736 list = g_hash_table_get_values(file_data_pool);
738 log_printf("%d", global_file_data_count);
739 log_printf("%d", g_list_length(list));
744 fd = static_cast<FileData *>(work->data);
745 log_printf("%-4d %s", fd->ref, fd->path);
754 static void file_data_free(FileData *fd)
756 g_assert(fd->magick == FD_MAGICK);
757 g_assert(fd->ref == 0);
758 g_assert(!fd->locked);
760 #ifdef DEBUG_FILEDATA
761 global_file_data_count--;
762 DEBUG_2("file data count--: %d", global_file_data_count);
765 metadata_cache_free(fd);
766 g_hash_table_remove(file_data_pool, fd->original_path);
769 g_free(fd->original_path);
770 g_free(fd->collate_key_name);
771 g_free(fd->collate_key_name_nocase);
772 g_free(fd->extended_extension);
773 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
774 histmap_free(fd->histmap);
777 g_free(fd->sym_link);
778 g_free(fd->format_name);
779 g_assert(fd->sidecar_files == nullptr); /* sidecar files must be freed before calling this */
781 file_data_change_info_free(nullptr, fd);
786 * @brief Checks if the FileData is referenced
788 * Checks the refcount and whether the FileData is locked.
790 static gboolean file_data_check_has_ref(FileData *fd)
792 return fd->ref > 0 || fd->locked;
796 * @brief Consider freeing a FileData.
798 * This function will free a FileData and its children provided that neither its parent nor it has
799 * a positive refcount, and provided that neither is locked.
801 static void file_data_consider_free(FileData *fd)
804 FileData *parent = fd->parent ? fd->parent : fd;
806 g_assert(fd->magick == FD_MAGICK);
807 if (file_data_check_has_ref(fd)) return;
808 if (file_data_check_has_ref(parent)) return;
810 work = parent->sidecar_files;
813 auto sfd = static_cast<FileData *>(work->data);
814 if (file_data_check_has_ref(sfd)) return;
818 /* Neither the parent nor the siblings are referenced, so we can free everything */
819 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
820 fd->path, fd->parent ? parent->path : "-");
822 g_list_free_full(parent->sidecar_files, reinterpret_cast<GDestroyNotify>(file_data_free));
823 parent->sidecar_files = nullptr;
825 file_data_free(parent);
828 #ifdef DEBUG_FILEDATA
829 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
831 void file_data_unref(FileData *fd)
834 if (fd == nullptr) return;
835 if (fd->magick != FD_MAGICK)
836 #ifdef DEBUG_FILEDATA
837 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, (void *)fd);
839 log_printf("Error: fd magick mismatch fd=%p", fd);
841 g_assert(fd->magick == FD_MAGICK);
844 #ifdef DEBUG_FILEDATA
845 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->locked, fd->path,
848 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
851 // Free FileData if it's no longer ref'd
852 file_data_consider_free(fd);
856 * @brief Lock the FileData in memory.
858 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
859 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
860 * even if the code would continue to function properly even if the FileData were freed. Code that
861 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
863 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
864 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
866 void file_data_lock(FileData *fd)
868 if (fd == nullptr) return;
869 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
871 g_assert(fd->magick == FD_MAGICK);
874 DEBUG_2("file_data_ref fd=%p (%d): '%s'", (void *)fd, fd->ref, fd->path);
878 * @brief Reset the maintain-FileData-in-memory lock
880 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
881 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
882 * keeping it from being freed.
884 void file_data_unlock(FileData *fd)
886 if (fd == nullptr) return;
887 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
889 g_assert(fd->magick == FD_MAGICK);
892 // Free FileData if it's no longer ref'd
893 file_data_consider_free(fd);
897 * @brief Lock all of the FileDatas in the provided list
899 * @see file_data_lock(#FileData)
901 void file_data_lock_list(GList *list)
908 auto fd = static_cast<FileData *>(work->data);
915 * @brief Unlock all of the FileDatas in the provided list
917 * @see #file_data_unlock(#FileData)
919 void file_data_unlock_list(GList *list)
926 auto fd = static_cast<FileData *>(work->data);
928 file_data_unlock(fd);
933 *-----------------------------------------------------------------------------
934 * sidecar file info struct
935 *-----------------------------------------------------------------------------
938 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
940 auto fda = static_cast<const FileData *>(a);
941 auto fdb = static_cast<const FileData *>(b);
943 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
944 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
946 return strcmp(fdb->extension, fda->extension);
950 static gint sidecar_file_priority(const gchar *extension)
955 if (extension == nullptr)
958 work = sidecar_ext_get_list();
961 auto ext = static_cast<gchar *>(work->data);
964 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
970 static void file_data_check_sidecars(const GList *basename_list)
972 /* basename_list contains the new group - first is the parent, then sorted sidecars */
973 /* all files in the list have ref count > 0 */
980 if (!basename_list) return;
983 DEBUG_2("basename start");
984 work = basename_list;
987 auto fd = static_cast<FileData *>(work->data);
989 g_assert(fd->magick == FD_MAGICK);
990 DEBUG_2("basename: %p %s", (void *)fd, fd->name);
993 g_assert(fd->parent->magick == FD_MAGICK);
994 DEBUG_2(" parent: %p", (void *)fd->parent);
996 s_work = fd->sidecar_files;
999 auto sfd = static_cast<FileData *>(s_work->data);
1000 s_work = s_work->next;
1001 g_assert(sfd->magick == FD_MAGICK);
1002 DEBUG_2(" sidecar: %p %s", (void *)sfd, sfd->name);
1005 g_assert(fd->parent == nullptr || fd->sidecar_files == nullptr);
1008 parent_fd = static_cast<FileData *>(basename_list->data);
1010 /* check if the second and next entries of basename_list are already connected
1011 as sidecars of the first entry (parent_fd) */
1012 work = basename_list->next;
1013 s_work = parent_fd->sidecar_files;
1015 while (work && s_work)
1017 if (work->data != s_work->data) break;
1019 s_work = s_work->next;
1022 if (!work && !s_work)
1024 DEBUG_2("basename no change");
1025 return; /* no change in grouping */
1028 /* we have to regroup it */
1030 /* first, disconnect everything and send notification*/
1032 work = basename_list;
1035 auto fd = static_cast<FileData *>(work->data);
1037 g_assert(fd->parent == nullptr || fd->sidecar_files == nullptr);
1041 FileData *old_parent = fd->parent;
1042 g_assert(old_parent->parent == nullptr || old_parent->sidecar_files == nullptr);
1043 file_data_ref(old_parent);
1044 file_data_disconnect_sidecar_file(old_parent, fd);
1045 file_data_send_notification(old_parent, NOTIFY_REREAD);
1046 file_data_unref(old_parent);
1049 while (fd->sidecar_files)
1051 auto sfd = static_cast<FileData *>(fd->sidecar_files->data);
1052 g_assert(sfd->parent == nullptr || sfd->sidecar_files == nullptr);
1054 file_data_disconnect_sidecar_file(fd, sfd);
1055 file_data_send_notification(sfd, NOTIFY_REREAD);
1056 file_data_unref(sfd);
1058 file_data_send_notification(fd, NOTIFY_GROUPING);
1060 g_assert(fd->parent == nullptr && fd->sidecar_files == nullptr);
1063 /* now we can form the new group */
1064 work = basename_list->next;
1065 new_sidecars = nullptr;
1068 auto sfd = static_cast<FileData *>(work->data);
1069 g_assert(sfd->magick == FD_MAGICK);
1070 g_assert(sfd->parent == nullptr && sfd->sidecar_files == nullptr);
1071 sfd->parent = parent_fd;
1072 new_sidecars = g_list_prepend(new_sidecars, sfd);
1075 g_assert(parent_fd->sidecar_files == nullptr);
1076 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
1077 DEBUG_1("basename group changed for %s", parent_fd->path);
1081 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
1083 g_assert(target->magick == FD_MAGICK);
1084 g_assert(sfd->magick == FD_MAGICK);
1085 g_assert(g_list_find(target->sidecar_files, sfd));
1087 file_data_ref(target);
1090 g_assert(sfd->parent == target);
1092 file_data_increment_version(sfd); /* increments both sfd and target */
1094 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
1095 sfd->parent = nullptr;
1096 g_free(sfd->extended_extension);
1097 sfd->extended_extension = nullptr;
1099 file_data_unref(target);
1100 file_data_unref(sfd);
1103 /* disables / enables grouping for particular file, sends UPDATE notification */
1104 void file_data_disable_grouping(FileData *fd, gboolean disable)
1106 if (!fd->disable_grouping == !disable) return;
1108 fd->disable_grouping = !!disable;
1114 FileData *parent = file_data_ref(fd->parent);
1115 file_data_disconnect_sidecar_file(parent, fd);
1116 file_data_send_notification(parent, NOTIFY_GROUPING);
1117 file_data_unref(parent);
1119 else if (fd->sidecar_files)
1121 GList *sidecar_files = filelist_copy(fd->sidecar_files);
1122 GList *work = sidecar_files;
1125 auto sfd = static_cast<FileData *>(work->data);
1127 file_data_disconnect_sidecar_file(fd, sfd);
1128 file_data_send_notification(sfd, NOTIFY_GROUPING);
1130 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
1131 filelist_free(sidecar_files);
1135 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
1140 file_data_increment_version(fd);
1141 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
1143 file_data_send_notification(fd, NOTIFY_GROUPING);
1146 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
1153 auto fd = static_cast<FileData *>(work->data);
1155 file_data_disable_grouping(fd, disable);
1163 *-----------------------------------------------------------------------------
1165 *-----------------------------------------------------------------------------
1169 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
1172 if (!filelist_sort_ascend)
1179 switch (filelist_sort_method)
1184 if (fa->size < fb->size) return -1;
1185 if (fa->size > fb->size) return 1;
1186 /* fall back to name */
1189 if (fa->date < fb->date) return -1;
1190 if (fa->date > fb->date) return 1;
1191 /* fall back to name */
1194 if (fa->cdate < fb->cdate) return -1;
1195 if (fa->cdate > fb->cdate) return 1;
1196 /* fall back to name */
1199 if (fa->exifdate < fb->exifdate) return -1;
1200 if (fa->exifdate > fb->exifdate) return 1;
1201 /* fall back to name */
1203 case SORT_EXIFTIMEDIGITIZED:
1204 if (fa->exifdate_digitized < fb->exifdate_digitized) return -1;
1205 if (fa->exifdate_digitized > fb->exifdate_digitized) return 1;
1206 /* fall back to name */
1209 if (fa->rating < fb->rating) return -1;
1210 if (fa->rating > fb->rating) return 1;
1211 /* fall back to name */
1214 if (fa->format_class < fb->format_class) return -1;
1215 if (fa->format_class > fb->format_class) return 1;
1216 /* fall back to name */
1219 ret = strcmp(fa->collate_key_name_natural, fb->collate_key_name_natural);
1220 if (ret != 0) return ret;
1221 /* fall back to name */
1227 if (filelist_sort_case)
1228 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1230 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1232 if (ret != 0) return ret;
1234 /* do not return 0 unless the files are really the same
1235 file_data_pool ensures that original_path is unique
1237 return strcmp(fa->original_path, fb->original_path);
1240 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1242 filelist_sort_method = method;
1243 filelist_sort_ascend = ascend;
1244 return filelist_sort_compare_filedata(fa, fb);
1247 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1249 return filelist_sort_compare_filedata(static_cast<FileData *>(a), static_cast<FileData *>(b));
1252 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, gboolean case_sensitive, GCompareFunc cb)
1254 filelist_sort_method = method;
1255 filelist_sort_ascend = ascend;
1256 filelist_sort_case = case_sensitive;
1257 return g_list_sort(list, cb);
1260 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, gboolean case_sensitive, GCompareFunc cb)
1262 filelist_sort_method = method;
1263 filelist_sort_ascend = ascend;
1264 filelist_sort_case = case_sensitive;
1265 return g_list_insert_sorted(list, data, cb);
1268 GList *filelist_sort(GList *list, SortType method, gboolean ascend, gboolean case_sensitive)
1270 return filelist_sort_full(list, method, ascend, case_sensitive, reinterpret_cast<GCompareFunc>(filelist_sort_file_cb));
1273 #pragma GCC diagnostic push
1274 #pragma GCC diagnostic ignored "-Wunused-function"
1275 GList *filelist_insert_sort_unused(GList *list, FileData *fd, SortType method, gboolean ascend)
1277 return filelist_insert_sort_full(list, fd, method, ascend, ascend, (GCompareFunc) filelist_sort_file_cb);
1279 #pragma GCC diagnostic pop
1282 *-----------------------------------------------------------------------------
1283 * basename hash - grouping of sidecars in filelist
1284 *-----------------------------------------------------------------------------
1288 static GHashTable *file_data_basename_hash_new()
1290 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, nullptr);
1293 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1296 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1298 list = static_cast<GList *>(g_hash_table_lookup(basename_hash, basename));
1302 DEBUG_1("TG: basename_hash not found for %s",fd->path);
1303 const gchar *parent_extension = registered_extension_from_path(basename);
1305 if (parent_extension)
1307 DEBUG_1("TG: parent extension %s",parent_extension);
1308 gchar *parent_basename = g_strndup(basename, parent_extension - basename);
1309 DEBUG_1("TG: parent basename %s",parent_basename);
1310 auto parent_fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, basename));
1313 DEBUG_1("TG: parent fd found");
1314 list = static_cast<GList *>(g_hash_table_lookup(basename_hash, parent_basename));
1315 if (!g_list_find(list, parent_fd))
1317 DEBUG_1("TG: parent fd doesn't fit");
1318 g_free(parent_basename);
1324 basename = parent_basename;
1325 fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
1331 if (!g_list_find(list, fd))
1333 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1334 g_hash_table_insert(basename_hash, basename, list);
1343 static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
1345 file_data_basename_hash_insert(static_cast<GHashTable *>(basename_hash), static_cast<FileData *>(fd));
1348 static void file_data_basename_hash_remove_list(gpointer, gpointer value, gpointer)
1350 filelist_free(static_cast<GList *>(value));
1353 static void file_data_basename_hash_free(GHashTable *basename_hash)
1355 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, nullptr);
1356 g_hash_table_destroy(basename_hash);
1360 *-----------------------------------------------------------------------------
1361 * handling sidecars in filelist
1362 *-----------------------------------------------------------------------------
1365 static GList *filelist_filter_out_sidecars(GList *flist)
1367 GList *work = flist;
1368 GList *flist_filtered = nullptr;
1372 auto fd = static_cast<FileData *>(work->data);
1375 if (fd->parent) /* remove fd's that are children */
1376 file_data_unref(fd);
1378 flist_filtered = g_list_prepend(flist_filtered, fd);
1382 return flist_filtered;
1385 static void file_data_basename_hash_to_sidecars(gpointer, gpointer value, gpointer)
1387 auto basename_list = static_cast<GList *>(value);
1388 file_data_check_sidecars(basename_list);
1392 static gboolean is_hidden_file(const gchar *name)
1394 if (name[0] != '.') return FALSE;
1395 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1400 *-----------------------------------------------------------------------------
1401 * the main filelist function
1402 *-----------------------------------------------------------------------------
1405 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1410 GList *dlist = nullptr;
1411 GList *flist = nullptr;
1412 GList *xmp_files = nullptr;
1413 gint (*stat_func)(const gchar *path, struct stat *buf);
1414 GHashTable *basename_hash = nullptr;
1416 g_assert(files || dirs);
1418 if (files) *files = nullptr;
1419 if (dirs) *dirs = nullptr;
1421 pathl = path_from_utf8(dir_path);
1422 if (!pathl) return FALSE;
1424 dp = opendir(pathl);
1431 if (files) basename_hash = file_data_basename_hash_new();
1433 if (follow_symlinks)
1438 while ((dir = readdir(dp)) != nullptr)
1440 struct stat ent_sbuf;
1441 const gchar *name = dir->d_name;
1444 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1447 filepath = g_build_filename(pathl, name, NULL);
1448 if (stat_func(filepath, &ent_sbuf) >= 0)
1450 if (S_ISDIR(ent_sbuf.st_mode))
1452 /* we ignore the .thumbnails dir for cleanliness */
1454 (name[0] != '.' || (name[1] != '\0' && (name[1] != '.' || name[2] != '\0'))) &&
1455 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1456 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1457 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1459 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1464 if (files && filter_name_exists(name))
1466 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1467 flist = g_list_prepend(flist, fd);
1468 if (fd->sidecar_priority && !fd->disable_grouping)
1470 if (strcmp(fd->extension, ".xmp") != 0)
1471 file_data_basename_hash_insert(basename_hash, fd);
1473 xmp_files = g_list_append(xmp_files, fd);
1480 if (errno == EOVERFLOW)
1482 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1494 g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
1495 g_list_free(xmp_files);
1498 if (dirs) *dirs = dlist;
1502 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, nullptr);
1504 *files = filelist_filter_out_sidecars(flist);
1506 if (basename_hash) file_data_basename_hash_free(basename_hash);
1511 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1513 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1516 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1518 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1521 FileData *file_data_new_group(const gchar *path_utf8)
1528 if (!file_data_pool)
1530 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
1533 if (!stat_utf8(path_utf8, &st))
1539 if (S_ISDIR(st.st_mode))
1540 return file_data_new(path_utf8, &st, TRUE);
1542 dir = remove_level_from_path(path_utf8);
1544 filelist_read_real(dir, &files, nullptr, TRUE);
1546 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
1547 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1553 filelist_free(files);
1559 void filelist_free(GList *list)
1566 file_data_unref((FileData *)work->data);
1574 GList *filelist_copy(GList *list)
1576 GList *new_list = nullptr;
1578 for (GList *work = list; work; work = work->next)
1580 auto fd = static_cast<FileData *>(work->data);
1582 new_list = g_list_prepend(new_list, file_data_ref(fd));
1585 return g_list_reverse(new_list);
1588 GList *filelist_from_path_list(GList *list)
1590 GList *new_list = nullptr;
1598 path = static_cast<gchar *>(work->data);
1601 new_list = g_list_prepend(new_list, file_data_new_group(path));
1604 return g_list_reverse(new_list);
1607 GList *filelist_to_path_list(GList *list)
1609 GList *new_list = nullptr;
1617 fd = static_cast<FileData *>(work->data);
1620 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1623 return g_list_reverse(new_list);
1626 GList *filelist_filter(GList *list, gboolean is_dir_list)
1630 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1635 auto fd = static_cast<FileData *>(work->data);
1636 const gchar *name = fd->name;
1640 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1641 (!is_dir_list && !filter_name_exists(name)) ||
1642 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1643 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1645 list = g_list_remove_link(list, link);
1646 file_data_unref(fd);
1655 *-----------------------------------------------------------------------------
1656 * filelist recursive
1657 *-----------------------------------------------------------------------------
1660 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1662 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1665 GList *filelist_sort_path(GList *list)
1667 return g_list_sort(list, filelist_sort_path_cb);
1670 static void filelist_recursive_append(GList **list, GList *dirs)
1677 auto fd = static_cast<FileData *>(work->data);
1681 if (filelist_read(fd, &f, &d))
1683 f = filelist_filter(f, FALSE);
1684 f = filelist_sort_path(f);
1685 *list = g_list_concat(*list, f);
1687 d = filelist_filter(d, TRUE);
1688 d = filelist_sort_path(d);
1689 filelist_recursive_append(list, d);
1697 static void filelist_recursive_append_full(GList **list, GList *dirs, SortType method, gboolean ascend, gboolean case_sensitive)
1704 auto fd = static_cast<FileData *>(work->data);
1708 if (filelist_read(fd, &f, &d))
1710 f = filelist_filter(f, FALSE);
1711 f = filelist_sort_full(f, method, ascend, case_sensitive, reinterpret_cast<GCompareFunc>(filelist_sort_file_cb));
1712 *list = g_list_concat(*list, f);
1714 d = filelist_filter(d, TRUE);
1715 d = filelist_sort_path(d);
1716 filelist_recursive_append_full(list, d, method, ascend, case_sensitive);
1724 GList *filelist_recursive(FileData *dir_fd)
1729 if (!filelist_read(dir_fd, &list, &d)) return nullptr;
1730 list = filelist_filter(list, FALSE);
1731 list = filelist_sort_path(list);
1733 d = filelist_filter(d, TRUE);
1734 d = filelist_sort_path(d);
1735 filelist_recursive_append(&list, d);
1741 GList *filelist_recursive_full(FileData *dir_fd, SortType method, gboolean ascend, gboolean case_sensitive)
1746 if (!filelist_read(dir_fd, &list, &d)) return nullptr;
1747 list = filelist_filter(list, FALSE);
1748 list = filelist_sort_full(list, method, ascend, case_sensitive, reinterpret_cast<GCompareFunc>(filelist_sort_file_cb));
1750 d = filelist_filter(d, TRUE);
1751 d = filelist_sort_path(d);
1752 filelist_recursive_append_full(&list, d, method, ascend, case_sensitive);
1759 *-----------------------------------------------------------------------------
1760 * file modification support
1761 *-----------------------------------------------------------------------------
1765 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1767 if (!fdci && fd) fdci = fd->change;
1771 g_free(fdci->source);
1776 if (fd) fd->change = nullptr;
1779 static gboolean file_data_can_write_directly(FileData *fd)
1781 return filter_name_is_writable(fd->extension);
1784 static gboolean file_data_can_write_sidecar(FileData *fd)
1786 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1789 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1791 gchar *sidecar_path = nullptr;
1794 if (!file_data_can_write_sidecar(fd)) return nullptr;
1796 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1797 gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1800 auto sfd = static_cast<FileData *>(work->data);
1802 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1804 sidecar_path = g_strdup(sfd->path);
1808 g_free(extended_extension);
1810 if (!existing_only && !sidecar_path)
1812 if (options->metadata.sidecar_extended_name)
1813 sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1816 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1817 sidecar_path = g_strconcat(base, ".xmp", NULL);
1822 return sidecar_path;
1826 * marks and orientation
1829 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1830 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1831 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1832 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1834 gboolean file_data_get_mark(FileData *fd, gint n)
1836 gboolean valid = (fd->valid_marks & (1 << n));
1838 if (file_data_get_mark_func[n] && !valid)
1840 guint old = fd->marks;
1841 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1843 if (!value != !(fd->marks & (1 << n)))
1845 fd->marks = fd->marks ^ (1 << n);
1848 fd->valid_marks |= (1 << n);
1849 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1851 file_data_unref(fd);
1853 else if (!old && fd->marks)
1859 return !!(fd->marks & (1 << n));
1862 guint file_data_get_marks(FileData *fd)
1865 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1869 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1872 if (!value == !file_data_get_mark(fd, n)) return;
1874 if (file_data_set_mark_func[n])
1876 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1881 fd->marks = fd->marks ^ (1 << n);
1883 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1885 file_data_unref(fd);
1887 else if (!old && fd->marks)
1892 file_data_increment_version(fd);
1893 file_data_send_notification(fd, NOTIFY_MARKS);
1896 gboolean file_data_filter_marks(FileData *fd, guint filter)
1899 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1900 return ((fd->marks & filter) == filter);
1903 GList *file_data_filter_marks_list(GList *list, guint filter)
1910 auto fd = static_cast<FileData *>(work->data);
1914 if (!file_data_filter_marks(fd, filter))
1916 list = g_list_remove_link(list, link);
1917 file_data_unref(fd);
1925 gboolean file_data_filter_file_filter(FileData *fd, GRegex *filter)
1927 return g_regex_match(filter, fd->name, static_cast<GRegexMatchFlags>(0), nullptr);
1930 GList *file_data_filter_file_filter_list(GList *list, GRegex *filter)
1937 auto fd = static_cast<FileData *>(work->data);
1941 if (!file_data_filter_file_filter(fd, filter))
1943 list = g_list_remove_link(list, link);
1944 file_data_unref(fd);
1952 static gboolean file_data_filter_class(FileData *fd, guint filter)
1956 for (i = 0; i < FILE_FORMAT_CLASSES; i++)
1958 if (filter & (1 << i))
1960 if (static_cast<FileFormatClass>(i) == filter_file_get_class(fd->path))
1970 GList *file_data_filter_class_list(GList *list, guint filter)
1977 auto fd = static_cast<FileData *>(work->data);
1981 if (!file_data_filter_class(fd, filter))
1983 list = g_list_remove_link(list, link);
1984 file_data_unref(fd);
1992 static void file_data_notify_mark_func(gpointer, gpointer value, gpointer)
1994 auto fd = static_cast<FileData *>(value);
1995 file_data_increment_version(fd);
1996 file_data_send_notification(fd, NOTIFY_MARKS);
1999 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
2001 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
2003 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
2005 file_data_get_mark_func[n] = get_mark_func;
2006 file_data_set_mark_func[n] = set_mark_func;
2007 file_data_mark_func_data[n] = data;
2008 file_data_destroy_mark_func[n] = notify;
2010 if (get_mark_func && file_data_pool)
2012 /* this effectively changes all known files */
2013 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, nullptr);
2019 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
2021 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
2022 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
2023 if (data) *data = file_data_mark_func_data[n];
2026 #pragma GCC diagnostic push
2027 #pragma GCC diagnostic ignored "-Wunused-function"
2028 gint file_data_get_user_orientation_unused(FileData *fd)
2030 return fd->user_orientation;
2033 void file_data_set_user_orientation_unused(FileData *fd, gint value)
2035 if (fd->user_orientation == value) return;
2037 fd->user_orientation = value;
2038 file_data_increment_version(fd);
2039 file_data_send_notification(fd, NOTIFY_ORIENTATION);
2041 #pragma GCC diagnostic pop
2045 * file_data - operates on the given fd
2046 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
2050 /* return list of sidecar file extensions in a string */
2051 gchar *file_data_sc_list_to_string(FileData *fd)
2054 GString *result = g_string_new("");
2056 work = fd->sidecar_files;
2059 auto sfd = static_cast<FileData *>(work->data);
2061 result = g_string_append(result, "+ ");
2062 result = g_string_append(result, sfd->extension);
2064 if (work) result = g_string_append_c(result, ' ');
2067 return g_string_free(result, FALSE);
2073 * add FileDataChangeInfo (see typedefs.h) for the given operation
2074 * uses file_data_add_change_info
2076 * fails if the fd->change already exists - change operations can't run in parallel
2077 * fd->change_info works as a lock
2079 * dest can be NULL - in this case the current name is used for now, it will
2084 FileDataChangeInfo types:
2086 MOVE - path is changed, name may be changed too
2087 RENAME - path remains unchanged, name is changed
2088 extension should remain (FIXME should we allow editing extension? it will make problems with grouping)
2089 sidecar names are changed too, extensions are not changed
2091 UPDATE - file size, date or grouping has been changed
2094 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
2096 FileDataChangeInfo *fdci;
2098 if (fd->change) return FALSE;
2100 fdci = g_new0(FileDataChangeInfo, 1);
2105 fdci->source = g_strdup(src);
2107 fdci->source = g_strdup(fd->path);
2110 fdci->dest = g_strdup(dest);
2117 static void file_data_planned_change_remove(FileData *fd)
2119 if (file_data_planned_change_hash &&
2120 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
2122 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
2124 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
2125 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
2126 file_data_unref(fd);
2127 if (g_hash_table_size(file_data_planned_change_hash) == 0)
2129 g_hash_table_destroy(file_data_planned_change_hash);
2130 file_data_planned_change_hash = nullptr;
2131 DEBUG_1("planned change: empty");
2138 void file_data_free_ci(FileData *fd)
2140 FileDataChangeInfo *fdci = fd->change;
2144 file_data_planned_change_remove(fd);
2146 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
2148 g_free(fdci->source);
2153 fd->change = nullptr;
2156 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
2158 FileDataChangeInfo *fdci = fd->change;
2160 fdci->regroup_when_finished = enable;
2163 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
2167 if (fd->parent) fd = fd->parent;
2169 if (fd->change) return FALSE;
2171 work = fd->sidecar_files;
2174 auto sfd = static_cast<FileData *>(work->data);
2176 if (sfd->change) return FALSE;
2180 file_data_add_ci(fd, type, nullptr, nullptr);
2182 work = fd->sidecar_files;
2185 auto sfd = static_cast<FileData *>(work->data);
2187 file_data_add_ci(sfd, type, nullptr, nullptr);
2194 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
2198 if (fd->parent) fd = fd->parent;
2200 if (!fd->change || fd->change->type != type) return FALSE;
2202 work = fd->sidecar_files;
2205 auto sfd = static_cast<FileData *>(work->data);
2207 if (!sfd->change || sfd->change->type != type) return FALSE;
2215 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
2217 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
2218 file_data_sc_update_ci_copy(fd, dest_path);
2222 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
2224 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
2225 file_data_sc_update_ci_move(fd, dest_path);
2229 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
2231 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
2232 file_data_sc_update_ci_rename(fd, dest_path);
2236 gboolean file_data_sc_add_ci_delete(FileData *fd)
2238 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
2241 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
2243 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
2244 file_data_sc_update_ci_unspecified(fd, dest_path);
2248 gboolean file_data_add_ci_write_metadata(FileData *fd)
2250 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, nullptr, nullptr);
2253 void file_data_sc_free_ci(FileData *fd)
2257 if (fd->parent) fd = fd->parent;
2259 file_data_free_ci(fd);
2261 work = fd->sidecar_files;
2264 auto sfd = static_cast<FileData *>(work->data);
2266 file_data_free_ci(sfd);
2271 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2274 gboolean ret = TRUE;
2279 auto fd = static_cast<FileData *>(work->data);
2281 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2288 static void file_data_sc_revert_ci_list(GList *fd_list)
2295 auto fd = static_cast<FileData *>(work->data);
2297 file_data_sc_free_ci(fd);
2302 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2309 auto fd = static_cast<FileData *>(work->data);
2311 if (!func(fd, dest))
2313 file_data_sc_revert_ci_list(work->prev);
2322 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2324 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2327 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2329 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2332 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2334 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2337 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2339 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2342 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2345 gboolean ret = TRUE;
2350 auto fd = static_cast<FileData *>(work->data);
2352 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2359 void file_data_free_ci_list(GList *fd_list)
2366 auto fd = static_cast<FileData *>(work->data);
2368 file_data_free_ci(fd);
2373 void file_data_sc_free_ci_list(GList *fd_list)
2380 auto fd = static_cast<FileData *>(work->data);
2382 file_data_sc_free_ci(fd);
2388 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2389 * fails if fd->change does not exist or the change type does not match
2392 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2394 FileDataChangeType type = fd->change->type;
2396 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2400 if (!file_data_planned_change_hash)
2401 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2403 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2405 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2406 g_hash_table_remove(file_data_planned_change_hash, old_path);
2407 file_data_unref(fd);
2410 ofd = static_cast<FileData *>(g_hash_table_lookup(file_data_planned_change_hash, new_path));
2415 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2416 g_hash_table_remove(file_data_planned_change_hash, new_path);
2417 file_data_unref(ofd);
2420 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2422 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2427 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2429 gchar *old_path = fd->change->dest;
2431 fd->change->dest = g_strdup(dest_path);
2432 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2436 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2438 const gchar *extension = registered_extension_from_path(fd->change->source);
2439 gchar *base = remove_extension_from_path(dest_path);
2440 gchar *old_path = fd->change->dest;
2442 fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2443 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2449 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2452 gchar *dest_path_full = nullptr;
2454 if (fd->parent) fd = fd->parent;
2458 dest_path = fd->path;
2460 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2462 gchar *dir = remove_level_from_path(fd->path);
2464 dest_path_full = g_build_filename(dir, dest_path, NULL);
2466 dest_path = dest_path_full;
2468 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2470 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2471 dest_path = dest_path_full;
2474 file_data_update_ci_dest(fd, dest_path);
2476 work = fd->sidecar_files;
2479 auto sfd = static_cast<FileData *>(work->data);
2481 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2485 g_free(dest_path_full);
2488 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2490 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2491 file_data_sc_update_ci(fd, dest_path);
2495 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2497 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2500 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2502 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2505 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2507 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2510 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2512 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2515 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2517 gboolean (*func)(FileData *, const gchar *))
2520 gboolean ret = TRUE;
2525 auto fd = static_cast<FileData *>(work->data);
2527 if (!func(fd, dest)) ret = FALSE;
2534 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2536 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2539 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2541 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2544 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2546 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2551 * verify source and dest paths - dest image exists, etc.
2552 * it should detect all possible problems with the planned operation
2555 gint file_data_verify_ci(FileData *fd, GList *list)
2557 gint ret = CHANGE_OK;
2559 GList *work = nullptr;
2560 FileData *fd1 = nullptr;
2564 DEBUG_1("Change checked: no change info: %s", fd->path);
2568 if (!isname(fd->path))
2570 /* this probably should not happen */
2571 ret |= CHANGE_NO_SRC;
2572 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2576 dir = remove_level_from_path(fd->path);
2578 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2579 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2580 fd->change->type != FILEDATA_CHANGE_RENAME &&
2581 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2584 ret |= CHANGE_WARN_UNSAVED_META;
2585 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2588 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2589 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2590 !access_file(fd->path, R_OK))
2592 ret |= CHANGE_NO_READ_PERM;
2593 DEBUG_1("Change checked: no read permission: %s", fd->path);
2595 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2596 !access_file(dir, W_OK))
2598 ret |= CHANGE_NO_WRITE_PERM_DIR;
2599 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2601 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2602 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2603 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2604 !access_file(fd->path, W_OK))
2606 ret |= CHANGE_WARN_NO_WRITE_PERM;
2607 DEBUG_1("Change checked: no write permission: %s", fd->path);
2609 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2610 - that means that there are no hard errors and warnings can be disabled
2611 - the destination is determined during the check
2613 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2615 /* determine destination file */
2616 gboolean have_dest = FALSE;
2617 gchar *dest_dir = nullptr;
2619 if (options->metadata.save_in_image_file)
2621 if (file_data_can_write_directly(fd))
2623 /* we can write the file directly */
2624 if (access_file(fd->path, W_OK))
2630 if (options->metadata.warn_on_write_problems)
2632 ret |= CHANGE_WARN_NO_WRITE_PERM;
2633 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2637 else if (file_data_can_write_sidecar(fd))
2639 /* we can write sidecar */
2640 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2641 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2643 file_data_update_ci_dest(fd, sidecar);
2648 if (options->metadata.warn_on_write_problems)
2650 ret |= CHANGE_WARN_NO_WRITE_PERM;
2651 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2660 /* write private metadata file under ~/.geeqie */
2662 /* If an existing metadata file exists, we will try writing to
2663 * it's location regardless of the user's preference.
2665 gchar *metadata_path = nullptr;
2667 /* but ignore XMP if we are not able to write it */
2668 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2670 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2672 if (metadata_path && !access_file(metadata_path, W_OK))
2674 g_free(metadata_path);
2675 metadata_path = nullptr;
2682 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2683 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2685 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2687 metadata_path = g_build_filename(dest_dir, filename, NULL);
2691 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2693 file_data_update_ci_dest(fd, metadata_path);
2697 ret |= CHANGE_NO_WRITE_PERM_DEST;
2698 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2700 g_free(metadata_path);
2705 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2710 same = (strcmp(fd->path, fd->change->dest) == 0);
2714 const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2715 if (!dest_ext) dest_ext = "";
2716 if (!options->file_filter.disable_file_extension_checks)
2718 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2720 ret |= CHANGE_WARN_CHANGED_EXT;
2721 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2727 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /** @FIXME this is now needed for running editors */
2729 ret |= CHANGE_WARN_SAME;
2730 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2734 dest_dir = remove_level_from_path(fd->change->dest);
2736 if (!isdir(dest_dir))
2738 ret |= CHANGE_NO_DEST_DIR;
2739 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2741 else if (!access_file(dest_dir, W_OK))
2743 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2744 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2748 if (isfile(fd->change->dest))
2750 if (!access_file(fd->change->dest, W_OK))
2752 ret |= CHANGE_NO_WRITE_PERM_DEST;
2753 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2757 ret |= CHANGE_WARN_DEST_EXISTS;
2758 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2761 else if (isdir(fd->change->dest))
2763 ret |= CHANGE_DEST_EXISTS;
2764 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2771 /* During a rename operation, check if another planned destination file has
2774 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2775 fd->change->type == FILEDATA_CHANGE_COPY ||
2776 fd->change->type == FILEDATA_CHANGE_MOVE)
2781 fd1 = static_cast<FileData *>(work->data);
2783 if (fd1 != nullptr && fd != fd1 )
2785 if (!strcmp(fd->change->dest, fd1->change->dest))
2787 ret |= CHANGE_DUPLICATE_DEST;
2793 fd->change->error = ret;
2794 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2801 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2806 ret = file_data_verify_ci(fd, list);
2808 work = fd->sidecar_files;
2811 auto sfd = static_cast<FileData *>(work->data);
2813 ret |= file_data_verify_ci(sfd, list);
2820 gchar *file_data_get_error_string(gint error)
2822 GString *result = g_string_new("");
2824 if (error & CHANGE_NO_SRC)
2826 if (result->len > 0) g_string_append(result, ", ");
2827 g_string_append(result, _("file or directory does not exist"));
2830 if (error & CHANGE_DEST_EXISTS)
2832 if (result->len > 0) g_string_append(result, ", ");
2833 g_string_append(result, _("destination already exists"));
2836 if (error & CHANGE_NO_WRITE_PERM_DEST)
2838 if (result->len > 0) g_string_append(result, ", ");
2839 g_string_append(result, _("destination can't be overwritten"));
2842 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2844 if (result->len > 0) g_string_append(result, ", ");
2845 g_string_append(result, _("destination directory is not writable"));
2848 if (error & CHANGE_NO_DEST_DIR)
2850 if (result->len > 0) g_string_append(result, ", ");
2851 g_string_append(result, _("destination directory does not exist"));
2854 if (error & CHANGE_NO_WRITE_PERM_DIR)
2856 if (result->len > 0) g_string_append(result, ", ");
2857 g_string_append(result, _("source directory is not writable"));
2860 if (error & CHANGE_NO_READ_PERM)
2862 if (result->len > 0) g_string_append(result, ", ");
2863 g_string_append(result, _("no read permission"));
2866 if (error & CHANGE_WARN_NO_WRITE_PERM)
2868 if (result->len > 0) g_string_append(result, ", ");
2869 g_string_append(result, _("file is readonly"));
2872 if (error & CHANGE_WARN_DEST_EXISTS)
2874 if (result->len > 0) g_string_append(result, ", ");
2875 g_string_append(result, _("destination already exists and will be overwritten"));
2878 if (error & CHANGE_WARN_SAME)
2880 if (result->len > 0) g_string_append(result, ", ");
2881 g_string_append(result, _("source and destination are the same"));
2884 if (error & CHANGE_WARN_CHANGED_EXT)
2886 if (result->len > 0) g_string_append(result, ", ");
2887 g_string_append(result, _("source and destination have different extension"));
2890 if (error & CHANGE_WARN_UNSAVED_META)
2892 if (result->len > 0) g_string_append(result, ", ");
2893 g_string_append(result, _("there are unsaved metadata changes for the file"));
2896 if (error & CHANGE_DUPLICATE_DEST)
2898 if (result->len > 0) g_string_append(result, ", ");
2899 g_string_append(result, _("another destination file has the same filename"));
2902 return g_string_free(result, FALSE);
2905 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2908 gint all_errors = 0;
2909 gint common_errors = ~0;
2914 if (!list) return 0;
2916 num = g_list_length(list);
2917 errors = g_new(int, num);
2925 fd = static_cast<FileData *>(work->data);
2928 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2929 all_errors |= error;
2930 common_errors &= error;
2937 if (desc && all_errors)
2940 GString *result = g_string_new("");
2944 gchar *str = file_data_get_error_string(common_errors);
2945 g_string_append(result, str);
2946 g_string_append(result, "\n");
2957 fd = static_cast<FileData *>(work->data);
2960 error = errors[i] & ~common_errors;
2964 gchar *str = file_data_get_error_string(error);
2965 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2970 *desc = g_string_free(result, FALSE);
2979 * perform the change described by FileFataChangeInfo
2980 * it is used for internal operations,
2981 * this function actually operates with files on the filesystem
2982 * it should implement safe delete
2985 static gboolean file_data_perform_move(FileData *fd)
2987 g_assert(!strcmp(fd->change->source, fd->path));
2988 return move_file(fd->change->source, fd->change->dest);
2991 static gboolean file_data_perform_copy(FileData *fd)
2993 g_assert(!strcmp(fd->change->source, fd->path));
2994 return copy_file(fd->change->source, fd->change->dest);
2997 static gboolean file_data_perform_delete(FileData *fd)
2999 if (isdir(fd->path) && !islink(fd->path))
3000 return rmdir_utf8(fd->path);
3002 if (options->file_ops.safe_delete_enable)
3003 return file_util_safe_unlink(fd->path);
3005 return unlink_file(fd->path);
3008 gboolean file_data_perform_ci(FileData *fd)
3010 /** @FIXME When a directory that is a symbolic link is deleted,
3011 * at this point fd->change is null because no FileDataChangeInfo
3012 * has been set up. Therefore there is a seg. fault.
3013 * This code simply aborts the delete.
3020 FileDataChangeType type = fd->change->type;
3024 case FILEDATA_CHANGE_MOVE:
3025 return file_data_perform_move(fd);
3026 case FILEDATA_CHANGE_COPY:
3027 return file_data_perform_copy(fd);
3028 case FILEDATA_CHANGE_RENAME:
3029 return file_data_perform_move(fd); /* the same as move */
3030 case FILEDATA_CHANGE_DELETE:
3031 return file_data_perform_delete(fd);
3032 case FILEDATA_CHANGE_WRITE_METADATA:
3033 return metadata_write_perform(fd);
3034 case FILEDATA_CHANGE_UNSPECIFIED:
3035 /* nothing to do here */
3043 gboolean file_data_sc_perform_ci(FileData *fd)
3046 gboolean ret = TRUE;
3047 FileDataChangeType type = fd->change->type;
3049 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3051 work = fd->sidecar_files;
3054 auto sfd = static_cast<FileData *>(work->data);
3056 if (!file_data_perform_ci(sfd)) ret = FALSE;
3060 if (!file_data_perform_ci(fd)) ret = FALSE;
3066 * updates FileData structure according to FileDataChangeInfo
3069 gboolean file_data_apply_ci(FileData *fd)
3071 FileDataChangeType type = fd->change->type;
3073 /** @FIXME delete ?*/
3074 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
3076 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
3077 file_data_planned_change_remove(fd);
3079 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
3081 /* this change overwrites another file which is already known to other modules
3082 renaming fd would create duplicate FileData structure
3083 the best thing we can do is nothing
3085 /** @FIXME maybe we could copy stuff like marks
3087 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
3091 file_data_set_path(fd, fd->change->dest);
3094 file_data_increment_version(fd);
3095 file_data_send_notification(fd, NOTIFY_CHANGE);
3100 gboolean file_data_sc_apply_ci(FileData *fd)
3103 FileDataChangeType type = fd->change->type;
3105 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3107 work = fd->sidecar_files;
3110 auto sfd = static_cast<FileData *>(work->data);
3112 file_data_apply_ci(sfd);
3116 file_data_apply_ci(fd);
3121 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
3124 if (fd->parent) fd = fd->parent;
3125 if (!g_list_find(list, fd)) return FALSE;
3127 work = fd->sidecar_files;
3130 if (!g_list_find(list, work->data)) return FALSE;
3136 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
3138 GList *out = nullptr;
3141 /* change partial groups to independent files */
3146 auto fd = static_cast<FileData *>(work->data);
3149 if (!file_data_list_contains_whole_group(list, fd))
3151 file_data_disable_grouping(fd, TRUE);
3154 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
3160 /* remove sidecars from the list,
3161 they can be still accessed via main_fd->sidecar_files */
3165 auto fd = static_cast<FileData *>(work->data);
3169 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
3171 out = g_list_prepend(out, file_data_ref(fd));
3175 filelist_free(list);
3176 out = g_list_reverse(out);
3186 * notify other modules about the change described by FileDataChangeInfo
3189 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks */
3190 /** @FIXME do we need the ignore_list? It looks like a workaround for ineffective
3191 implementation in view-file-list.cc */
3194 struct NotifyIdleData {
3201 FileDataNotifyFunc func;
3203 NotifyPriority priority;
3206 static GList *notify_func_list = nullptr;
3208 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
3210 auto nda = static_cast<const NotifyData *>(a);
3211 auto ndb = static_cast<const NotifyData *>(b);
3213 if (nda->priority < ndb->priority) return -1;
3214 if (nda->priority > ndb->priority) return 1;
3218 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
3221 GList *work = notify_func_list;
3225 auto nd = static_cast<NotifyData *>(work->data);
3227 if (nd->func == func && nd->data == data)
3229 g_warning("Notify func already registered");
3235 nd = g_new(NotifyData, 1);
3238 nd->priority = priority;
3240 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
3241 DEBUG_2("Notify func registered: %p", (void *)nd);
3246 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
3248 GList *work = notify_func_list;
3252 auto nd = static_cast<NotifyData *>(work->data);
3254 if (nd->func == func && nd->data == data)
3256 notify_func_list = g_list_delete_link(notify_func_list, work);
3257 DEBUG_2("Notify func unregistered: %p", (void *)nd);
3264 g_warning("Notify func not found");
3268 #pragma GCC diagnostic push
3269 #pragma GCC diagnostic ignored "-Wunused-function"
3270 gboolean file_data_send_notification_idle_cb_unused(gpointer data)
3272 auto *nid = (NotifyIdleData *)data;
3273 GList *work = notify_func_list;
3277 auto *nd = (NotifyData *)work->data;
3279 nd->func(nid->fd, nid->type, nd->data);
3282 file_data_unref(nid->fd);
3286 #pragma GCC diagnostic pop
3288 void file_data_send_notification(FileData *fd, NotifyType type)
3290 GList *work = notify_func_list;
3294 auto nd = static_cast<NotifyData *>(work->data);
3296 nd->func(fd, type, nd->data);
3300 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3301 nid->fd = file_data_ref(fd);
3303 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3307 static GHashTable *file_data_monitor_pool = nullptr;
3308 static guint realtime_monitor_id = 0; /* event source id */
3310 static void realtime_monitor_check_cb(gpointer key, gpointer, gpointer)
3312 auto fd = static_cast<FileData *>(key);
3314 file_data_check_changed_files(fd);
3316 DEBUG_1("monitor %s", fd->path);
3319 static gboolean realtime_monitor_cb(gpointer)
3321 if (!options->update_on_time_change) return TRUE;
3322 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, nullptr);
3326 gboolean file_data_register_real_time_monitor(FileData *fd)
3332 if (!file_data_monitor_pool)
3333 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3335 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3337 DEBUG_1("Register realtime %d %s", count, fd->path);
3340 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3342 if (!realtime_monitor_id)
3344 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, nullptr);
3350 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3354 g_assert(file_data_monitor_pool);
3356 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3358 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3360 g_assert(count > 0);
3365 g_hash_table_remove(file_data_monitor_pool, fd);
3367 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3369 file_data_unref(fd);
3371 if (g_hash_table_size(file_data_monitor_pool) == 0)
3373 g_source_remove(realtime_monitor_id);
3374 realtime_monitor_id = 0;
3382 *-----------------------------------------------------------------------------
3383 * Saving marks list, clearing marks
3384 * Uses file_data_pool
3385 *-----------------------------------------------------------------------------
3388 static void marks_get_files(gpointer key, gpointer value, gpointer userdata)
3390 auto file_name = static_cast<gchar *>(key);
3391 auto result = static_cast<GString *>(userdata);
3394 if (isfile(file_name))
3396 fd = static_cast<FileData *>(value);
3397 if (fd && fd->marks > 0)
3399 g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks);
3404 gboolean marks_list_load(const gchar *path)
3412 pathl = path_from_utf8(path);
3413 f = fopen(pathl, "r");
3415 if (!f) return FALSE;
3417 /* first line must start with Marks comment */
3418 if (!fgets(s_buf, sizeof(s_buf), f) ||
3419 strncmp(s_buf, "#Marks", 6) != 0)
3425 while (fgets(s_buf, sizeof(s_buf), f))
3427 if (s_buf[0]=='#') continue;
3428 file_path = strtok(s_buf, ",");
3429 marks_value = strtok(nullptr, ",");
3430 if (isfile(file_path))
3432 FileData *fd = file_data_new_no_grouping(file_path);
3437 gint mark_no = 1 << n;
3438 if (atoi(marks_value) & mark_no)
3440 file_data_set_mark(fd, n , 1);
3451 gboolean marks_list_save(gchar *path, gboolean save)
3453 SecureSaveInfo *ssi;
3456 pathl = path_from_utf8(path);
3457 ssi = secure_open(pathl);
3461 log_printf(_("Error: Unable to write marks lists to: %s\n"), path);
3465 secure_fprintf(ssi, "#Marks lists\n");
3467 GString *marks = g_string_new("");
3470 g_hash_table_foreach(file_data_pool, marks_get_files, marks);
3472 secure_fprintf(ssi, "%s", marks->str);
3473 g_string_free(marks, TRUE);
3475 secure_fprintf(ssi, "#end\n");
3476 return (secure_close(ssi) == 0);
3479 static void marks_clear(gpointer key, gpointer value, gpointer)
3481 auto file_name = static_cast<gchar *>(key);
3486 if (isfile(file_name))
3488 fd = static_cast<FileData *>(value);
3489 if (fd && fd->marks > 0)
3495 if (fd->marks & mark_no)
3497 file_data_set_mark(fd, n , 0);
3505 void marks_clear_all()
3507 g_hash_table_foreach(file_data_pool, marks_clear, nullptr);
3510 void file_data_set_page_num(FileData *fd, gint page_num)
3512 if (fd->page_total > 1 && page_num < 0)
3514 fd->page_num = fd->page_total - 1;
3516 else if (fd->page_total > 1 && page_num <= fd->page_total)
3518 fd->page_num = page_num - 1;
3524 file_data_send_notification(fd, NOTIFY_REREAD);
3527 void file_data_inc_page_num(FileData *fd)
3529 if (fd->page_total > 0 && fd->page_num < fd->page_total - 1)
3531 fd->page_num = fd->page_num + 1;
3533 else if (fd->page_total == 0)
3535 fd->page_num = fd->page_num + 1;
3537 file_data_send_notification(fd, NOTIFY_REREAD);
3540 void file_data_dec_page_num(FileData *fd)
3542 if (fd->page_num > 0)
3544 fd->page_num = fd->page_num - 1;
3546 file_data_send_notification(fd, NOTIFY_REREAD);
3549 void file_data_set_page_total(FileData *fd, gint page_total)
3551 fd->page_total = page_total;
3554 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */