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);
336 if (strcmp(fd->name, ".") == 0)
339 fd->path = remove_level_from_path(path);
341 fd->extension = fd->name + 1;
342 file_data_set_collate_keys(fd);
346 fd->extension = registered_extension_from_path(fd->path);
347 if (fd->extension == nullptr)
349 fd->extension = fd->name + strlen(fd->name);
352 fd->sidecar_priority = sidecar_file_priority(fd->extension);
353 file_data_set_collate_keys(fd);
357 *-----------------------------------------------------------------------------
358 * create or reuse Filedata
359 *-----------------------------------------------------------------------------
362 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
368 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
370 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
373 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
375 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
381 if (!fd && file_data_planned_change_hash)
383 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_planned_change_hash, path_utf8));
386 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
387 if (!isfile(fd->path))
390 file_data_apply_ci(fd);
401 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
403 #ifdef DEBUG_FILEDATA
406 file_data_check_changed_single_file(fd, st);
408 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
413 fd = g_new0(FileData, 1);
414 #ifdef DEBUG_FILEDATA
415 global_file_data_count++;
416 DEBUG_2("file data count++: %d", global_file_data_count);
419 fd->size = st->st_size;
420 fd->date = st->st_mtime;
421 fd->cdate = st->st_ctime;
422 fd->mode = st->st_mode;
424 fd->magick = FD_MAGICK;
426 fd->rating = STAR_RATING_NOT_READ;
427 fd->format_class = filter_file_get_class(path_utf8);
431 user = getpwuid(st->st_uid);
434 fd->owner = g_strdup_printf("%u", st->st_uid);
438 fd->owner = g_strdup(user->pw_name);
441 group = getgrgid(st->st_gid);
444 fd->group = g_strdup_printf("%u", st->st_gid);
448 fd->group = g_strdup(group->gr_name);
451 fd->sym_link = get_symbolic_link(path_utf8);
453 if (disable_sidecars) fd->disable_grouping = TRUE;
455 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
460 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
462 gchar *path_utf8 = path_to_utf8(path);
463 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
469 FileData *file_data_new_simple(const gchar *path_utf8)
474 if (!stat_utf8(path_utf8, &st))
480 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
481 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
490 void read_exif_time_data(FileData *file)
492 if (file->exifdate > 0)
494 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
505 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
506 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), (void *)file, file->path);
511 uint year, month, day, hour, min, sec;
513 sscanf(tmp, "%4u:%2u:%2u %2u:%2u:%2u", &year, &month, &day, &hour, &min, &sec);
514 time_str.tm_year = year - 1900;
515 time_str.tm_mon = month - 1;
516 time_str.tm_mday = day;
517 time_str.tm_hour = hour;
518 time_str.tm_min = min;
519 time_str.tm_sec = sec;
520 time_str.tm_isdst = 0;
522 file->exifdate = mktime(&time_str);
528 void read_exif_time_digitized_data(FileData *file)
530 if (file->exifdate_digitized > 0)
532 DEBUG_1("%s set_exif_time_digitized_data: Already exists for %s", get_exec_time(), file->path);
543 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeDigitized");
544 DEBUG_2("%s set_exif_time_digitized_data: reading %p %s", get_exec_time(), (void *)file, file->path);
549 uint year, month, day, hour, min, sec;
551 sscanf(tmp, "%4u:%2u:%2u %2u:%2u:%2u", &year, &month, &day, &hour, &min, &sec);
552 time_str.tm_year = year - 1900;
553 time_str.tm_mon = month - 1;
554 time_str.tm_mday = day;
555 time_str.tm_hour = hour;
556 time_str.tm_min = min;
557 time_str.tm_sec = sec;
558 time_str.tm_isdst = 0;
560 file->exifdate_digitized = mktime(&time_str);
566 void read_rating_data(FileData *file)
570 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
573 file->rating = atoi(rating_str);
582 #pragma GCC diagnostic push
583 #pragma GCC diagnostic ignored "-Wunused-function"
584 void set_exif_time_data_unused(GList *files)
586 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
590 auto *file = static_cast<FileData *>(files->data);
592 read_exif_time_data(file);
597 void set_exif_time_digitized_data_unused(GList *files)
599 DEBUG_1("%s set_exif_time_digitized_data: ...", get_exec_time());
603 auto *file = static_cast<FileData *>(files->data);
605 read_exif_time_digitized_data(file);
610 void set_rating_data_unused(GList *files)
613 DEBUG_1("%s set_rating_data: ...", get_exec_time());
617 auto *file = static_cast<FileData *>(files->data);
618 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
621 file->rating = atoi(rating_str);
627 #pragma GCC diagnostic pop
629 FileData *file_data_new_no_grouping(const gchar *path_utf8)
633 if (!stat_utf8(path_utf8, &st))
639 return file_data_new(path_utf8, &st, TRUE);
642 FileData *file_data_new_dir(const gchar *path_utf8)
646 if (!stat_utf8(path_utf8, &st))
652 /* dir or non-existing yet */
653 g_assert(S_ISDIR(st.st_mode));
655 return file_data_new(path_utf8, &st, TRUE);
659 *-----------------------------------------------------------------------------
661 *-----------------------------------------------------------------------------
664 #ifdef DEBUG_FILEDATA
665 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
667 FileData *file_data_ref(FileData *fd)
670 if (fd == nullptr) return nullptr;
671 if (fd->magick != FD_MAGICK)
672 #ifdef DEBUG_FILEDATA
673 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, (void *)fd);
675 log_printf("Error: fd magick mismatch fd=%p", fd);
677 g_assert(fd->magick == FD_MAGICK);
680 #ifdef DEBUG_FILEDATA
681 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->path, file, line);
683 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
689 * @brief Print ref. count and image name
692 * Print image ref. count and full path name of all images in
693 * the file_data_pool.
695 * Used only by DEBUG_FD()
697 void file_data_dump()
699 #ifdef DEBUG_FILEDATA
705 list = g_hash_table_get_values(file_data_pool);
707 log_printf("%d", global_file_data_count);
708 log_printf("%d", g_list_length(list));
713 fd = static_cast<FileData *>(work->data);
714 log_printf("%-4d %s", fd->ref, fd->path);
723 static void file_data_free(FileData *fd)
725 g_assert(fd->magick == FD_MAGICK);
726 g_assert(fd->ref == 0);
727 g_assert(!fd->locked);
729 #ifdef DEBUG_FILEDATA
730 global_file_data_count--;
731 DEBUG_2("file data count--: %d", global_file_data_count);
734 metadata_cache_free(fd);
735 g_hash_table_remove(file_data_pool, fd->original_path);
738 g_free(fd->original_path);
739 g_free(fd->collate_key_name);
740 g_free(fd->collate_key_name_nocase);
741 g_free(fd->extended_extension);
742 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
743 histmap_free(fd->histmap);
746 g_free(fd->sym_link);
747 g_free(fd->format_name);
748 g_assert(fd->sidecar_files == nullptr); /* sidecar files must be freed before calling this */
750 file_data_change_info_free(nullptr, fd);
755 * @brief Checks if the FileData is referenced
757 * Checks the refcount and whether the FileData is locked.
759 static gboolean file_data_check_has_ref(FileData *fd)
761 return fd->ref > 0 || fd->locked;
765 * @brief Consider freeing a FileData.
767 * This function will free a FileData and its children provided that neither its parent nor it has
768 * a positive refcount, and provided that neither is locked.
770 static void file_data_consider_free(FileData *fd)
773 FileData *parent = fd->parent ? fd->parent : fd;
775 g_assert(fd->magick == FD_MAGICK);
776 if (file_data_check_has_ref(fd)) return;
777 if (file_data_check_has_ref(parent)) return;
779 work = parent->sidecar_files;
782 auto sfd = static_cast<FileData *>(work->data);
783 if (file_data_check_has_ref(sfd)) return;
787 /* Neither the parent nor the siblings are referenced, so we can free everything */
788 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
789 fd->path, fd->parent ? parent->path : "-");
791 g_list_free_full(parent->sidecar_files, reinterpret_cast<GDestroyNotify>(file_data_free));
792 parent->sidecar_files = nullptr;
794 file_data_free(parent);
797 #ifdef DEBUG_FILEDATA
798 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
800 void file_data_unref(FileData *fd)
803 if (fd == nullptr) return;
804 if (fd->magick != FD_MAGICK)
805 #ifdef DEBUG_FILEDATA
806 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, (void *)fd);
808 log_printf("Error: fd magick mismatch fd=%p", fd);
810 g_assert(fd->magick == FD_MAGICK);
813 #ifdef DEBUG_FILEDATA
814 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->locked, fd->path,
817 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
820 // Free FileData if it's no longer ref'd
821 file_data_consider_free(fd);
825 * @brief Lock the FileData in memory.
827 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
828 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
829 * even if the code would continue to function properly even if the FileData were freed. Code that
830 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
832 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
833 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
835 void file_data_lock(FileData *fd)
837 if (fd == nullptr) return;
838 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
840 g_assert(fd->magick == FD_MAGICK);
843 DEBUG_2("file_data_ref fd=%p (%d): '%s'", (void *)fd, fd->ref, fd->path);
847 * @brief Reset the maintain-FileData-in-memory lock
849 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
850 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
851 * keeping it from being freed.
853 void file_data_unlock(FileData *fd)
855 if (fd == nullptr) return;
856 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
858 g_assert(fd->magick == FD_MAGICK);
861 // Free FileData if it's no longer ref'd
862 file_data_consider_free(fd);
866 * @brief Lock all of the FileDatas in the provided list
868 * @see file_data_lock(#FileData)
870 void file_data_lock_list(GList *list)
877 auto fd = static_cast<FileData *>(work->data);
884 * @brief Unlock all of the FileDatas in the provided list
886 * @see #file_data_unlock(#FileData)
888 void file_data_unlock_list(GList *list)
895 auto fd = static_cast<FileData *>(work->data);
897 file_data_unlock(fd);
902 *-----------------------------------------------------------------------------
903 * sidecar file info struct
904 *-----------------------------------------------------------------------------
907 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
909 auto fda = static_cast<const FileData *>(a);
910 auto fdb = static_cast<const FileData *>(b);
912 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
913 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
915 return strcmp(fdb->extension, fda->extension);
919 static gint sidecar_file_priority(const gchar *extension)
924 if (extension == nullptr)
927 work = sidecar_ext_get_list();
930 auto ext = static_cast<gchar *>(work->data);
933 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
939 static void file_data_check_sidecars(const GList *basename_list)
941 /* basename_list contains the new group - first is the parent, then sorted sidecars */
942 /* all files in the list have ref count > 0 */
945 GList *s_work, *new_sidecars;
948 if (!basename_list) return;
951 DEBUG_2("basename start");
952 work = basename_list;
955 auto fd = static_cast<FileData *>(work->data);
957 g_assert(fd->magick == FD_MAGICK);
958 DEBUG_2("basename: %p %s", (void *)fd, fd->name);
961 g_assert(fd->parent->magick == FD_MAGICK);
962 DEBUG_2(" parent: %p", (void *)fd->parent);
964 s_work = fd->sidecar_files;
967 auto sfd = static_cast<FileData *>(s_work->data);
968 s_work = s_work->next;
969 g_assert(sfd->magick == FD_MAGICK);
970 DEBUG_2(" sidecar: %p %s", (void *)sfd, sfd->name);
973 g_assert(fd->parent == nullptr || fd->sidecar_files == nullptr);
976 parent_fd = static_cast<FileData *>(basename_list->data);
978 /* check if the second and next entries of basename_list are already connected
979 as sidecars of the first entry (parent_fd) */
980 work = basename_list->next;
981 s_work = parent_fd->sidecar_files;
983 while (work && s_work)
985 if (work->data != s_work->data) break;
987 s_work = s_work->next;
990 if (!work && !s_work)
992 DEBUG_2("basename no change");
993 return; /* no change in grouping */
996 /* we have to regroup it */
998 /* first, disconnect everything and send notification*/
1000 work = basename_list;
1003 auto fd = static_cast<FileData *>(work->data);
1005 g_assert(fd->parent == nullptr || fd->sidecar_files == nullptr);
1009 FileData *old_parent = fd->parent;
1010 g_assert(old_parent->parent == nullptr || old_parent->sidecar_files == nullptr);
1011 file_data_ref(old_parent);
1012 file_data_disconnect_sidecar_file(old_parent, fd);
1013 file_data_send_notification(old_parent, NOTIFY_REREAD);
1014 file_data_unref(old_parent);
1017 while (fd->sidecar_files)
1019 auto sfd = static_cast<FileData *>(fd->sidecar_files->data);
1020 g_assert(sfd->parent == nullptr || sfd->sidecar_files == nullptr);
1022 file_data_disconnect_sidecar_file(fd, sfd);
1023 file_data_send_notification(sfd, NOTIFY_REREAD);
1024 file_data_unref(sfd);
1026 file_data_send_notification(fd, NOTIFY_GROUPING);
1028 g_assert(fd->parent == nullptr && fd->sidecar_files == nullptr);
1031 /* now we can form the new group */
1032 work = basename_list->next;
1033 new_sidecars = nullptr;
1036 auto sfd = static_cast<FileData *>(work->data);
1037 g_assert(sfd->magick == FD_MAGICK);
1038 g_assert(sfd->parent == nullptr && sfd->sidecar_files == nullptr);
1039 sfd->parent = parent_fd;
1040 new_sidecars = g_list_prepend(new_sidecars, sfd);
1043 g_assert(parent_fd->sidecar_files == nullptr);
1044 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
1045 DEBUG_1("basename group changed for %s", parent_fd->path);
1049 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
1051 g_assert(target->magick == FD_MAGICK);
1052 g_assert(sfd->magick == FD_MAGICK);
1053 g_assert(g_list_find(target->sidecar_files, sfd));
1055 file_data_ref(target);
1058 g_assert(sfd->parent == target);
1060 file_data_increment_version(sfd); /* increments both sfd and target */
1062 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
1063 sfd->parent = nullptr;
1064 g_free(sfd->extended_extension);
1065 sfd->extended_extension = nullptr;
1067 file_data_unref(target);
1068 file_data_unref(sfd);
1071 /* disables / enables grouping for particular file, sends UPDATE notification */
1072 void file_data_disable_grouping(FileData *fd, gboolean disable)
1074 if (!fd->disable_grouping == !disable) return;
1076 fd->disable_grouping = !!disable;
1082 FileData *parent = file_data_ref(fd->parent);
1083 file_data_disconnect_sidecar_file(parent, fd);
1084 file_data_send_notification(parent, NOTIFY_GROUPING);
1085 file_data_unref(parent);
1087 else if (fd->sidecar_files)
1089 GList *sidecar_files = filelist_copy(fd->sidecar_files);
1090 GList *work = sidecar_files;
1093 auto sfd = static_cast<FileData *>(work->data);
1095 file_data_disconnect_sidecar_file(fd, sfd);
1096 file_data_send_notification(sfd, NOTIFY_GROUPING);
1098 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
1099 filelist_free(sidecar_files);
1103 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
1108 file_data_increment_version(fd);
1109 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
1111 file_data_send_notification(fd, NOTIFY_GROUPING);
1114 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
1121 auto fd = static_cast<FileData *>(work->data);
1123 file_data_disable_grouping(fd, disable);
1131 *-----------------------------------------------------------------------------
1133 *-----------------------------------------------------------------------------
1137 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
1140 if (!filelist_sort_ascend)
1147 switch (filelist_sort_method)
1152 if (fa->size < fb->size) return -1;
1153 if (fa->size > fb->size) return 1;
1154 /* fall back to name */
1157 if (fa->date < fb->date) return -1;
1158 if (fa->date > fb->date) return 1;
1159 /* fall back to name */
1162 if (fa->cdate < fb->cdate) return -1;
1163 if (fa->cdate > fb->cdate) return 1;
1164 /* fall back to name */
1167 if (fa->exifdate < fb->exifdate) return -1;
1168 if (fa->exifdate > fb->exifdate) return 1;
1169 /* fall back to name */
1171 case SORT_EXIFTIMEDIGITIZED:
1172 if (fa->exifdate_digitized < fb->exifdate_digitized) return -1;
1173 if (fa->exifdate_digitized > fb->exifdate_digitized) return 1;
1174 /* fall back to name */
1177 if (fa->rating < fb->rating) return -1;
1178 if (fa->rating > fb->rating) return 1;
1179 /* fall back to name */
1182 if (fa->format_class < fb->format_class) return -1;
1183 if (fa->format_class > fb->format_class) return 1;
1184 /* fall back to name */
1187 ret = strcmp(fa->collate_key_name_natural, fb->collate_key_name_natural);
1188 if (ret != 0) return ret;
1189 /* fall back to name */
1195 if (filelist_sort_case)
1196 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1198 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1200 if (ret != 0) return ret;
1202 /* do not return 0 unless the files are really the same
1203 file_data_pool ensures that original_path is unique
1205 return strcmp(fa->original_path, fb->original_path);
1208 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1210 filelist_sort_method = method;
1211 filelist_sort_ascend = ascend;
1212 return filelist_sort_compare_filedata(fa, fb);
1215 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1217 return filelist_sort_compare_filedata(static_cast<FileData *>(a), static_cast<FileData *>(b));
1220 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, gboolean case_sensitive, GCompareFunc cb)
1222 filelist_sort_method = method;
1223 filelist_sort_ascend = ascend;
1224 filelist_sort_case = case_sensitive;
1225 return g_list_sort(list, cb);
1228 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, gboolean case_sensitive, GCompareFunc cb)
1230 filelist_sort_method = method;
1231 filelist_sort_ascend = ascend;
1232 filelist_sort_case = case_sensitive;
1233 return g_list_insert_sorted(list, data, cb);
1236 GList *filelist_sort(GList *list, SortType method, gboolean ascend, gboolean case_sensitive)
1238 return filelist_sort_full(list, method, ascend, case_sensitive, reinterpret_cast<GCompareFunc>(filelist_sort_file_cb));
1241 #pragma GCC diagnostic push
1242 #pragma GCC diagnostic ignored "-Wunused-function"
1243 GList *filelist_insert_sort_unused(GList *list, FileData *fd, SortType method, gboolean ascend)
1245 return filelist_insert_sort_full(list, fd, method, ascend, ascend, (GCompareFunc) filelist_sort_file_cb);
1247 #pragma GCC diagnostic pop
1250 *-----------------------------------------------------------------------------
1251 * basename hash - grouping of sidecars in filelist
1252 *-----------------------------------------------------------------------------
1256 static GHashTable *file_data_basename_hash_new()
1258 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, nullptr);
1261 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1264 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1266 list = static_cast<GList *>(g_hash_table_lookup(basename_hash, basename));
1270 DEBUG_1("TG: basename_hash not found for %s",fd->path);
1271 const gchar *parent_extension = registered_extension_from_path(basename);
1273 if (parent_extension)
1275 DEBUG_1("TG: parent extension %s",parent_extension);
1276 gchar *parent_basename = g_strndup(basename, parent_extension - basename);
1277 DEBUG_1("TG: parent basename %s",parent_basename);
1278 auto parent_fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, basename));
1281 DEBUG_1("TG: parent fd found");
1282 list = static_cast<GList *>(g_hash_table_lookup(basename_hash, parent_basename));
1283 if (!g_list_find(list, parent_fd))
1285 DEBUG_1("TG: parent fd doesn't fit");
1286 g_free(parent_basename);
1292 basename = parent_basename;
1293 fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
1299 if (!g_list_find(list, fd))
1301 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1302 g_hash_table_insert(basename_hash, basename, list);
1311 static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
1313 file_data_basename_hash_insert(static_cast<GHashTable *>(basename_hash), static_cast<FileData *>(fd));
1316 static void file_data_basename_hash_remove_list(gpointer, gpointer value, gpointer)
1318 filelist_free(static_cast<GList *>(value));
1321 static void file_data_basename_hash_free(GHashTable *basename_hash)
1323 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, nullptr);
1324 g_hash_table_destroy(basename_hash);
1328 *-----------------------------------------------------------------------------
1329 * handling sidecars in filelist
1330 *-----------------------------------------------------------------------------
1333 static GList *filelist_filter_out_sidecars(GList *flist)
1335 GList *work = flist;
1336 GList *flist_filtered = nullptr;
1340 auto fd = static_cast<FileData *>(work->data);
1343 if (fd->parent) /* remove fd's that are children */
1344 file_data_unref(fd);
1346 flist_filtered = g_list_prepend(flist_filtered, fd);
1350 return flist_filtered;
1353 static void file_data_basename_hash_to_sidecars(gpointer, gpointer value, gpointer)
1355 auto basename_list = static_cast<GList *>(value);
1356 file_data_check_sidecars(basename_list);
1360 static gboolean is_hidden_file(const gchar *name)
1362 if (name[0] != '.') return FALSE;
1363 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1368 *-----------------------------------------------------------------------------
1369 * the main filelist function
1370 *-----------------------------------------------------------------------------
1373 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1378 GList *dlist = nullptr;
1379 GList *flist = nullptr;
1380 GList *xmp_files = nullptr;
1381 gint (*stat_func)(const gchar *path, struct stat *buf);
1382 GHashTable *basename_hash = nullptr;
1384 g_assert(files || dirs);
1386 if (files) *files = nullptr;
1387 if (dirs) *dirs = nullptr;
1389 pathl = path_from_utf8(dir_path);
1390 if (!pathl) return FALSE;
1392 dp = opendir(pathl);
1399 if (files) basename_hash = file_data_basename_hash_new();
1401 if (follow_symlinks)
1406 while ((dir = readdir(dp)) != nullptr)
1408 struct stat ent_sbuf;
1409 const gchar *name = dir->d_name;
1412 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1415 filepath = g_build_filename(pathl, name, NULL);
1416 if (stat_func(filepath, &ent_sbuf) >= 0)
1418 if (S_ISDIR(ent_sbuf.st_mode))
1420 /* we ignore the .thumbnails dir for cleanliness */
1422 (name[0] != '.' || (name[1] != '\0' && (name[1] != '.' || name[2] != '\0'))) &&
1423 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1424 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1425 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1427 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1432 if (files && filter_name_exists(name))
1434 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1435 flist = g_list_prepend(flist, fd);
1436 if (fd->sidecar_priority && !fd->disable_grouping)
1438 if (strcmp(fd->extension, ".xmp") != 0)
1439 file_data_basename_hash_insert(basename_hash, fd);
1441 xmp_files = g_list_append(xmp_files, fd);
1448 if (errno == EOVERFLOW)
1450 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1462 g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
1463 g_list_free(xmp_files);
1466 if (dirs) *dirs = dlist;
1470 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, nullptr);
1472 *files = filelist_filter_out_sidecars(flist);
1474 if (basename_hash) file_data_basename_hash_free(basename_hash);
1479 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1481 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1484 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1486 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1489 FileData *file_data_new_group(const gchar *path_utf8)
1496 if (!file_data_pool)
1498 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
1501 if (!stat_utf8(path_utf8, &st))
1507 if (S_ISDIR(st.st_mode))
1508 return file_data_new(path_utf8, &st, TRUE);
1510 dir = remove_level_from_path(path_utf8);
1512 filelist_read_real(dir, &files, nullptr, TRUE);
1514 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
1515 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1521 filelist_free(files);
1527 void filelist_free(GList *list)
1534 file_data_unref((FileData *)work->data);
1542 GList *filelist_copy(GList *list)
1544 GList *new_list = nullptr;
1546 for (GList *work = list; work; work = work->next)
1548 auto fd = static_cast<FileData *>(work->data);
1550 new_list = g_list_prepend(new_list, file_data_ref(fd));
1553 return g_list_reverse(new_list);
1556 GList *filelist_from_path_list(GList *list)
1558 GList *new_list = nullptr;
1566 path = static_cast<gchar *>(work->data);
1569 new_list = g_list_prepend(new_list, file_data_new_group(path));
1572 return g_list_reverse(new_list);
1575 GList *filelist_to_path_list(GList *list)
1577 GList *new_list = nullptr;
1585 fd = static_cast<FileData *>(work->data);
1588 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1591 return g_list_reverse(new_list);
1594 GList *filelist_filter(GList *list, gboolean is_dir_list)
1598 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1603 auto fd = static_cast<FileData *>(work->data);
1604 const gchar *name = fd->name;
1608 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1609 (!is_dir_list && !filter_name_exists(name)) ||
1610 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1611 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1613 list = g_list_remove_link(list, link);
1614 file_data_unref(fd);
1623 *-----------------------------------------------------------------------------
1624 * filelist recursive
1625 *-----------------------------------------------------------------------------
1628 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1630 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1633 GList *filelist_sort_path(GList *list)
1635 return g_list_sort(list, filelist_sort_path_cb);
1638 static void filelist_recursive_append(GList **list, GList *dirs)
1645 auto fd = static_cast<FileData *>(work->data);
1649 if (filelist_read(fd, &f, &d))
1651 f = filelist_filter(f, FALSE);
1652 f = filelist_sort_path(f);
1653 *list = g_list_concat(*list, f);
1655 d = filelist_filter(d, TRUE);
1656 d = filelist_sort_path(d);
1657 filelist_recursive_append(list, d);
1665 static void filelist_recursive_append_full(GList **list, GList *dirs, SortType method, gboolean ascend, gboolean case_sensitive)
1672 auto fd = static_cast<FileData *>(work->data);
1676 if (filelist_read(fd, &f, &d))
1678 f = filelist_filter(f, FALSE);
1679 f = filelist_sort_full(f, method, ascend, case_sensitive, reinterpret_cast<GCompareFunc>(filelist_sort_file_cb));
1680 *list = g_list_concat(*list, f);
1682 d = filelist_filter(d, TRUE);
1683 d = filelist_sort_path(d);
1684 filelist_recursive_append_full(list, d, method, ascend, case_sensitive);
1692 GList *filelist_recursive(FileData *dir_fd)
1697 if (!filelist_read(dir_fd, &list, &d)) return nullptr;
1698 list = filelist_filter(list, FALSE);
1699 list = filelist_sort_path(list);
1701 d = filelist_filter(d, TRUE);
1702 d = filelist_sort_path(d);
1703 filelist_recursive_append(&list, d);
1709 GList *filelist_recursive_full(FileData *dir_fd, SortType method, gboolean ascend, gboolean case_sensitive)
1714 if (!filelist_read(dir_fd, &list, &d)) return nullptr;
1715 list = filelist_filter(list, FALSE);
1716 list = filelist_sort_full(list, method, ascend, case_sensitive, reinterpret_cast<GCompareFunc>(filelist_sort_file_cb));
1718 d = filelist_filter(d, TRUE);
1719 d = filelist_sort_path(d);
1720 filelist_recursive_append_full(&list, d, method, ascend, case_sensitive);
1727 *-----------------------------------------------------------------------------
1728 * file modification support
1729 *-----------------------------------------------------------------------------
1733 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1735 if (!fdci && fd) fdci = fd->change;
1739 g_free(fdci->source);
1744 if (fd) fd->change = nullptr;
1747 static gboolean file_data_can_write_directly(FileData *fd)
1749 return filter_name_is_writable(fd->extension);
1752 static gboolean file_data_can_write_sidecar(FileData *fd)
1754 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1757 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1759 gchar *sidecar_path = nullptr;
1762 if (!file_data_can_write_sidecar(fd)) return nullptr;
1764 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1765 gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1768 auto sfd = static_cast<FileData *>(work->data);
1770 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1772 sidecar_path = g_strdup(sfd->path);
1776 g_free(extended_extension);
1778 if (!existing_only && !sidecar_path)
1780 if (options->metadata.sidecar_extended_name)
1781 sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1784 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1785 sidecar_path = g_strconcat(base, ".xmp", NULL);
1790 return sidecar_path;
1794 * marks and orientation
1797 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1798 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1799 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1800 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1802 gboolean file_data_get_mark(FileData *fd, gint n)
1804 gboolean valid = (fd->valid_marks & (1 << n));
1806 if (file_data_get_mark_func[n] && !valid)
1808 guint old = fd->marks;
1809 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1811 if (!value != !(fd->marks & (1 << n)))
1813 fd->marks = fd->marks ^ (1 << n);
1816 fd->valid_marks |= (1 << n);
1817 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1819 file_data_unref(fd);
1821 else if (!old && fd->marks)
1827 return !!(fd->marks & (1 << n));
1830 guint file_data_get_marks(FileData *fd)
1833 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1837 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1840 if (!value == !file_data_get_mark(fd, n)) return;
1842 if (file_data_set_mark_func[n])
1844 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1849 fd->marks = fd->marks ^ (1 << n);
1851 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1853 file_data_unref(fd);
1855 else if (!old && fd->marks)
1860 file_data_increment_version(fd);
1861 file_data_send_notification(fd, NOTIFY_MARKS);
1864 gboolean file_data_filter_marks(FileData *fd, guint filter)
1867 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1868 return ((fd->marks & filter) == filter);
1871 GList *file_data_filter_marks_list(GList *list, guint filter)
1878 auto fd = static_cast<FileData *>(work->data);
1882 if (!file_data_filter_marks(fd, filter))
1884 list = g_list_remove_link(list, link);
1885 file_data_unref(fd);
1893 gboolean file_data_filter_file_filter(FileData *fd, GRegex *filter)
1895 return g_regex_match(filter, fd->name, static_cast<GRegexMatchFlags>(0), nullptr);
1898 GList *file_data_filter_file_filter_list(GList *list, GRegex *filter)
1905 auto fd = static_cast<FileData *>(work->data);
1909 if (!file_data_filter_file_filter(fd, filter))
1911 list = g_list_remove_link(list, link);
1912 file_data_unref(fd);
1920 static gboolean file_data_filter_class(FileData *fd, guint filter)
1924 for (i = 0; i < FILE_FORMAT_CLASSES; i++)
1926 if (filter & (1 << i))
1928 if (static_cast<FileFormatClass>(i) == filter_file_get_class(fd->path))
1938 GList *file_data_filter_class_list(GList *list, guint filter)
1945 auto fd = static_cast<FileData *>(work->data);
1949 if (!file_data_filter_class(fd, filter))
1951 list = g_list_remove_link(list, link);
1952 file_data_unref(fd);
1960 static void file_data_notify_mark_func(gpointer, gpointer value, gpointer)
1962 auto fd = static_cast<FileData *>(value);
1963 file_data_increment_version(fd);
1964 file_data_send_notification(fd, NOTIFY_MARKS);
1967 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1969 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1971 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1973 file_data_get_mark_func[n] = get_mark_func;
1974 file_data_set_mark_func[n] = set_mark_func;
1975 file_data_mark_func_data[n] = data;
1976 file_data_destroy_mark_func[n] = notify;
1978 if (get_mark_func && file_data_pool)
1980 /* this effectively changes all known files */
1981 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, nullptr);
1987 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1989 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1990 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1991 if (data) *data = file_data_mark_func_data[n];
1994 #pragma GCC diagnostic push
1995 #pragma GCC diagnostic ignored "-Wunused-function"
1996 gint file_data_get_user_orientation_unused(FileData *fd)
1998 return fd->user_orientation;
2001 void file_data_set_user_orientation_unused(FileData *fd, gint value)
2003 if (fd->user_orientation == value) return;
2005 fd->user_orientation = value;
2006 file_data_increment_version(fd);
2007 file_data_send_notification(fd, NOTIFY_ORIENTATION);
2009 #pragma GCC diagnostic pop
2013 * file_data - operates on the given fd
2014 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
2018 /* return list of sidecar file extensions in a string */
2019 gchar *file_data_sc_list_to_string(FileData *fd)
2022 GString *result = g_string_new("");
2024 work = fd->sidecar_files;
2027 auto sfd = static_cast<FileData *>(work->data);
2029 result = g_string_append(result, "+ ");
2030 result = g_string_append(result, sfd->extension);
2032 if (work) result = g_string_append_c(result, ' ');
2035 return g_string_free(result, FALSE);
2041 * add FileDataChangeInfo (see typedefs.h) for the given operation
2042 * uses file_data_add_change_info
2044 * fails if the fd->change already exists - change operations can't run in parallel
2045 * fd->change_info works as a lock
2047 * dest can be NULL - in this case the current name is used for now, it will
2052 FileDataChangeInfo types:
2054 MOVE - path is changed, name may be changed too
2055 RENAME - path remains unchanged, name is changed
2056 extension should remain (FIXME should we allow editing extension? it will make problems with grouping)
2057 sidecar names are changed too, extensions are not changed
2059 UPDATE - file size, date or grouping has been changed
2062 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
2064 FileDataChangeInfo *fdci;
2066 if (fd->change) return FALSE;
2068 fdci = g_new0(FileDataChangeInfo, 1);
2073 fdci->source = g_strdup(src);
2075 fdci->source = g_strdup(fd->path);
2078 fdci->dest = g_strdup(dest);
2085 static void file_data_planned_change_remove(FileData *fd)
2087 if (file_data_planned_change_hash &&
2088 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
2090 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
2092 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
2093 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
2094 file_data_unref(fd);
2095 if (g_hash_table_size(file_data_planned_change_hash) == 0)
2097 g_hash_table_destroy(file_data_planned_change_hash);
2098 file_data_planned_change_hash = nullptr;
2099 DEBUG_1("planned change: empty");
2106 void file_data_free_ci(FileData *fd)
2108 FileDataChangeInfo *fdci = fd->change;
2112 file_data_planned_change_remove(fd);
2114 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
2116 g_free(fdci->source);
2121 fd->change = nullptr;
2124 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
2126 FileDataChangeInfo *fdci = fd->change;
2128 fdci->regroup_when_finished = enable;
2131 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
2135 if (fd->parent) fd = fd->parent;
2137 if (fd->change) return FALSE;
2139 work = fd->sidecar_files;
2142 auto sfd = static_cast<FileData *>(work->data);
2144 if (sfd->change) return FALSE;
2148 file_data_add_ci(fd, type, nullptr, nullptr);
2150 work = fd->sidecar_files;
2153 auto sfd = static_cast<FileData *>(work->data);
2155 file_data_add_ci(sfd, type, nullptr, nullptr);
2162 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
2166 if (fd->parent) fd = fd->parent;
2168 if (!fd->change || fd->change->type != type) return FALSE;
2170 work = fd->sidecar_files;
2173 auto sfd = static_cast<FileData *>(work->data);
2175 if (!sfd->change || sfd->change->type != type) return FALSE;
2183 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
2185 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
2186 file_data_sc_update_ci_copy(fd, dest_path);
2190 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
2192 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
2193 file_data_sc_update_ci_move(fd, dest_path);
2197 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
2199 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
2200 file_data_sc_update_ci_rename(fd, dest_path);
2204 gboolean file_data_sc_add_ci_delete(FileData *fd)
2206 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
2209 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
2211 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
2212 file_data_sc_update_ci_unspecified(fd, dest_path);
2216 gboolean file_data_add_ci_write_metadata(FileData *fd)
2218 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, nullptr, nullptr);
2221 void file_data_sc_free_ci(FileData *fd)
2225 if (fd->parent) fd = fd->parent;
2227 file_data_free_ci(fd);
2229 work = fd->sidecar_files;
2232 auto sfd = static_cast<FileData *>(work->data);
2234 file_data_free_ci(sfd);
2239 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2242 gboolean ret = TRUE;
2247 auto fd = static_cast<FileData *>(work->data);
2249 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2256 static void file_data_sc_revert_ci_list(GList *fd_list)
2263 auto fd = static_cast<FileData *>(work->data);
2265 file_data_sc_free_ci(fd);
2270 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2277 auto fd = static_cast<FileData *>(work->data);
2279 if (!func(fd, dest))
2281 file_data_sc_revert_ci_list(work->prev);
2290 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2292 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2295 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2297 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2300 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2302 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2305 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2307 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2310 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2313 gboolean ret = TRUE;
2318 auto fd = static_cast<FileData *>(work->data);
2320 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2327 void file_data_free_ci_list(GList *fd_list)
2334 auto fd = static_cast<FileData *>(work->data);
2336 file_data_free_ci(fd);
2341 void file_data_sc_free_ci_list(GList *fd_list)
2348 auto fd = static_cast<FileData *>(work->data);
2350 file_data_sc_free_ci(fd);
2356 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2357 * fails if fd->change does not exist or the change type does not match
2360 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2362 FileDataChangeType type = fd->change->type;
2364 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2368 if (!file_data_planned_change_hash)
2369 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2371 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2373 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2374 g_hash_table_remove(file_data_planned_change_hash, old_path);
2375 file_data_unref(fd);
2378 ofd = static_cast<FileData *>(g_hash_table_lookup(file_data_planned_change_hash, new_path));
2383 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2384 g_hash_table_remove(file_data_planned_change_hash, new_path);
2385 file_data_unref(ofd);
2388 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2390 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2395 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2397 gchar *old_path = fd->change->dest;
2399 fd->change->dest = g_strdup(dest_path);
2400 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2404 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2406 const gchar *extension = registered_extension_from_path(fd->change->source);
2407 gchar *base = remove_extension_from_path(dest_path);
2408 gchar *old_path = fd->change->dest;
2410 fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2411 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2417 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2420 gchar *dest_path_full = nullptr;
2422 if (fd->parent) fd = fd->parent;
2426 dest_path = fd->path;
2428 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2430 gchar *dir = remove_level_from_path(fd->path);
2432 dest_path_full = g_build_filename(dir, dest_path, NULL);
2434 dest_path = dest_path_full;
2436 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2438 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2439 dest_path = dest_path_full;
2442 file_data_update_ci_dest(fd, dest_path);
2444 work = fd->sidecar_files;
2447 auto sfd = static_cast<FileData *>(work->data);
2449 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2453 g_free(dest_path_full);
2456 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2458 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2459 file_data_sc_update_ci(fd, dest_path);
2463 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2465 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2468 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2470 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2473 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2475 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2478 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2480 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2483 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2485 gboolean (*func)(FileData *, const gchar *))
2488 gboolean ret = TRUE;
2493 auto fd = static_cast<FileData *>(work->data);
2495 if (!func(fd, dest)) ret = FALSE;
2502 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2504 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2507 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2509 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2512 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2514 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2519 * verify source and dest paths - dest image exists, etc.
2520 * it should detect all possible problems with the planned operation
2523 gint file_data_verify_ci(FileData *fd, GList *list)
2525 gint ret = CHANGE_OK;
2527 GList *work = nullptr;
2528 FileData *fd1 = nullptr;
2532 DEBUG_1("Change checked: no change info: %s", fd->path);
2536 if (!isname(fd->path))
2538 /* this probably should not happen */
2539 ret |= CHANGE_NO_SRC;
2540 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2544 dir = remove_level_from_path(fd->path);
2546 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2547 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2548 fd->change->type != FILEDATA_CHANGE_RENAME &&
2549 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2552 ret |= CHANGE_WARN_UNSAVED_META;
2553 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2556 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2557 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2558 !access_file(fd->path, R_OK))
2560 ret |= CHANGE_NO_READ_PERM;
2561 DEBUG_1("Change checked: no read permission: %s", fd->path);
2563 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2564 !access_file(dir, W_OK))
2566 ret |= CHANGE_NO_WRITE_PERM_DIR;
2567 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2569 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2570 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2571 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2572 !access_file(fd->path, W_OK))
2574 ret |= CHANGE_WARN_NO_WRITE_PERM;
2575 DEBUG_1("Change checked: no write permission: %s", fd->path);
2577 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2578 - that means that there are no hard errors and warnings can be disabled
2579 - the destination is determined during the check
2581 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2583 /* determine destination file */
2584 gboolean have_dest = FALSE;
2585 gchar *dest_dir = nullptr;
2587 if (options->metadata.save_in_image_file)
2589 if (file_data_can_write_directly(fd))
2591 /* we can write the file directly */
2592 if (access_file(fd->path, W_OK))
2598 if (options->metadata.warn_on_write_problems)
2600 ret |= CHANGE_WARN_NO_WRITE_PERM;
2601 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2605 else if (file_data_can_write_sidecar(fd))
2607 /* we can write sidecar */
2608 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2609 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2611 file_data_update_ci_dest(fd, sidecar);
2616 if (options->metadata.warn_on_write_problems)
2618 ret |= CHANGE_WARN_NO_WRITE_PERM;
2619 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2628 /* write private metadata file under ~/.geeqie */
2630 /* If an existing metadata file exists, we will try writing to
2631 * it's location regardless of the user's preference.
2633 gchar *metadata_path = nullptr;
2635 /* but ignore XMP if we are not able to write it */
2636 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2638 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2640 if (metadata_path && !access_file(metadata_path, W_OK))
2642 g_free(metadata_path);
2643 metadata_path = nullptr;
2650 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2651 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2653 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2655 metadata_path = g_build_filename(dest_dir, filename, NULL);
2659 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2661 file_data_update_ci_dest(fd, metadata_path);
2665 ret |= CHANGE_NO_WRITE_PERM_DEST;
2666 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2668 g_free(metadata_path);
2673 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2678 same = (strcmp(fd->path, fd->change->dest) == 0);
2682 const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2683 if (!dest_ext) dest_ext = "";
2684 if (!options->file_filter.disable_file_extension_checks)
2686 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2688 ret |= CHANGE_WARN_CHANGED_EXT;
2689 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2695 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /** @FIXME this is now needed for running editors */
2697 ret |= CHANGE_WARN_SAME;
2698 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2702 dest_dir = remove_level_from_path(fd->change->dest);
2704 if (!isdir(dest_dir))
2706 ret |= CHANGE_NO_DEST_DIR;
2707 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2709 else if (!access_file(dest_dir, W_OK))
2711 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2712 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2716 if (isfile(fd->change->dest))
2718 if (!access_file(fd->change->dest, W_OK))
2720 ret |= CHANGE_NO_WRITE_PERM_DEST;
2721 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2725 ret |= CHANGE_WARN_DEST_EXISTS;
2726 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2729 else if (isdir(fd->change->dest))
2731 ret |= CHANGE_DEST_EXISTS;
2732 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2739 /* During a rename operation, check if another planned destination file has
2742 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2743 fd->change->type == FILEDATA_CHANGE_COPY ||
2744 fd->change->type == FILEDATA_CHANGE_MOVE)
2749 fd1 = static_cast<FileData *>(work->data);
2751 if (fd1 != nullptr && fd != fd1 )
2753 if (!strcmp(fd->change->dest, fd1->change->dest))
2755 ret |= CHANGE_DUPLICATE_DEST;
2761 fd->change->error = ret;
2762 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2769 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2774 ret = file_data_verify_ci(fd, list);
2776 work = fd->sidecar_files;
2779 auto sfd = static_cast<FileData *>(work->data);
2781 ret |= file_data_verify_ci(sfd, list);
2788 gchar *file_data_get_error_string(gint error)
2790 GString *result = g_string_new("");
2792 if (error & CHANGE_NO_SRC)
2794 if (result->len > 0) g_string_append(result, ", ");
2795 g_string_append(result, _("file or directory does not exist"));
2798 if (error & CHANGE_DEST_EXISTS)
2800 if (result->len > 0) g_string_append(result, ", ");
2801 g_string_append(result, _("destination already exists"));
2804 if (error & CHANGE_NO_WRITE_PERM_DEST)
2806 if (result->len > 0) g_string_append(result, ", ");
2807 g_string_append(result, _("destination can't be overwritten"));
2810 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2812 if (result->len > 0) g_string_append(result, ", ");
2813 g_string_append(result, _("destination directory is not writable"));
2816 if (error & CHANGE_NO_DEST_DIR)
2818 if (result->len > 0) g_string_append(result, ", ");
2819 g_string_append(result, _("destination directory does not exist"));
2822 if (error & CHANGE_NO_WRITE_PERM_DIR)
2824 if (result->len > 0) g_string_append(result, ", ");
2825 g_string_append(result, _("source directory is not writable"));
2828 if (error & CHANGE_NO_READ_PERM)
2830 if (result->len > 0) g_string_append(result, ", ");
2831 g_string_append(result, _("no read permission"));
2834 if (error & CHANGE_WARN_NO_WRITE_PERM)
2836 if (result->len > 0) g_string_append(result, ", ");
2837 g_string_append(result, _("file is readonly"));
2840 if (error & CHANGE_WARN_DEST_EXISTS)
2842 if (result->len > 0) g_string_append(result, ", ");
2843 g_string_append(result, _("destination already exists and will be overwritten"));
2846 if (error & CHANGE_WARN_SAME)
2848 if (result->len > 0) g_string_append(result, ", ");
2849 g_string_append(result, _("source and destination are the same"));
2852 if (error & CHANGE_WARN_CHANGED_EXT)
2854 if (result->len > 0) g_string_append(result, ", ");
2855 g_string_append(result, _("source and destination have different extension"));
2858 if (error & CHANGE_WARN_UNSAVED_META)
2860 if (result->len > 0) g_string_append(result, ", ");
2861 g_string_append(result, _("there are unsaved metadata changes for the file"));
2864 if (error & CHANGE_DUPLICATE_DEST)
2866 if (result->len > 0) g_string_append(result, ", ");
2867 g_string_append(result, _("another destination file has the same filename"));
2870 return g_string_free(result, FALSE);
2873 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2876 gint all_errors = 0;
2877 gint common_errors = ~0;
2882 if (!list) return 0;
2884 num = g_list_length(list);
2885 errors = g_new(int, num);
2893 fd = static_cast<FileData *>(work->data);
2896 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2897 all_errors |= error;
2898 common_errors &= error;
2905 if (desc && all_errors)
2908 GString *result = g_string_new("");
2912 gchar *str = file_data_get_error_string(common_errors);
2913 g_string_append(result, str);
2914 g_string_append(result, "\n");
2925 fd = static_cast<FileData *>(work->data);
2928 error = errors[i] & ~common_errors;
2932 gchar *str = file_data_get_error_string(error);
2933 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2938 *desc = g_string_free(result, FALSE);
2947 * perform the change described by FileFataChangeInfo
2948 * it is used for internal operations,
2949 * this function actually operates with files on the filesystem
2950 * it should implement safe delete
2953 static gboolean file_data_perform_move(FileData *fd)
2955 g_assert(!strcmp(fd->change->source, fd->path));
2956 return move_file(fd->change->source, fd->change->dest);
2959 static gboolean file_data_perform_copy(FileData *fd)
2961 g_assert(!strcmp(fd->change->source, fd->path));
2962 return copy_file(fd->change->source, fd->change->dest);
2965 static gboolean file_data_perform_delete(FileData *fd)
2967 if (isdir(fd->path) && !islink(fd->path))
2968 return rmdir_utf8(fd->path);
2970 if (options->file_ops.safe_delete_enable)
2971 return file_util_safe_unlink(fd->path);
2973 return unlink_file(fd->path);
2976 gboolean file_data_perform_ci(FileData *fd)
2978 /** @FIXME When a directory that is a symbolic link is deleted,
2979 * at this point fd->change is null because no FileDataChangeInfo
2980 * has been set up. Therefore there is a seg. fault.
2981 * This code simply aborts the delete.
2988 FileDataChangeType type = fd->change->type;
2992 case FILEDATA_CHANGE_MOVE:
2993 return file_data_perform_move(fd);
2994 case FILEDATA_CHANGE_COPY:
2995 return file_data_perform_copy(fd);
2996 case FILEDATA_CHANGE_RENAME:
2997 return file_data_perform_move(fd); /* the same as move */
2998 case FILEDATA_CHANGE_DELETE:
2999 return file_data_perform_delete(fd);
3000 case FILEDATA_CHANGE_WRITE_METADATA:
3001 return metadata_write_perform(fd);
3002 case FILEDATA_CHANGE_UNSPECIFIED:
3003 /* nothing to do here */
3011 gboolean file_data_sc_perform_ci(FileData *fd)
3014 gboolean ret = TRUE;
3015 FileDataChangeType type = fd->change->type;
3017 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3019 work = fd->sidecar_files;
3022 auto sfd = static_cast<FileData *>(work->data);
3024 if (!file_data_perform_ci(sfd)) ret = FALSE;
3028 if (!file_data_perform_ci(fd)) ret = FALSE;
3034 * updates FileData structure according to FileDataChangeInfo
3037 gboolean file_data_apply_ci(FileData *fd)
3039 FileDataChangeType type = fd->change->type;
3041 /** @FIXME delete ?*/
3042 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
3044 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
3045 file_data_planned_change_remove(fd);
3047 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
3049 /* this change overwrites another file which is already known to other modules
3050 renaming fd would create duplicate FileData structure
3051 the best thing we can do is nothing
3053 /** @FIXME maybe we could copy stuff like marks
3055 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
3059 file_data_set_path(fd, fd->change->dest);
3062 file_data_increment_version(fd);
3063 file_data_send_notification(fd, NOTIFY_CHANGE);
3068 gboolean file_data_sc_apply_ci(FileData *fd)
3071 FileDataChangeType type = fd->change->type;
3073 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3075 work = fd->sidecar_files;
3078 auto sfd = static_cast<FileData *>(work->data);
3080 file_data_apply_ci(sfd);
3084 file_data_apply_ci(fd);
3089 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
3092 if (fd->parent) fd = fd->parent;
3093 if (!g_list_find(list, fd)) return FALSE;
3095 work = fd->sidecar_files;
3098 if (!g_list_find(list, work->data)) return FALSE;
3104 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
3106 GList *out = nullptr;
3109 /* change partial groups to independent files */
3114 auto fd = static_cast<FileData *>(work->data);
3117 if (!file_data_list_contains_whole_group(list, fd))
3119 file_data_disable_grouping(fd, TRUE);
3122 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
3128 /* remove sidecars from the list,
3129 they can be still accessed via main_fd->sidecar_files */
3133 auto fd = static_cast<FileData *>(work->data);
3137 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
3139 out = g_list_prepend(out, file_data_ref(fd));
3143 filelist_free(list);
3144 out = g_list_reverse(out);
3154 * notify other modules about the change described by FileDataChangeInfo
3157 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks */
3158 /** @FIXME do we need the ignore_list? It looks like a workaround for ineffective
3159 implementation in view-file-list.cc */
3162 struct NotifyIdleData {
3169 FileDataNotifyFunc func;
3171 NotifyPriority priority;
3174 static GList *notify_func_list = nullptr;
3176 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
3178 auto nda = static_cast<const NotifyData *>(a);
3179 auto ndb = static_cast<const NotifyData *>(b);
3181 if (nda->priority < ndb->priority) return -1;
3182 if (nda->priority > ndb->priority) return 1;
3186 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
3189 GList *work = notify_func_list;
3193 auto nd = static_cast<NotifyData *>(work->data);
3195 if (nd->func == func && nd->data == data)
3197 g_warning("Notify func already registered");
3203 nd = g_new(NotifyData, 1);
3206 nd->priority = priority;
3208 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
3209 DEBUG_2("Notify func registered: %p", (void *)nd);
3214 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
3216 GList *work = notify_func_list;
3220 auto nd = static_cast<NotifyData *>(work->data);
3222 if (nd->func == func && nd->data == data)
3224 notify_func_list = g_list_delete_link(notify_func_list, work);
3225 DEBUG_2("Notify func unregistered: %p", (void *)nd);
3232 g_warning("Notify func not found");
3236 #pragma GCC diagnostic push
3237 #pragma GCC diagnostic ignored "-Wunused-function"
3238 gboolean file_data_send_notification_idle_cb_unused(gpointer data)
3240 auto *nid = (NotifyIdleData *)data;
3241 GList *work = notify_func_list;
3245 auto *nd = (NotifyData *)work->data;
3247 nd->func(nid->fd, nid->type, nd->data);
3250 file_data_unref(nid->fd);
3254 #pragma GCC diagnostic pop
3256 void file_data_send_notification(FileData *fd, NotifyType type)
3258 GList *work = notify_func_list;
3262 auto nd = static_cast<NotifyData *>(work->data);
3264 nd->func(fd, type, nd->data);
3268 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3269 nid->fd = file_data_ref(fd);
3271 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3275 static GHashTable *file_data_monitor_pool = nullptr;
3276 static guint realtime_monitor_id = 0; /* event source id */
3278 static void realtime_monitor_check_cb(gpointer key, gpointer, gpointer)
3280 auto fd = static_cast<FileData *>(key);
3282 file_data_check_changed_files(fd);
3284 DEBUG_1("monitor %s", fd->path);
3287 static gboolean realtime_monitor_cb(gpointer)
3289 if (!options->update_on_time_change) return TRUE;
3290 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, nullptr);
3294 gboolean file_data_register_real_time_monitor(FileData *fd)
3300 if (!file_data_monitor_pool)
3301 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3303 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3305 DEBUG_1("Register realtime %d %s", count, fd->path);
3308 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3310 if (!realtime_monitor_id)
3312 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, nullptr);
3318 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3322 g_assert(file_data_monitor_pool);
3324 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3326 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3328 g_assert(count > 0);
3333 g_hash_table_remove(file_data_monitor_pool, fd);
3335 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3337 file_data_unref(fd);
3339 if (g_hash_table_size(file_data_monitor_pool) == 0)
3341 g_source_remove(realtime_monitor_id);
3342 realtime_monitor_id = 0;
3350 *-----------------------------------------------------------------------------
3351 * Saving marks list, clearing marks
3352 * Uses file_data_pool
3353 *-----------------------------------------------------------------------------
3356 static void marks_get_files(gpointer key, gpointer value, gpointer userdata)
3358 auto file_name = static_cast<gchar *>(key);
3359 auto result = static_cast<GString *>(userdata);
3362 if (isfile(file_name))
3364 fd = static_cast<FileData *>(value);
3365 if (fd && fd->marks > 0)
3367 g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks);
3372 gboolean marks_list_load(const gchar *path)
3380 pathl = path_from_utf8(path);
3381 f = fopen(pathl, "r");
3383 if (!f) return FALSE;
3385 /* first line must start with Marks comment */
3386 if (!fgets(s_buf, sizeof(s_buf), f) ||
3387 strncmp(s_buf, "#Marks", 6) != 0)
3393 while (fgets(s_buf, sizeof(s_buf), f))
3395 if (s_buf[0]=='#') continue;
3396 file_path = strtok(s_buf, ",");
3397 marks_value = strtok(nullptr, ",");
3398 if (isfile(file_path))
3400 FileData *fd = file_data_new_no_grouping(file_path);
3405 gint mark_no = 1 << n;
3406 if (atoi(marks_value) & mark_no)
3408 file_data_set_mark(fd, n , 1);
3419 gboolean marks_list_save(gchar *path, gboolean save)
3421 SecureSaveInfo *ssi;
3424 pathl = path_from_utf8(path);
3425 ssi = secure_open(pathl);
3429 log_printf(_("Error: Unable to write marks lists to: %s\n"), path);
3433 secure_fprintf(ssi, "#Marks lists\n");
3435 GString *marks = g_string_new("");
3438 g_hash_table_foreach(file_data_pool, marks_get_files, marks);
3440 secure_fprintf(ssi, "%s", marks->str);
3441 g_string_free(marks, TRUE);
3443 secure_fprintf(ssi, "#end\n");
3444 return (secure_close(ssi) == 0);
3447 static void marks_clear(gpointer key, gpointer value, gpointer)
3449 auto file_name = static_cast<gchar *>(key);
3454 if (isfile(file_name))
3456 fd = static_cast<FileData *>(value);
3457 if (fd && fd->marks > 0)
3463 if (fd->marks & mark_no)
3465 file_data_set_mark(fd, n , 0);
3473 void marks_clear_all()
3475 g_hash_table_foreach(file_data_pool, marks_clear, nullptr);
3478 void file_data_set_page_num(FileData *fd, gint page_num)
3480 if (fd->page_total > 1 && page_num < 0)
3482 fd->page_num = fd->page_total - 1;
3484 else if (fd->page_total > 1 && page_num <= fd->page_total)
3486 fd->page_num = page_num - 1;
3492 file_data_send_notification(fd, NOTIFY_REREAD);
3495 void file_data_inc_page_num(FileData *fd)
3497 if (fd->page_total > 0 && fd->page_num < fd->page_total - 1)
3499 fd->page_num = fd->page_num + 1;
3501 else if (fd->page_total == 0)
3503 fd->page_num = fd->page_num + 1;
3505 file_data_send_notification(fd, NOTIFY_REREAD);
3508 void file_data_dec_page_num(FileData *fd)
3510 if (fd->page_num > 0)
3512 fd->page_num = fd->page_num - 1;
3514 file_data_send_notification(fd, NOTIFY_REREAD);
3517 void file_data_set_page_total(FileData *fd, gint page_total)
3519 fd->page_total = page_total;
3522 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */