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)
71 /* what I would like to use is printf("%'d", size)
72 * BUT: not supported on every libc :(
76 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
77 a = g_strdup_printf("%d%09d", static_cast<guint>(size / 1000000000), static_cast<guint>(size % 1000000000));
81 a = g_strdup_printf("%d", static_cast<guint>(size));
87 b = g_new(gchar, l + n + 1);
112 gchar *text_from_size_abrev(gint64 size)
114 if (size < static_cast<gint64>(1024))
116 return g_strdup_printf(_("%d bytes"), static_cast<gint>(size));
118 if (size < static_cast<gint64>(1048576))
120 return g_strdup_printf(_("%.1f KiB"), static_cast<gdouble>(size) / 1024.0);
122 if (size < static_cast<gint64>(1073741824))
124 return g_strdup_printf(_("%.1f MiB"), static_cast<gdouble>(size) / 1048576.0);
127 /* to avoid overflowing the gdouble, do division in two steps */
129 return g_strdup_printf(_("%.1f GiB"), static_cast<gdouble>(size) / 1024.0);
132 /* note: returned string is valid until next call to text_from_time() */
133 const gchar *text_from_time(time_t t)
135 static gchar *ret = nullptr;
139 GError *error = nullptr;
141 btime = localtime(&t);
143 /* the %x warning about 2 digit years is not an error */
144 buflen = strftime(buf, sizeof(buf), "%x %X", btime);
145 if (buflen < 1) return "";
148 ret = g_locale_to_utf8(buf, buflen, nullptr, nullptr, &error);
151 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
160 *-----------------------------------------------------------------------------
161 * changed files detection and notification
162 *-----------------------------------------------------------------------------
165 void file_data_increment_version(FileData *fd)
171 fd->parent->version++;
172 fd->parent->valid_marks = 0;
176 static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st)
178 if (fd->size != st->st_size ||
179 fd->date != st->st_mtime)
181 fd->size = st->st_size;
182 fd->date = st->st_mtime;
183 fd->cdate = st->st_ctime;
184 fd->mode = st->st_mode;
185 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
186 fd->thumb_pixbuf = nullptr;
187 file_data_increment_version(fd);
188 file_data_send_notification(fd, NOTIFY_REREAD);
194 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
196 gboolean ret = FALSE;
199 ret = file_data_check_changed_single_file(fd, st);
201 work = fd->sidecar_files;
204 auto sfd = static_cast<FileData *>(work->data);
208 if (!stat_utf8(sfd->path, &st))
213 file_data_disconnect_sidecar_file(fd, sfd);
215 file_data_increment_version(sfd);
216 file_data_send_notification(sfd, NOTIFY_REREAD);
217 file_data_unref(sfd);
221 ret |= file_data_check_changed_files_recursive(sfd, &st);
227 gboolean file_data_check_changed_files(FileData *fd)
229 gboolean ret = FALSE;
232 if (fd->parent) fd = fd->parent;
234 if (!stat_utf8(fd->path, &st))
238 FileData *sfd = nullptr;
240 /* parent is missing, we have to rebuild whole group */
245 /* file_data_disconnect_sidecar_file might delete the file,
246 we have to keep the reference to prevent this */
247 sidecars = filelist_copy(fd->sidecar_files);
252 sfd = static_cast<FileData *>(work->data);
255 file_data_disconnect_sidecar_file(fd, sfd);
257 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
258 /* now we can release the sidecars */
259 filelist_free(sidecars);
260 file_data_increment_version(fd);
261 file_data_send_notification(fd, NOTIFY_REREAD);
266 ret |= file_data_check_changed_files_recursive(fd, &st);
273 *-----------------------------------------------------------------------------
274 * file name, extension, sorting, ...
275 *-----------------------------------------------------------------------------
278 static void file_data_set_collate_keys(FileData *fd)
280 gchar *caseless_name;
283 valid_name = g_filename_display_name(fd->name);
284 caseless_name = g_utf8_casefold(valid_name, -1);
286 g_free(fd->collate_key_name);
287 g_free(fd->collate_key_name_nocase);
289 fd->collate_key_name_natural = g_utf8_collate_key_for_filename(fd->name, -1);
290 fd->collate_key_name_nocase_natural = g_utf8_collate_key_for_filename(caseless_name, -1);
291 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
292 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
295 g_free(caseless_name);
298 static void file_data_set_path(FileData *fd, const gchar *path)
300 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
301 g_assert(file_data_pool);
305 if (fd->original_path)
307 g_hash_table_remove(file_data_pool, fd->original_path);
308 g_free(fd->original_path);
311 g_assert(!g_hash_table_lookup(file_data_pool, path));
313 fd->original_path = g_strdup(path);
314 g_hash_table_insert(file_data_pool, fd->original_path, fd);
316 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
318 fd->path = g_strdup(path);
320 fd->extension = fd->name + 1;
321 file_data_set_collate_keys(fd);
325 fd->path = g_strdup(path);
326 fd->name = filename_from_path(fd->path);
328 if (strcmp(fd->name, "..") == 0)
330 gchar *dir = remove_level_from_path(path);
332 fd->path = remove_level_from_path(dir);
335 fd->extension = fd->name + 2;
336 file_data_set_collate_keys(fd);
340 if (strcmp(fd->name, ".") == 0)
343 fd->path = remove_level_from_path(path);
345 fd->extension = fd->name + 1;
346 file_data_set_collate_keys(fd);
350 fd->extension = registered_extension_from_path(fd->path);
351 if (fd->extension == nullptr)
353 fd->extension = fd->name + strlen(fd->name);
356 fd->sidecar_priority = sidecar_file_priority(fd->extension);
357 file_data_set_collate_keys(fd);
361 *-----------------------------------------------------------------------------
362 * create or reuse Filedata
363 *-----------------------------------------------------------------------------
366 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
372 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
374 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
377 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
379 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
385 if (!fd && file_data_planned_change_hash)
387 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_planned_change_hash, path_utf8));
390 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
391 if (!isfile(fd->path))
394 file_data_apply_ci(fd);
405 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
407 #ifdef DEBUG_FILEDATA
410 file_data_check_changed_single_file(fd, st);
412 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
417 fd = g_new0(FileData, 1);
418 #ifdef DEBUG_FILEDATA
419 global_file_data_count++;
420 DEBUG_2("file data count++: %d", global_file_data_count);
423 fd->size = st->st_size;
424 fd->date = st->st_mtime;
425 fd->cdate = st->st_ctime;
426 fd->mode = st->st_mode;
428 fd->magick = FD_MAGICK;
430 fd->rating = STAR_RATING_NOT_READ;
431 fd->format_class = filter_file_get_class(path_utf8);
435 user = getpwuid(st->st_uid);
438 fd->owner = g_strdup_printf("%u", st->st_uid);
442 fd->owner = g_strdup(user->pw_name);
445 group = getgrgid(st->st_gid);
448 fd->group = g_strdup_printf("%u", st->st_gid);
452 fd->group = g_strdup(group->gr_name);
455 fd->sym_link = get_symbolic_link(path_utf8);
457 if (disable_sidecars) fd->disable_grouping = TRUE;
459 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
464 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
466 gchar *path_utf8 = path_to_utf8(path);
467 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
473 FileData *file_data_new_simple(const gchar *path_utf8)
478 if (!stat_utf8(path_utf8, &st))
484 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
485 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
494 void read_exif_time_data(FileData *file)
496 if (file->exifdate > 0)
498 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
509 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
510 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), (void *)file, file->path);
522 sscanf(tmp, "%4u:%2u:%2u %2u:%2u:%2u", &year, &month, &day, &hour, &min, &sec);
523 time_str.tm_year = year - 1900;
524 time_str.tm_mon = month - 1;
525 time_str.tm_mday = day;
526 time_str.tm_hour = hour;
527 time_str.tm_min = min;
528 time_str.tm_sec = sec;
529 time_str.tm_isdst = 0;
531 file->exifdate = mktime(&time_str);
537 void read_exif_time_digitized_data(FileData *file)
539 if (file->exifdate_digitized > 0)
541 DEBUG_1("%s set_exif_time_digitized_data: Already exists for %s", get_exec_time(), file->path);
552 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeDigitized");
553 DEBUG_2("%s set_exif_time_digitized_data: reading %p %s", get_exec_time(), (void *)file, file->path);
565 sscanf(tmp, "%4u:%2u:%2u %2u:%2u:%2u", &year, &month, &day, &hour, &min, &sec);
566 time_str.tm_year = year - 1900;
567 time_str.tm_mon = month - 1;
568 time_str.tm_mday = day;
569 time_str.tm_hour = hour;
570 time_str.tm_min = min;
571 time_str.tm_sec = sec;
572 time_str.tm_isdst = 0;
574 file->exifdate_digitized = mktime(&time_str);
580 void read_rating_data(FileData *file)
584 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
587 file->rating = atoi(rating_str);
596 #pragma GCC diagnostic push
597 #pragma GCC diagnostic ignored "-Wunused-function"
598 void set_exif_time_data_unused(GList *files)
600 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
604 auto *file = static_cast<FileData *>(files->data);
606 read_exif_time_data(file);
611 void set_exif_time_digitized_data_unused(GList *files)
613 DEBUG_1("%s set_exif_time_digitized_data: ...", get_exec_time());
617 auto *file = static_cast<FileData *>(files->data);
619 read_exif_time_digitized_data(file);
624 void set_rating_data_unused(GList *files)
627 DEBUG_1("%s set_rating_data: ...", get_exec_time());
631 auto *file = static_cast<FileData *>(files->data);
632 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
635 file->rating = atoi(rating_str);
641 #pragma GCC diagnostic pop
643 FileData *file_data_new_no_grouping(const gchar *path_utf8)
647 if (!stat_utf8(path_utf8, &st))
653 return file_data_new(path_utf8, &st, TRUE);
656 FileData *file_data_new_dir(const gchar *path_utf8)
660 if (!stat_utf8(path_utf8, &st))
666 /* dir or non-existing yet */
667 g_assert(S_ISDIR(st.st_mode));
669 return file_data_new(path_utf8, &st, TRUE);
673 *-----------------------------------------------------------------------------
675 *-----------------------------------------------------------------------------
678 #ifdef DEBUG_FILEDATA
679 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
681 FileData *file_data_ref(FileData *fd)
684 if (fd == nullptr) return nullptr;
685 if (fd->magick != FD_MAGICK)
686 #ifdef DEBUG_FILEDATA
687 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, (void *)fd);
689 log_printf("Error: fd magick mismatch fd=%p", fd);
691 g_assert(fd->magick == FD_MAGICK);
694 #ifdef DEBUG_FILEDATA
695 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->path, file, line);
697 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
703 * @brief Print ref. count and image name
706 * Print image ref. count and full path name of all images in
707 * the file_data_pool.
709 * Used only by DEBUG_FD()
711 void file_data_dump()
713 #ifdef DEBUG_FILEDATA
719 list = g_hash_table_get_values(file_data_pool);
721 log_printf("%d", global_file_data_count);
722 log_printf("%d", g_list_length(list));
727 fd = static_cast<FileData *>(work->data);
728 log_printf("%-4d %s", fd->ref, fd->path);
737 static void file_data_free(FileData *fd)
739 g_assert(fd->magick == FD_MAGICK);
740 g_assert(fd->ref == 0);
741 g_assert(!fd->locked);
743 #ifdef DEBUG_FILEDATA
744 global_file_data_count--;
745 DEBUG_2("file data count--: %d", global_file_data_count);
748 metadata_cache_free(fd);
749 g_hash_table_remove(file_data_pool, fd->original_path);
752 g_free(fd->original_path);
753 g_free(fd->collate_key_name);
754 g_free(fd->collate_key_name_nocase);
755 g_free(fd->extended_extension);
756 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
757 histmap_free(fd->histmap);
760 g_free(fd->sym_link);
761 g_free(fd->format_name);
762 g_assert(fd->sidecar_files == nullptr); /* sidecar files must be freed before calling this */
764 file_data_change_info_free(nullptr, fd);
769 * @brief Checks if the FileData is referenced
771 * Checks the refcount and whether the FileData is locked.
773 static gboolean file_data_check_has_ref(FileData *fd)
775 return fd->ref > 0 || fd->locked;
779 * @brief Consider freeing a FileData.
781 * This function will free a FileData and its children provided that neither its parent nor it has
782 * a positive refcount, and provided that neither is locked.
784 static void file_data_consider_free(FileData *fd)
787 FileData *parent = fd->parent ? fd->parent : fd;
789 g_assert(fd->magick == FD_MAGICK);
790 if (file_data_check_has_ref(fd)) return;
791 if (file_data_check_has_ref(parent)) return;
793 work = parent->sidecar_files;
796 auto sfd = static_cast<FileData *>(work->data);
797 if (file_data_check_has_ref(sfd)) return;
801 /* Neither the parent nor the siblings are referenced, so we can free everything */
802 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
803 fd->path, fd->parent ? parent->path : "-");
805 g_list_free_full(parent->sidecar_files, reinterpret_cast<GDestroyNotify>(file_data_free));
806 parent->sidecar_files = nullptr;
808 file_data_free(parent);
811 #ifdef DEBUG_FILEDATA
812 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
814 void file_data_unref(FileData *fd)
817 if (fd == nullptr) return;
818 if (fd->magick != FD_MAGICK)
819 #ifdef DEBUG_FILEDATA
820 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, (void *)fd);
822 log_printf("Error: fd magick mismatch fd=%p", fd);
824 g_assert(fd->magick == FD_MAGICK);
827 #ifdef DEBUG_FILEDATA
828 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->locked, fd->path,
831 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
834 // Free FileData if it's no longer ref'd
835 file_data_consider_free(fd);
839 * @brief Lock the FileData in memory.
841 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
842 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
843 * even if the code would continue to function properly even if the FileData were freed. Code that
844 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
846 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
847 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
849 void file_data_lock(FileData *fd)
851 if (fd == nullptr) return;
852 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
854 g_assert(fd->magick == FD_MAGICK);
857 DEBUG_2("file_data_ref fd=%p (%d): '%s'", (void *)fd, fd->ref, fd->path);
861 * @brief Reset the maintain-FileData-in-memory lock
863 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
864 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
865 * keeping it from being freed.
867 void file_data_unlock(FileData *fd)
869 if (fd == nullptr) return;
870 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
872 g_assert(fd->magick == FD_MAGICK);
875 // Free FileData if it's no longer ref'd
876 file_data_consider_free(fd);
880 * @brief Lock all of the FileDatas in the provided list
882 * @see file_data_lock(#FileData)
884 void file_data_lock_list(GList *list)
891 auto fd = static_cast<FileData *>(work->data);
898 * @brief Unlock all of the FileDatas in the provided list
900 * @see #file_data_unlock(#FileData)
902 void file_data_unlock_list(GList *list)
909 auto fd = static_cast<FileData *>(work->data);
911 file_data_unlock(fd);
916 *-----------------------------------------------------------------------------
917 * sidecar file info struct
918 *-----------------------------------------------------------------------------
921 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
923 auto fda = static_cast<const FileData *>(a);
924 auto fdb = static_cast<const FileData *>(b);
926 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
927 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
929 return strcmp(fdb->extension, fda->extension);
933 static gint sidecar_file_priority(const gchar *extension)
938 if (extension == nullptr)
941 work = sidecar_ext_get_list();
944 auto ext = static_cast<gchar *>(work->data);
947 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
953 static void file_data_check_sidecars(const GList *basename_list)
955 /* basename_list contains the new group - first is the parent, then sorted sidecars */
956 /* all files in the list have ref count > 0 */
963 if (!basename_list) return;
966 DEBUG_2("basename start");
967 work = basename_list;
970 auto fd = static_cast<FileData *>(work->data);
972 g_assert(fd->magick == FD_MAGICK);
973 DEBUG_2("basename: %p %s", (void *)fd, fd->name);
976 g_assert(fd->parent->magick == FD_MAGICK);
977 DEBUG_2(" parent: %p", (void *)fd->parent);
979 s_work = fd->sidecar_files;
982 auto sfd = static_cast<FileData *>(s_work->data);
983 s_work = s_work->next;
984 g_assert(sfd->magick == FD_MAGICK);
985 DEBUG_2(" sidecar: %p %s", (void *)sfd, sfd->name);
988 g_assert(fd->parent == nullptr || fd->sidecar_files == nullptr);
991 parent_fd = static_cast<FileData *>(basename_list->data);
993 /* check if the second and next entries of basename_list are already connected
994 as sidecars of the first entry (parent_fd) */
995 work = basename_list->next;
996 s_work = parent_fd->sidecar_files;
998 while (work && s_work)
1000 if (work->data != s_work->data) break;
1002 s_work = s_work->next;
1005 if (!work && !s_work)
1007 DEBUG_2("basename no change");
1008 return; /* no change in grouping */
1011 /* we have to regroup it */
1013 /* first, disconnect everything and send notification*/
1015 work = basename_list;
1018 auto fd = static_cast<FileData *>(work->data);
1020 g_assert(fd->parent == nullptr || fd->sidecar_files == nullptr);
1024 FileData *old_parent = fd->parent;
1025 g_assert(old_parent->parent == nullptr || old_parent->sidecar_files == nullptr);
1026 file_data_ref(old_parent);
1027 file_data_disconnect_sidecar_file(old_parent, fd);
1028 file_data_send_notification(old_parent, NOTIFY_REREAD);
1029 file_data_unref(old_parent);
1032 while (fd->sidecar_files)
1034 auto sfd = static_cast<FileData *>(fd->sidecar_files->data);
1035 g_assert(sfd->parent == nullptr || sfd->sidecar_files == nullptr);
1037 file_data_disconnect_sidecar_file(fd, sfd);
1038 file_data_send_notification(sfd, NOTIFY_REREAD);
1039 file_data_unref(sfd);
1041 file_data_send_notification(fd, NOTIFY_GROUPING);
1043 g_assert(fd->parent == nullptr && fd->sidecar_files == nullptr);
1046 /* now we can form the new group */
1047 work = basename_list->next;
1048 new_sidecars = nullptr;
1051 auto sfd = static_cast<FileData *>(work->data);
1052 g_assert(sfd->magick == FD_MAGICK);
1053 g_assert(sfd->parent == nullptr && sfd->sidecar_files == nullptr);
1054 sfd->parent = parent_fd;
1055 new_sidecars = g_list_prepend(new_sidecars, sfd);
1058 g_assert(parent_fd->sidecar_files == nullptr);
1059 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
1060 DEBUG_1("basename group changed for %s", parent_fd->path);
1064 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
1066 g_assert(target->magick == FD_MAGICK);
1067 g_assert(sfd->magick == FD_MAGICK);
1068 g_assert(g_list_find(target->sidecar_files, sfd));
1070 file_data_ref(target);
1073 g_assert(sfd->parent == target);
1075 file_data_increment_version(sfd); /* increments both sfd and target */
1077 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
1078 sfd->parent = nullptr;
1079 g_free(sfd->extended_extension);
1080 sfd->extended_extension = nullptr;
1082 file_data_unref(target);
1083 file_data_unref(sfd);
1086 /* disables / enables grouping for particular file, sends UPDATE notification */
1087 void file_data_disable_grouping(FileData *fd, gboolean disable)
1089 if (!fd->disable_grouping == !disable) return;
1091 fd->disable_grouping = !!disable;
1097 FileData *parent = file_data_ref(fd->parent);
1098 file_data_disconnect_sidecar_file(parent, fd);
1099 file_data_send_notification(parent, NOTIFY_GROUPING);
1100 file_data_unref(parent);
1102 else if (fd->sidecar_files)
1104 GList *sidecar_files = filelist_copy(fd->sidecar_files);
1105 GList *work = sidecar_files;
1108 auto sfd = static_cast<FileData *>(work->data);
1110 file_data_disconnect_sidecar_file(fd, sfd);
1111 file_data_send_notification(sfd, NOTIFY_GROUPING);
1113 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
1114 filelist_free(sidecar_files);
1118 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
1123 file_data_increment_version(fd);
1124 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
1126 file_data_send_notification(fd, NOTIFY_GROUPING);
1129 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
1136 auto fd = static_cast<FileData *>(work->data);
1138 file_data_disable_grouping(fd, disable);
1146 *-----------------------------------------------------------------------------
1148 *-----------------------------------------------------------------------------
1152 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
1155 if (!filelist_sort_ascend)
1162 switch (filelist_sort_method)
1167 if (fa->size < fb->size) return -1;
1168 if (fa->size > fb->size) return 1;
1169 /* fall back to name */
1172 if (fa->date < fb->date) return -1;
1173 if (fa->date > fb->date) return 1;
1174 /* fall back to name */
1177 if (fa->cdate < fb->cdate) return -1;
1178 if (fa->cdate > fb->cdate) return 1;
1179 /* fall back to name */
1182 if (fa->exifdate < fb->exifdate) return -1;
1183 if (fa->exifdate > fb->exifdate) return 1;
1184 /* fall back to name */
1186 case SORT_EXIFTIMEDIGITIZED:
1187 if (fa->exifdate_digitized < fb->exifdate_digitized) return -1;
1188 if (fa->exifdate_digitized > fb->exifdate_digitized) return 1;
1189 /* fall back to name */
1192 if (fa->rating < fb->rating) return -1;
1193 if (fa->rating > fb->rating) return 1;
1194 /* fall back to name */
1197 if (fa->format_class < fb->format_class) return -1;
1198 if (fa->format_class > fb->format_class) return 1;
1199 /* fall back to name */
1202 ret = strcmp(fa->collate_key_name_natural, fb->collate_key_name_natural);
1203 if (ret != 0) return ret;
1204 /* fall back to name */
1210 if (filelist_sort_case)
1211 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1213 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1215 if (ret != 0) return ret;
1217 /* do not return 0 unless the files are really the same
1218 file_data_pool ensures that original_path is unique
1220 return strcmp(fa->original_path, fb->original_path);
1223 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1225 filelist_sort_method = method;
1226 filelist_sort_ascend = ascend;
1227 return filelist_sort_compare_filedata(fa, fb);
1230 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1232 return filelist_sort_compare_filedata(static_cast<FileData *>(a), static_cast<FileData *>(b));
1235 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, gboolean case_sensitive, GCompareFunc cb)
1237 filelist_sort_method = method;
1238 filelist_sort_ascend = ascend;
1239 filelist_sort_case = case_sensitive;
1240 return g_list_sort(list, cb);
1243 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, gboolean case_sensitive, GCompareFunc cb)
1245 filelist_sort_method = method;
1246 filelist_sort_ascend = ascend;
1247 filelist_sort_case = case_sensitive;
1248 return g_list_insert_sorted(list, data, cb);
1251 GList *filelist_sort(GList *list, SortType method, gboolean ascend, gboolean case_sensitive)
1253 return filelist_sort_full(list, method, ascend, case_sensitive, reinterpret_cast<GCompareFunc>(filelist_sort_file_cb));
1256 #pragma GCC diagnostic push
1257 #pragma GCC diagnostic ignored "-Wunused-function"
1258 GList *filelist_insert_sort_unused(GList *list, FileData *fd, SortType method, gboolean ascend)
1260 return filelist_insert_sort_full(list, fd, method, ascend, ascend, (GCompareFunc) filelist_sort_file_cb);
1262 #pragma GCC diagnostic pop
1265 *-----------------------------------------------------------------------------
1266 * basename hash - grouping of sidecars in filelist
1267 *-----------------------------------------------------------------------------
1271 static GHashTable *file_data_basename_hash_new()
1273 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, nullptr);
1276 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1279 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1281 list = static_cast<GList *>(g_hash_table_lookup(basename_hash, basename));
1285 DEBUG_1("TG: basename_hash not found for %s",fd->path);
1286 const gchar *parent_extension = registered_extension_from_path(basename);
1288 if (parent_extension)
1290 DEBUG_1("TG: parent extension %s",parent_extension);
1291 gchar *parent_basename = g_strndup(basename, parent_extension - basename);
1292 DEBUG_1("TG: parent basename %s",parent_basename);
1293 auto parent_fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, basename));
1296 DEBUG_1("TG: parent fd found");
1297 list = static_cast<GList *>(g_hash_table_lookup(basename_hash, parent_basename));
1298 if (!g_list_find(list, parent_fd))
1300 DEBUG_1("TG: parent fd doesn't fit");
1301 g_free(parent_basename);
1307 basename = parent_basename;
1308 fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
1314 if (!g_list_find(list, fd))
1316 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1317 g_hash_table_insert(basename_hash, basename, list);
1326 static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
1328 file_data_basename_hash_insert(static_cast<GHashTable *>(basename_hash), static_cast<FileData *>(fd));
1331 static void file_data_basename_hash_remove_list(gpointer, gpointer value, gpointer)
1333 filelist_free(static_cast<GList *>(value));
1336 static void file_data_basename_hash_free(GHashTable *basename_hash)
1338 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, nullptr);
1339 g_hash_table_destroy(basename_hash);
1343 *-----------------------------------------------------------------------------
1344 * handling sidecars in filelist
1345 *-----------------------------------------------------------------------------
1348 static GList *filelist_filter_out_sidecars(GList *flist)
1350 GList *work = flist;
1351 GList *flist_filtered = nullptr;
1355 auto fd = static_cast<FileData *>(work->data);
1358 if (fd->parent) /* remove fd's that are children */
1359 file_data_unref(fd);
1361 flist_filtered = g_list_prepend(flist_filtered, fd);
1365 return flist_filtered;
1368 static void file_data_basename_hash_to_sidecars(gpointer, gpointer value, gpointer)
1370 auto basename_list = static_cast<GList *>(value);
1371 file_data_check_sidecars(basename_list);
1375 static gboolean is_hidden_file(const gchar *name)
1377 if (name[0] != '.') return FALSE;
1378 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1383 *-----------------------------------------------------------------------------
1384 * the main filelist function
1385 *-----------------------------------------------------------------------------
1388 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1393 GList *dlist = nullptr;
1394 GList *flist = nullptr;
1395 GList *xmp_files = nullptr;
1396 gint (*stat_func)(const gchar *path, struct stat *buf);
1397 GHashTable *basename_hash = nullptr;
1399 g_assert(files || dirs);
1401 if (files) *files = nullptr;
1402 if (dirs) *dirs = nullptr;
1404 pathl = path_from_utf8(dir_path);
1405 if (!pathl) return FALSE;
1407 dp = opendir(pathl);
1414 if (files) basename_hash = file_data_basename_hash_new();
1416 if (follow_symlinks)
1421 while ((dir = readdir(dp)) != nullptr)
1423 struct stat ent_sbuf;
1424 const gchar *name = dir->d_name;
1427 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1430 filepath = g_build_filename(pathl, name, NULL);
1431 if (stat_func(filepath, &ent_sbuf) >= 0)
1433 if (S_ISDIR(ent_sbuf.st_mode))
1435 /* we ignore the .thumbnails dir for cleanliness */
1437 (name[0] != '.' || (name[1] != '\0' && (name[1] != '.' || name[2] != '\0'))) &&
1438 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1439 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1440 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1442 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1447 if (files && filter_name_exists(name))
1449 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1450 flist = g_list_prepend(flist, fd);
1451 if (fd->sidecar_priority && !fd->disable_grouping)
1453 if (strcmp(fd->extension, ".xmp") != 0)
1454 file_data_basename_hash_insert(basename_hash, fd);
1456 xmp_files = g_list_append(xmp_files, fd);
1463 if (errno == EOVERFLOW)
1465 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1477 g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
1478 g_list_free(xmp_files);
1481 if (dirs) *dirs = dlist;
1485 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, nullptr);
1487 *files = filelist_filter_out_sidecars(flist);
1489 if (basename_hash) file_data_basename_hash_free(basename_hash);
1494 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1496 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1499 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1501 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1504 FileData *file_data_new_group(const gchar *path_utf8)
1511 if (!file_data_pool)
1513 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
1516 if (!stat_utf8(path_utf8, &st))
1522 if (S_ISDIR(st.st_mode))
1523 return file_data_new(path_utf8, &st, TRUE);
1525 dir = remove_level_from_path(path_utf8);
1527 filelist_read_real(dir, &files, nullptr, TRUE);
1529 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
1530 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1536 filelist_free(files);
1542 void filelist_free(GList *list)
1549 file_data_unref((FileData *)work->data);
1557 GList *filelist_copy(GList *list)
1559 GList *new_list = nullptr;
1561 for (GList *work = list; work; work = work->next)
1563 auto fd = static_cast<FileData *>(work->data);
1565 new_list = g_list_prepend(new_list, file_data_ref(fd));
1568 return g_list_reverse(new_list);
1571 GList *filelist_from_path_list(GList *list)
1573 GList *new_list = nullptr;
1581 path = static_cast<gchar *>(work->data);
1584 new_list = g_list_prepend(new_list, file_data_new_group(path));
1587 return g_list_reverse(new_list);
1590 GList *filelist_to_path_list(GList *list)
1592 GList *new_list = nullptr;
1600 fd = static_cast<FileData *>(work->data);
1603 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1606 return g_list_reverse(new_list);
1609 GList *filelist_filter(GList *list, gboolean is_dir_list)
1613 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1618 auto fd = static_cast<FileData *>(work->data);
1619 const gchar *name = fd->name;
1623 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1624 (!is_dir_list && !filter_name_exists(name)) ||
1625 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1626 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1628 list = g_list_remove_link(list, link);
1629 file_data_unref(fd);
1638 *-----------------------------------------------------------------------------
1639 * filelist recursive
1640 *-----------------------------------------------------------------------------
1643 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1645 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1648 GList *filelist_sort_path(GList *list)
1650 return g_list_sort(list, filelist_sort_path_cb);
1653 static void filelist_recursive_append(GList **list, GList *dirs)
1660 auto fd = static_cast<FileData *>(work->data);
1664 if (filelist_read(fd, &f, &d))
1666 f = filelist_filter(f, FALSE);
1667 f = filelist_sort_path(f);
1668 *list = g_list_concat(*list, f);
1670 d = filelist_filter(d, TRUE);
1671 d = filelist_sort_path(d);
1672 filelist_recursive_append(list, d);
1680 static void filelist_recursive_append_full(GList **list, GList *dirs, SortType method, gboolean ascend, gboolean case_sensitive)
1687 auto fd = static_cast<FileData *>(work->data);
1691 if (filelist_read(fd, &f, &d))
1693 f = filelist_filter(f, FALSE);
1694 f = filelist_sort_full(f, method, ascend, case_sensitive, reinterpret_cast<GCompareFunc>(filelist_sort_file_cb));
1695 *list = g_list_concat(*list, f);
1697 d = filelist_filter(d, TRUE);
1698 d = filelist_sort_path(d);
1699 filelist_recursive_append_full(list, d, method, ascend, case_sensitive);
1707 GList *filelist_recursive(FileData *dir_fd)
1712 if (!filelist_read(dir_fd, &list, &d)) return nullptr;
1713 list = filelist_filter(list, FALSE);
1714 list = filelist_sort_path(list);
1716 d = filelist_filter(d, TRUE);
1717 d = filelist_sort_path(d);
1718 filelist_recursive_append(&list, d);
1724 GList *filelist_recursive_full(FileData *dir_fd, SortType method, gboolean ascend, gboolean case_sensitive)
1729 if (!filelist_read(dir_fd, &list, &d)) return nullptr;
1730 list = filelist_filter(list, FALSE);
1731 list = filelist_sort_full(list, method, ascend, case_sensitive, reinterpret_cast<GCompareFunc>(filelist_sort_file_cb));
1733 d = filelist_filter(d, TRUE);
1734 d = filelist_sort_path(d);
1735 filelist_recursive_append_full(&list, d, method, ascend, case_sensitive);
1742 *-----------------------------------------------------------------------------
1743 * file modification support
1744 *-----------------------------------------------------------------------------
1748 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1750 if (!fdci && fd) fdci = fd->change;
1754 g_free(fdci->source);
1759 if (fd) fd->change = nullptr;
1762 static gboolean file_data_can_write_directly(FileData *fd)
1764 return filter_name_is_writable(fd->extension);
1767 static gboolean file_data_can_write_sidecar(FileData *fd)
1769 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1772 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1774 gchar *sidecar_path = nullptr;
1777 if (!file_data_can_write_sidecar(fd)) return nullptr;
1779 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1780 gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1783 auto sfd = static_cast<FileData *>(work->data);
1785 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1787 sidecar_path = g_strdup(sfd->path);
1791 g_free(extended_extension);
1793 if (!existing_only && !sidecar_path)
1795 if (options->metadata.sidecar_extended_name)
1796 sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1799 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1800 sidecar_path = g_strconcat(base, ".xmp", NULL);
1805 return sidecar_path;
1809 * marks and orientation
1812 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1813 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1814 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1815 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1817 gboolean file_data_get_mark(FileData *fd, gint n)
1819 gboolean valid = (fd->valid_marks & (1 << n));
1821 if (file_data_get_mark_func[n] && !valid)
1823 guint old = fd->marks;
1824 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1826 if (!value != !(fd->marks & (1 << n)))
1828 fd->marks = fd->marks ^ (1 << n);
1831 fd->valid_marks |= (1 << n);
1832 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1834 file_data_unref(fd);
1836 else if (!old && fd->marks)
1842 return !!(fd->marks & (1 << n));
1845 guint file_data_get_marks(FileData *fd)
1848 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1852 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1855 if (!value == !file_data_get_mark(fd, n)) return;
1857 if (file_data_set_mark_func[n])
1859 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1864 fd->marks = fd->marks ^ (1 << n);
1866 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1868 file_data_unref(fd);
1870 else if (!old && fd->marks)
1875 file_data_increment_version(fd);
1876 file_data_send_notification(fd, NOTIFY_MARKS);
1879 gboolean file_data_filter_marks(FileData *fd, guint filter)
1882 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1883 return ((fd->marks & filter) == filter);
1886 GList *file_data_filter_marks_list(GList *list, guint filter)
1893 auto fd = static_cast<FileData *>(work->data);
1897 if (!file_data_filter_marks(fd, filter))
1899 list = g_list_remove_link(list, link);
1900 file_data_unref(fd);
1908 gboolean file_data_filter_file_filter(FileData *fd, GRegex *filter)
1910 return g_regex_match(filter, fd->name, static_cast<GRegexMatchFlags>(0), nullptr);
1913 GList *file_data_filter_file_filter_list(GList *list, GRegex *filter)
1920 auto fd = static_cast<FileData *>(work->data);
1924 if (!file_data_filter_file_filter(fd, filter))
1926 list = g_list_remove_link(list, link);
1927 file_data_unref(fd);
1935 static gboolean file_data_filter_class(FileData *fd, guint filter)
1939 for (i = 0; i < FILE_FORMAT_CLASSES; i++)
1941 if (filter & (1 << i))
1943 if (static_cast<FileFormatClass>(i) == filter_file_get_class(fd->path))
1953 GList *file_data_filter_class_list(GList *list, guint filter)
1960 auto fd = static_cast<FileData *>(work->data);
1964 if (!file_data_filter_class(fd, filter))
1966 list = g_list_remove_link(list, link);
1967 file_data_unref(fd);
1975 static void file_data_notify_mark_func(gpointer, gpointer value, gpointer)
1977 auto fd = static_cast<FileData *>(value);
1978 file_data_increment_version(fd);
1979 file_data_send_notification(fd, NOTIFY_MARKS);
1982 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1984 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1986 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1988 file_data_get_mark_func[n] = get_mark_func;
1989 file_data_set_mark_func[n] = set_mark_func;
1990 file_data_mark_func_data[n] = data;
1991 file_data_destroy_mark_func[n] = notify;
1993 if (get_mark_func && file_data_pool)
1995 /* this effectively changes all known files */
1996 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, nullptr);
2002 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
2004 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
2005 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
2006 if (data) *data = file_data_mark_func_data[n];
2009 #pragma GCC diagnostic push
2010 #pragma GCC diagnostic ignored "-Wunused-function"
2011 gint file_data_get_user_orientation_unused(FileData *fd)
2013 return fd->user_orientation;
2016 void file_data_set_user_orientation_unused(FileData *fd, gint value)
2018 if (fd->user_orientation == value) return;
2020 fd->user_orientation = value;
2021 file_data_increment_version(fd);
2022 file_data_send_notification(fd, NOTIFY_ORIENTATION);
2024 #pragma GCC diagnostic pop
2028 * file_data - operates on the given fd
2029 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
2033 /* return list of sidecar file extensions in a string */
2034 gchar *file_data_sc_list_to_string(FileData *fd)
2037 GString *result = g_string_new("");
2039 work = fd->sidecar_files;
2042 auto sfd = static_cast<FileData *>(work->data);
2044 result = g_string_append(result, "+ ");
2045 result = g_string_append(result, sfd->extension);
2047 if (work) result = g_string_append_c(result, ' ');
2050 return g_string_free(result, FALSE);
2056 * add FileDataChangeInfo (see typedefs.h) for the given operation
2057 * uses file_data_add_change_info
2059 * fails if the fd->change already exists - change operations can't run in parallel
2060 * fd->change_info works as a lock
2062 * dest can be NULL - in this case the current name is used for now, it will
2067 FileDataChangeInfo types:
2069 MOVE - path is changed, name may be changed too
2070 RENAME - path remains unchanged, name is changed
2071 extension should remain (FIXME should we allow editing extension? it will make problems with grouping)
2072 sidecar names are changed too, extensions are not changed
2074 UPDATE - file size, date or grouping has been changed
2077 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
2079 FileDataChangeInfo *fdci;
2081 if (fd->change) return FALSE;
2083 fdci = g_new0(FileDataChangeInfo, 1);
2088 fdci->source = g_strdup(src);
2090 fdci->source = g_strdup(fd->path);
2093 fdci->dest = g_strdup(dest);
2100 static void file_data_planned_change_remove(FileData *fd)
2102 if (file_data_planned_change_hash &&
2103 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
2105 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
2107 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
2108 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
2109 file_data_unref(fd);
2110 if (g_hash_table_size(file_data_planned_change_hash) == 0)
2112 g_hash_table_destroy(file_data_planned_change_hash);
2113 file_data_planned_change_hash = nullptr;
2114 DEBUG_1("planned change: empty");
2121 void file_data_free_ci(FileData *fd)
2123 FileDataChangeInfo *fdci = fd->change;
2127 file_data_planned_change_remove(fd);
2129 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
2131 g_free(fdci->source);
2136 fd->change = nullptr;
2139 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
2141 FileDataChangeInfo *fdci = fd->change;
2143 fdci->regroup_when_finished = enable;
2146 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
2150 if (fd->parent) fd = fd->parent;
2152 if (fd->change) return FALSE;
2154 work = fd->sidecar_files;
2157 auto sfd = static_cast<FileData *>(work->data);
2159 if (sfd->change) return FALSE;
2163 file_data_add_ci(fd, type, nullptr, nullptr);
2165 work = fd->sidecar_files;
2168 auto sfd = static_cast<FileData *>(work->data);
2170 file_data_add_ci(sfd, type, nullptr, nullptr);
2177 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
2181 if (fd->parent) fd = fd->parent;
2183 if (!fd->change || fd->change->type != type) return FALSE;
2185 work = fd->sidecar_files;
2188 auto sfd = static_cast<FileData *>(work->data);
2190 if (!sfd->change || sfd->change->type != type) return FALSE;
2198 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
2200 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
2201 file_data_sc_update_ci_copy(fd, dest_path);
2205 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
2207 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
2208 file_data_sc_update_ci_move(fd, dest_path);
2212 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
2214 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
2215 file_data_sc_update_ci_rename(fd, dest_path);
2219 gboolean file_data_sc_add_ci_delete(FileData *fd)
2221 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
2224 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
2226 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
2227 file_data_sc_update_ci_unspecified(fd, dest_path);
2231 gboolean file_data_add_ci_write_metadata(FileData *fd)
2233 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, nullptr, nullptr);
2236 void file_data_sc_free_ci(FileData *fd)
2240 if (fd->parent) fd = fd->parent;
2242 file_data_free_ci(fd);
2244 work = fd->sidecar_files;
2247 auto sfd = static_cast<FileData *>(work->data);
2249 file_data_free_ci(sfd);
2254 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2257 gboolean ret = TRUE;
2262 auto fd = static_cast<FileData *>(work->data);
2264 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2271 static void file_data_sc_revert_ci_list(GList *fd_list)
2278 auto fd = static_cast<FileData *>(work->data);
2280 file_data_sc_free_ci(fd);
2285 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2292 auto fd = static_cast<FileData *>(work->data);
2294 if (!func(fd, dest))
2296 file_data_sc_revert_ci_list(work->prev);
2305 gboolean file_data_sc_add_ci_copy_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_copy);
2310 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2312 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2315 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2317 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2320 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2322 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2325 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2328 gboolean ret = TRUE;
2333 auto fd = static_cast<FileData *>(work->data);
2335 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2342 void file_data_free_ci_list(GList *fd_list)
2349 auto fd = static_cast<FileData *>(work->data);
2351 file_data_free_ci(fd);
2356 void file_data_sc_free_ci_list(GList *fd_list)
2363 auto fd = static_cast<FileData *>(work->data);
2365 file_data_sc_free_ci(fd);
2371 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2372 * fails if fd->change does not exist or the change type does not match
2375 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2377 FileDataChangeType type = fd->change->type;
2379 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2383 if (!file_data_planned_change_hash)
2384 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2386 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2388 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2389 g_hash_table_remove(file_data_planned_change_hash, old_path);
2390 file_data_unref(fd);
2393 ofd = static_cast<FileData *>(g_hash_table_lookup(file_data_planned_change_hash, new_path));
2398 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2399 g_hash_table_remove(file_data_planned_change_hash, new_path);
2400 file_data_unref(ofd);
2403 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2405 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2410 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2412 gchar *old_path = fd->change->dest;
2414 fd->change->dest = g_strdup(dest_path);
2415 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2419 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2421 const gchar *extension = registered_extension_from_path(fd->change->source);
2422 gchar *base = remove_extension_from_path(dest_path);
2423 gchar *old_path = fd->change->dest;
2425 fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2426 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2432 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2435 gchar *dest_path_full = nullptr;
2437 if (fd->parent) fd = fd->parent;
2441 dest_path = fd->path;
2443 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2445 gchar *dir = remove_level_from_path(fd->path);
2447 dest_path_full = g_build_filename(dir, dest_path, NULL);
2449 dest_path = dest_path_full;
2451 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2453 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2454 dest_path = dest_path_full;
2457 file_data_update_ci_dest(fd, dest_path);
2459 work = fd->sidecar_files;
2462 auto sfd = static_cast<FileData *>(work->data);
2464 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2468 g_free(dest_path_full);
2471 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2473 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2474 file_data_sc_update_ci(fd, dest_path);
2478 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2480 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2483 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2485 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2488 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2490 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2493 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2495 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2498 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2500 gboolean (*func)(FileData *, const gchar *))
2503 gboolean ret = TRUE;
2508 auto fd = static_cast<FileData *>(work->data);
2510 if (!func(fd, dest)) ret = FALSE;
2517 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2519 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2522 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2524 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2527 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2529 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2534 * verify source and dest paths - dest image exists, etc.
2535 * it should detect all possible problems with the planned operation
2538 gint file_data_verify_ci(FileData *fd, GList *list)
2540 gint ret = CHANGE_OK;
2542 GList *work = nullptr;
2543 FileData *fd1 = nullptr;
2547 DEBUG_1("Change checked: no change info: %s", fd->path);
2551 if (!isname(fd->path))
2553 /* this probably should not happen */
2554 ret |= CHANGE_NO_SRC;
2555 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2559 dir = remove_level_from_path(fd->path);
2561 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2562 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2563 fd->change->type != FILEDATA_CHANGE_RENAME &&
2564 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2567 ret |= CHANGE_WARN_UNSAVED_META;
2568 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2571 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2572 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2573 !access_file(fd->path, R_OK))
2575 ret |= CHANGE_NO_READ_PERM;
2576 DEBUG_1("Change checked: no read permission: %s", fd->path);
2578 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2579 !access_file(dir, W_OK))
2581 ret |= CHANGE_NO_WRITE_PERM_DIR;
2582 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2584 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2585 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2586 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2587 !access_file(fd->path, W_OK))
2589 ret |= CHANGE_WARN_NO_WRITE_PERM;
2590 DEBUG_1("Change checked: no write permission: %s", fd->path);
2592 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2593 - that means that there are no hard errors and warnings can be disabled
2594 - the destination is determined during the check
2596 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2598 /* determine destination file */
2599 gboolean have_dest = FALSE;
2600 gchar *dest_dir = nullptr;
2602 if (options->metadata.save_in_image_file)
2604 if (file_data_can_write_directly(fd))
2606 /* we can write the file directly */
2607 if (access_file(fd->path, W_OK))
2613 if (options->metadata.warn_on_write_problems)
2615 ret |= CHANGE_WARN_NO_WRITE_PERM;
2616 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2620 else if (file_data_can_write_sidecar(fd))
2622 /* we can write sidecar */
2623 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2624 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2626 file_data_update_ci_dest(fd, sidecar);
2631 if (options->metadata.warn_on_write_problems)
2633 ret |= CHANGE_WARN_NO_WRITE_PERM;
2634 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2643 /* write private metadata file under ~/.geeqie */
2645 /* If an existing metadata file exists, we will try writing to
2646 * it's location regardless of the user's preference.
2648 gchar *metadata_path = nullptr;
2650 /* but ignore XMP if we are not able to write it */
2651 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2653 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2655 if (metadata_path && !access_file(metadata_path, W_OK))
2657 g_free(metadata_path);
2658 metadata_path = nullptr;
2665 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2666 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2668 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2670 metadata_path = g_build_filename(dest_dir, filename, NULL);
2674 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2676 file_data_update_ci_dest(fd, metadata_path);
2680 ret |= CHANGE_NO_WRITE_PERM_DEST;
2681 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2683 g_free(metadata_path);
2688 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2693 same = (strcmp(fd->path, fd->change->dest) == 0);
2697 const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2698 if (!dest_ext) dest_ext = "";
2699 if (!options->file_filter.disable_file_extension_checks)
2701 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2703 ret |= CHANGE_WARN_CHANGED_EXT;
2704 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2710 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /** @FIXME this is now needed for running editors */
2712 ret |= CHANGE_WARN_SAME;
2713 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2717 dest_dir = remove_level_from_path(fd->change->dest);
2719 if (!isdir(dest_dir))
2721 ret |= CHANGE_NO_DEST_DIR;
2722 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2724 else if (!access_file(dest_dir, W_OK))
2726 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2727 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2731 if (isfile(fd->change->dest))
2733 if (!access_file(fd->change->dest, W_OK))
2735 ret |= CHANGE_NO_WRITE_PERM_DEST;
2736 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2740 ret |= CHANGE_WARN_DEST_EXISTS;
2741 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2744 else if (isdir(fd->change->dest))
2746 ret |= CHANGE_DEST_EXISTS;
2747 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2754 /* During a rename operation, check if another planned destination file has
2757 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2758 fd->change->type == FILEDATA_CHANGE_COPY ||
2759 fd->change->type == FILEDATA_CHANGE_MOVE)
2764 fd1 = static_cast<FileData *>(work->data);
2766 if (fd1 != nullptr && fd != fd1 )
2768 if (!strcmp(fd->change->dest, fd1->change->dest))
2770 ret |= CHANGE_DUPLICATE_DEST;
2776 fd->change->error = ret;
2777 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2784 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2789 ret = file_data_verify_ci(fd, list);
2791 work = fd->sidecar_files;
2794 auto sfd = static_cast<FileData *>(work->data);
2796 ret |= file_data_verify_ci(sfd, list);
2803 gchar *file_data_get_error_string(gint error)
2805 GString *result = g_string_new("");
2807 if (error & CHANGE_NO_SRC)
2809 if (result->len > 0) g_string_append(result, ", ");
2810 g_string_append(result, _("file or directory does not exist"));
2813 if (error & CHANGE_DEST_EXISTS)
2815 if (result->len > 0) g_string_append(result, ", ");
2816 g_string_append(result, _("destination already exists"));
2819 if (error & CHANGE_NO_WRITE_PERM_DEST)
2821 if (result->len > 0) g_string_append(result, ", ");
2822 g_string_append(result, _("destination can't be overwritten"));
2825 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2827 if (result->len > 0) g_string_append(result, ", ");
2828 g_string_append(result, _("destination directory is not writable"));
2831 if (error & CHANGE_NO_DEST_DIR)
2833 if (result->len > 0) g_string_append(result, ", ");
2834 g_string_append(result, _("destination directory does not exist"));
2837 if (error & CHANGE_NO_WRITE_PERM_DIR)
2839 if (result->len > 0) g_string_append(result, ", ");
2840 g_string_append(result, _("source directory is not writable"));
2843 if (error & CHANGE_NO_READ_PERM)
2845 if (result->len > 0) g_string_append(result, ", ");
2846 g_string_append(result, _("no read permission"));
2849 if (error & CHANGE_WARN_NO_WRITE_PERM)
2851 if (result->len > 0) g_string_append(result, ", ");
2852 g_string_append(result, _("file is readonly"));
2855 if (error & CHANGE_WARN_DEST_EXISTS)
2857 if (result->len > 0) g_string_append(result, ", ");
2858 g_string_append(result, _("destination already exists and will be overwritten"));
2861 if (error & CHANGE_WARN_SAME)
2863 if (result->len > 0) g_string_append(result, ", ");
2864 g_string_append(result, _("source and destination are the same"));
2867 if (error & CHANGE_WARN_CHANGED_EXT)
2869 if (result->len > 0) g_string_append(result, ", ");
2870 g_string_append(result, _("source and destination have different extension"));
2873 if (error & CHANGE_WARN_UNSAVED_META)
2875 if (result->len > 0) g_string_append(result, ", ");
2876 g_string_append(result, _("there are unsaved metadata changes for the file"));
2879 if (error & CHANGE_DUPLICATE_DEST)
2881 if (result->len > 0) g_string_append(result, ", ");
2882 g_string_append(result, _("another destination file has the same filename"));
2885 return g_string_free(result, FALSE);
2888 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2891 gint all_errors = 0;
2892 gint common_errors = ~0;
2897 if (!list) return 0;
2899 num = g_list_length(list);
2900 errors = g_new(int, num);
2908 fd = static_cast<FileData *>(work->data);
2911 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2912 all_errors |= error;
2913 common_errors &= error;
2920 if (desc && all_errors)
2923 GString *result = g_string_new("");
2927 gchar *str = file_data_get_error_string(common_errors);
2928 g_string_append(result, str);
2929 g_string_append(result, "\n");
2940 fd = static_cast<FileData *>(work->data);
2943 error = errors[i] & ~common_errors;
2947 gchar *str = file_data_get_error_string(error);
2948 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2953 *desc = g_string_free(result, FALSE);
2962 * perform the change described by FileFataChangeInfo
2963 * it is used for internal operations,
2964 * this function actually operates with files on the filesystem
2965 * it should implement safe delete
2968 static gboolean file_data_perform_move(FileData *fd)
2970 g_assert(!strcmp(fd->change->source, fd->path));
2971 return move_file(fd->change->source, fd->change->dest);
2974 static gboolean file_data_perform_copy(FileData *fd)
2976 g_assert(!strcmp(fd->change->source, fd->path));
2977 return copy_file(fd->change->source, fd->change->dest);
2980 static gboolean file_data_perform_delete(FileData *fd)
2982 if (isdir(fd->path) && !islink(fd->path))
2983 return rmdir_utf8(fd->path);
2985 if (options->file_ops.safe_delete_enable)
2986 return file_util_safe_unlink(fd->path);
2988 return unlink_file(fd->path);
2991 gboolean file_data_perform_ci(FileData *fd)
2993 /** @FIXME When a directory that is a symbolic link is deleted,
2994 * at this point fd->change is null because no FileDataChangeInfo
2995 * has been set up. Therefore there is a seg. fault.
2996 * This code simply aborts the delete.
3003 FileDataChangeType type = fd->change->type;
3007 case FILEDATA_CHANGE_MOVE:
3008 return file_data_perform_move(fd);
3009 case FILEDATA_CHANGE_COPY:
3010 return file_data_perform_copy(fd);
3011 case FILEDATA_CHANGE_RENAME:
3012 return file_data_perform_move(fd); /* the same as move */
3013 case FILEDATA_CHANGE_DELETE:
3014 return file_data_perform_delete(fd);
3015 case FILEDATA_CHANGE_WRITE_METADATA:
3016 return metadata_write_perform(fd);
3017 case FILEDATA_CHANGE_UNSPECIFIED:
3018 /* nothing to do here */
3026 gboolean file_data_sc_perform_ci(FileData *fd)
3029 gboolean ret = TRUE;
3030 FileDataChangeType type = fd->change->type;
3032 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3034 work = fd->sidecar_files;
3037 auto sfd = static_cast<FileData *>(work->data);
3039 if (!file_data_perform_ci(sfd)) ret = FALSE;
3043 if (!file_data_perform_ci(fd)) ret = FALSE;
3049 * updates FileData structure according to FileDataChangeInfo
3052 gboolean file_data_apply_ci(FileData *fd)
3054 FileDataChangeType type = fd->change->type;
3056 /** @FIXME delete ?*/
3057 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
3059 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
3060 file_data_planned_change_remove(fd);
3062 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
3064 /* this change overwrites another file which is already known to other modules
3065 renaming fd would create duplicate FileData structure
3066 the best thing we can do is nothing
3068 /** @FIXME maybe we could copy stuff like marks
3070 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
3074 file_data_set_path(fd, fd->change->dest);
3077 file_data_increment_version(fd);
3078 file_data_send_notification(fd, NOTIFY_CHANGE);
3083 gboolean file_data_sc_apply_ci(FileData *fd)
3086 FileDataChangeType type = fd->change->type;
3088 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3090 work = fd->sidecar_files;
3093 auto sfd = static_cast<FileData *>(work->data);
3095 file_data_apply_ci(sfd);
3099 file_data_apply_ci(fd);
3104 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
3107 if (fd->parent) fd = fd->parent;
3108 if (!g_list_find(list, fd)) return FALSE;
3110 work = fd->sidecar_files;
3113 if (!g_list_find(list, work->data)) return FALSE;
3119 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
3121 GList *out = nullptr;
3124 /* change partial groups to independent files */
3129 auto fd = static_cast<FileData *>(work->data);
3132 if (!file_data_list_contains_whole_group(list, fd))
3134 file_data_disable_grouping(fd, TRUE);
3137 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
3143 /* remove sidecars from the list,
3144 they can be still accessed via main_fd->sidecar_files */
3148 auto fd = static_cast<FileData *>(work->data);
3152 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
3154 out = g_list_prepend(out, file_data_ref(fd));
3158 filelist_free(list);
3159 out = g_list_reverse(out);
3169 * notify other modules about the change described by FileDataChangeInfo
3172 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks */
3173 /** @FIXME do we need the ignore_list? It looks like a workaround for ineffective
3174 implementation in view-file-list.cc */
3177 struct NotifyIdleData {
3184 FileDataNotifyFunc func;
3186 NotifyPriority priority;
3189 static GList *notify_func_list = nullptr;
3191 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
3193 auto nda = static_cast<const NotifyData *>(a);
3194 auto ndb = static_cast<const NotifyData *>(b);
3196 if (nda->priority < ndb->priority) return -1;
3197 if (nda->priority > ndb->priority) return 1;
3201 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
3204 GList *work = notify_func_list;
3208 auto nd = static_cast<NotifyData *>(work->data);
3210 if (nd->func == func && nd->data == data)
3212 g_warning("Notify func already registered");
3218 nd = g_new(NotifyData, 1);
3221 nd->priority = priority;
3223 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
3224 DEBUG_2("Notify func registered: %p", (void *)nd);
3229 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
3231 GList *work = notify_func_list;
3235 auto nd = static_cast<NotifyData *>(work->data);
3237 if (nd->func == func && nd->data == data)
3239 notify_func_list = g_list_delete_link(notify_func_list, work);
3240 DEBUG_2("Notify func unregistered: %p", (void *)nd);
3247 g_warning("Notify func not found");
3251 #pragma GCC diagnostic push
3252 #pragma GCC diagnostic ignored "-Wunused-function"
3253 gboolean file_data_send_notification_idle_cb_unused(gpointer data)
3255 auto *nid = (NotifyIdleData *)data;
3256 GList *work = notify_func_list;
3260 auto *nd = (NotifyData *)work->data;
3262 nd->func(nid->fd, nid->type, nd->data);
3265 file_data_unref(nid->fd);
3269 #pragma GCC diagnostic pop
3271 void file_data_send_notification(FileData *fd, NotifyType type)
3273 GList *work = notify_func_list;
3277 auto nd = static_cast<NotifyData *>(work->data);
3279 nd->func(fd, type, nd->data);
3283 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3284 nid->fd = file_data_ref(fd);
3286 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3290 static GHashTable *file_data_monitor_pool = nullptr;
3291 static guint realtime_monitor_id = 0; /* event source id */
3293 static void realtime_monitor_check_cb(gpointer key, gpointer, gpointer)
3295 auto fd = static_cast<FileData *>(key);
3297 file_data_check_changed_files(fd);
3299 DEBUG_1("monitor %s", fd->path);
3302 static gboolean realtime_monitor_cb(gpointer)
3304 if (!options->update_on_time_change) return TRUE;
3305 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, nullptr);
3309 gboolean file_data_register_real_time_monitor(FileData *fd)
3315 if (!file_data_monitor_pool)
3316 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3318 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3320 DEBUG_1("Register realtime %d %s", count, fd->path);
3323 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3325 if (!realtime_monitor_id)
3327 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, nullptr);
3333 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3337 g_assert(file_data_monitor_pool);
3339 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3341 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3343 g_assert(count > 0);
3348 g_hash_table_remove(file_data_monitor_pool, fd);
3350 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3352 file_data_unref(fd);
3354 if (g_hash_table_size(file_data_monitor_pool) == 0)
3356 g_source_remove(realtime_monitor_id);
3357 realtime_monitor_id = 0;
3365 *-----------------------------------------------------------------------------
3366 * Saving marks list, clearing marks
3367 * Uses file_data_pool
3368 *-----------------------------------------------------------------------------
3371 static void marks_get_files(gpointer key, gpointer value, gpointer userdata)
3373 auto file_name = static_cast<gchar *>(key);
3374 auto result = static_cast<GString *>(userdata);
3377 if (isfile(file_name))
3379 fd = static_cast<FileData *>(value);
3380 if (fd && fd->marks > 0)
3382 g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks);
3387 gboolean marks_list_load(const gchar *path)
3395 pathl = path_from_utf8(path);
3396 f = fopen(pathl, "r");
3398 if (!f) return FALSE;
3400 /* first line must start with Marks comment */
3401 if (!fgets(s_buf, sizeof(s_buf), f) ||
3402 strncmp(s_buf, "#Marks", 6) != 0)
3408 while (fgets(s_buf, sizeof(s_buf), f))
3410 if (s_buf[0]=='#') continue;
3411 file_path = strtok(s_buf, ",");
3412 marks_value = strtok(nullptr, ",");
3413 if (isfile(file_path))
3415 FileData *fd = file_data_new_no_grouping(file_path);
3420 gint mark_no = 1 << n;
3421 if (atoi(marks_value) & mark_no)
3423 file_data_set_mark(fd, n , 1);
3434 gboolean marks_list_save(gchar *path, gboolean save)
3436 SecureSaveInfo *ssi;
3439 pathl = path_from_utf8(path);
3440 ssi = secure_open(pathl);
3444 log_printf(_("Error: Unable to write marks lists to: %s\n"), path);
3448 secure_fprintf(ssi, "#Marks lists\n");
3450 GString *marks = g_string_new("");
3453 g_hash_table_foreach(file_data_pool, marks_get_files, marks);
3455 secure_fprintf(ssi, "%s", marks->str);
3456 g_string_free(marks, TRUE);
3458 secure_fprintf(ssi, "#end\n");
3459 return (secure_close(ssi) == 0);
3462 static void marks_clear(gpointer key, gpointer value, gpointer)
3464 auto file_name = static_cast<gchar *>(key);
3469 if (isfile(file_name))
3471 fd = static_cast<FileData *>(value);
3472 if (fd && fd->marks > 0)
3478 if (fd->marks & mark_no)
3480 file_data_set_mark(fd, n , 0);
3488 void marks_clear_all()
3490 g_hash_table_foreach(file_data_pool, marks_clear, nullptr);
3493 void file_data_set_page_num(FileData *fd, gint page_num)
3495 if (fd->page_total > 1 && page_num < 0)
3497 fd->page_num = fd->page_total - 1;
3499 else if (fd->page_total > 1 && page_num <= fd->page_total)
3501 fd->page_num = page_num - 1;
3507 file_data_send_notification(fd, NOTIFY_REREAD);
3510 void file_data_inc_page_num(FileData *fd)
3512 if (fd->page_total > 0 && fd->page_num < fd->page_total - 1)
3514 fd->page_num = fd->page_num + 1;
3516 else if (fd->page_total == 0)
3518 fd->page_num = fd->page_num + 1;
3520 file_data_send_notification(fd, NOTIFY_REREAD);
3523 void file_data_dec_page_num(FileData *fd)
3525 if (fd->page_num > 0)
3527 fd->page_num = fd->page_num - 1;
3529 file_data_send_notification(fd, NOTIFY_REREAD);
3532 void file_data_set_page_total(FileData *fd, gint page_total)
3534 fd->page_total = page_total;
3537 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */