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"
38 gint global_file_data_count = 0;
41 static GHashTable *file_data_pool = NULL;
42 static GHashTable *file_data_planned_change_hash = NULL;
44 static gint sidecar_file_priority(const gchar *extension);
45 static void file_data_check_sidecars(const GList *basename_list);
46 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
49 static SortType filelist_sort_method = SORT_NONE;
50 static gboolean filelist_sort_ascend = TRUE;
53 *-----------------------------------------------------------------------------
54 * text conversion utils
55 *-----------------------------------------------------------------------------
58 gchar *text_from_size(gint64 size)
64 /* what I would like to use is printf("%'d", size)
65 * BUT: not supported on every libc :(
69 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
70 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
74 a = g_strdup_printf("%d", (guint)size);
80 b = g_new(gchar, l + n + 1);
105 gchar *text_from_size_abrev(gint64 size)
107 if (size < (gint64)1024)
109 return g_strdup_printf(_("%d bytes"), (gint)size);
111 if (size < (gint64)1048576)
113 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
115 if (size < (gint64)1073741824)
117 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
120 /* to avoid overflowing the gdouble, do division in two steps */
122 return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
125 /* note: returned string is valid until next call to text_from_time() */
126 const gchar *text_from_time(time_t t)
128 static gchar *ret = NULL;
132 GError *error = NULL;
134 btime = localtime(&t);
136 /* the %x warning about 2 digit years is not an error */
137 buflen = strftime(buf, sizeof(buf), "%x %X", btime);
138 if (buflen < 1) return "";
141 ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
144 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
153 *-----------------------------------------------------------------------------
154 * changed files detection and notification
155 *-----------------------------------------------------------------------------
158 void file_data_increment_version(FileData *fd)
164 fd->parent->version++;
165 fd->parent->valid_marks = 0;
169 static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st)
171 if (fd->size != st->st_size ||
172 fd->date != st->st_mtime)
174 fd->size = st->st_size;
175 fd->date = st->st_mtime;
176 fd->cdate = st->st_ctime;
177 fd->mode = st->st_mode;
178 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
179 fd->thumb_pixbuf = NULL;
180 file_data_increment_version(fd);
181 file_data_send_notification(fd, NOTIFY_REREAD);
187 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
189 gboolean ret = FALSE;
192 ret = file_data_check_changed_single_file(fd, st);
194 work = fd->sidecar_files;
197 FileData *sfd = work->data;
201 if (!stat_utf8(sfd->path, &st))
206 file_data_disconnect_sidecar_file(fd, sfd);
208 file_data_increment_version(sfd);
209 file_data_send_notification(sfd, NOTIFY_REREAD);
210 file_data_unref(sfd);
214 ret |= file_data_check_changed_files_recursive(sfd, &st);
220 gboolean file_data_check_changed_files(FileData *fd)
222 gboolean ret = FALSE;
225 if (fd->parent) fd = fd->parent;
227 if (!stat_utf8(fd->path, &st))
231 FileData *sfd = NULL;
233 /* parent is missing, we have to rebuild whole group */
238 /* file_data_disconnect_sidecar_file might delete the file,
239 we have to keep the reference to prevent this */
240 sidecars = filelist_copy(fd->sidecar_files);
248 file_data_disconnect_sidecar_file(fd, sfd);
250 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
251 /* now we can release the sidecars */
252 filelist_free(sidecars);
253 file_data_increment_version(fd);
254 file_data_send_notification(fd, NOTIFY_REREAD);
259 ret |= file_data_check_changed_files_recursive(fd, &st);
266 *-----------------------------------------------------------------------------
267 * file name, extension, sorting, ...
268 *-----------------------------------------------------------------------------
271 static void file_data_set_collate_keys(FileData *fd)
273 gchar *caseless_name;
276 valid_name = g_filename_display_name(fd->name);
277 caseless_name = g_utf8_casefold(valid_name, -1);
279 g_free(fd->collate_key_name);
280 g_free(fd->collate_key_name_nocase);
282 #if GTK_CHECK_VERSION(2, 8, 0)
283 if (options->file_sort.natural)
285 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
286 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
290 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
291 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
294 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
295 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
299 g_free(caseless_name);
302 static void file_data_set_path(FileData *fd, const gchar *path)
304 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
305 g_assert(file_data_pool);
309 if (fd->original_path)
311 g_hash_table_remove(file_data_pool, fd->original_path);
312 g_free(fd->original_path);
315 g_assert(!g_hash_table_lookup(file_data_pool, path));
317 fd->original_path = g_strdup(path);
318 g_hash_table_insert(file_data_pool, fd->original_path, fd);
320 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
322 fd->path = g_strdup(path);
324 fd->extension = fd->name + 1;
325 file_data_set_collate_keys(fd);
329 fd->path = g_strdup(path);
330 fd->name = filename_from_path(fd->path);
332 if (strcmp(fd->name, "..") == 0)
334 gchar *dir = remove_level_from_path(path);
336 fd->path = remove_level_from_path(dir);
339 fd->extension = fd->name + 2;
340 file_data_set_collate_keys(fd);
343 else if (strcmp(fd->name, ".") == 0)
346 fd->path = remove_level_from_path(path);
348 fd->extension = fd->name + 1;
349 file_data_set_collate_keys(fd);
353 fd->extension = registered_extension_from_path(fd->path);
354 if (fd->extension == NULL)
356 fd->extension = fd->name + strlen(fd->name);
359 fd->sidecar_priority = sidecar_file_priority(fd->extension);
360 file_data_set_collate_keys(fd);
364 *-----------------------------------------------------------------------------
365 * create or reuse Filedata
366 *-----------------------------------------------------------------------------
369 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
373 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
375 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
378 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
380 fd = g_hash_table_lookup(file_data_pool, path_utf8);
386 if (!fd && file_data_planned_change_hash)
388 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
391 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
392 if (!isfile(fd->path))
395 file_data_apply_ci(fd);
408 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
411 changed = file_data_check_changed_single_file(fd, st);
413 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
418 fd = g_new0(FileData, 1);
419 #ifdef DEBUG_FILEDATA
420 global_file_data_count++;
421 DEBUG_2("file data count++: %d", global_file_data_count);
424 fd->size = st->st_size;
425 fd->date = st->st_mtime;
426 fd->cdate = st->st_ctime;
427 fd->mode = st->st_mode;
429 fd->magick = FD_MAGICK;
431 if (disable_sidecars) fd->disable_grouping = TRUE;
433 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
438 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
440 gchar *path_utf8 = path_to_utf8(path);
441 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
447 FileData *file_data_new_simple(const gchar *path_utf8)
452 if (!stat_utf8(path_utf8, &st))
458 fd = g_hash_table_lookup(file_data_pool, path_utf8);
459 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
468 void init_exif_time_data(GList *files)
471 DEBUG_1("%s init_exif_time_data: ...", get_exec_time());
483 void read_exif_time_data(FileData *file)
485 if (file->exifdate > 0)
487 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
491 file->exif = exif_read_fd(file);
495 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
496 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), file, file->path);
501 uint year, month, day, hour, min, sec;
503 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
504 time_str.tm_year = year - 1900;
505 time_str.tm_mon = month - 1;
506 time_str.tm_mday = day;
507 time_str.tm_hour = hour;
508 time_str.tm_min = min;
509 time_str.tm_sec = sec;
510 time_str.tm_isdst = 0;
512 file->exifdate = mktime(&time_str);
518 void set_exif_time_data(GList *files)
520 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
524 FileData *file = files->data;
526 read_exif_time_data(file);
531 FileData *file_data_new_no_grouping(const gchar *path_utf8)
535 if (!stat_utf8(path_utf8, &st))
541 return file_data_new(path_utf8, &st, TRUE);
544 FileData *file_data_new_dir(const gchar *path_utf8)
548 if (!stat_utf8(path_utf8, &st))
554 /* dir or non-existing yet */
555 g_assert(S_ISDIR(st.st_mode));
557 return file_data_new(path_utf8, &st, TRUE);
561 *-----------------------------------------------------------------------------
563 *-----------------------------------------------------------------------------
566 #ifdef DEBUG_FILEDATA
567 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
569 FileData *file_data_ref(FileData *fd)
572 if (fd == NULL) return NULL;
573 if (fd->magick != FD_MAGICK)
574 #ifdef DEBUG_FILEDATA
575 DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
577 DEBUG_0("fd magick mismatch fd=%p", fd);
579 g_assert(fd->magick == FD_MAGICK);
582 #ifdef DEBUG_FILEDATA
583 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", fd, fd->ref, fd->path, file, line);
585 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
590 static void file_data_free(FileData *fd)
592 g_assert(fd->magick == FD_MAGICK);
593 g_assert(fd->ref == 0);
594 g_assert(!fd->locked);
596 #ifdef DEBUG_FILEDATA
597 global_file_data_count--;
598 DEBUG_2("file data count--: %d", global_file_data_count);
601 metadata_cache_free(fd);
602 g_hash_table_remove(file_data_pool, fd->original_path);
605 g_free(fd->original_path);
606 g_free(fd->collate_key_name);
607 g_free(fd->collate_key_name_nocase);
608 g_free(fd->extended_extension);
609 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
610 histmap_free(fd->histmap);
612 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
614 file_data_change_info_free(NULL, fd);
619 * \brief Checks if the FileData is referenced
621 * Checks the refcount and whether the FileData is locked.
623 static gboolean file_data_check_has_ref(FileData *fd)
625 return fd->ref > 0 || fd->locked;
629 * \brief Consider freeing a FileData.
631 * This function will free a FileData and its children provided that neither its parent nor it has
632 * a positive refcount, and provided that neither is locked.
634 static void file_data_consider_free(FileData *fd)
637 FileData *parent = fd->parent ? fd->parent : fd;
639 g_assert(fd->magick == FD_MAGICK);
640 if (file_data_check_has_ref(fd)) return;
641 if (file_data_check_has_ref(parent)) return;
643 work = parent->sidecar_files;
646 FileData *sfd = work->data;
647 if (file_data_check_has_ref(sfd)) return;
651 /* Neither the parent nor the siblings are referenced, so we can free everything */
652 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
653 fd->path, fd->parent ? parent->path : "-");
655 work = parent->sidecar_files;
658 FileData *sfd = work->data;
663 g_list_free(parent->sidecar_files);
664 parent->sidecar_files = NULL;
666 file_data_free(parent);
669 #ifdef DEBUG_FILEDATA
670 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
672 void file_data_unref(FileData *fd)
675 if (fd == NULL) return;
676 if (fd->magick != FD_MAGICK)
677 #ifdef DEBUG_FILEDATA
678 DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
680 DEBUG_0("fd magick mismatch fd=%p", fd);
682 g_assert(fd->magick == FD_MAGICK);
685 #ifdef DEBUG_FILEDATA
686 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", fd, fd->ref, fd->locked, fd->path,
689 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
692 // Free FileData if it's no longer ref'd
693 file_data_consider_free(fd);
697 * \brief Lock the FileData in memory.
699 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
700 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
701 * even if the code would continue to function properly even if the FileData were freed. Code that
702 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
704 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
705 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
707 void file_data_lock(FileData *fd)
709 if (fd == NULL) return;
710 if (fd->magick != FD_MAGICK) DEBUG_0("fd magick mismatch fd=%p", fd);
712 g_assert(fd->magick == FD_MAGICK);
715 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
719 * \brief Reset the maintain-FileData-in-memory lock
721 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
722 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
723 * keeping it from being freed.
725 void file_data_unlock(FileData *fd)
727 if (fd == NULL) return;
728 if (fd->magick != FD_MAGICK) DEBUG_0("fd magick mismatch fd=%p", fd);
730 g_assert(fd->magick == FD_MAGICK);
733 // Free FileData if it's no longer ref'd
734 file_data_consider_free(fd);
738 * \brief Lock all of the FileDatas in the provided list
740 * \see file_data_lock(FileData)
742 void file_data_lock_list(GList *list)
749 FileData *fd = work->data;
756 * \brief Unlock all of the FileDatas in the provided list
758 * \see file_data_unlock(FileData)
760 void file_data_unlock_list(GList *list)
767 FileData *fd = work->data;
769 file_data_unlock(fd);
774 *-----------------------------------------------------------------------------
775 * sidecar file info struct
776 *-----------------------------------------------------------------------------
779 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
781 const FileData *fda = a;
782 const FileData *fdb = b;
784 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
785 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
787 return strcmp(fdb->extension, fda->extension);
791 static gint sidecar_file_priority(const gchar *extension)
796 if (extension == NULL)
799 work = sidecar_ext_get_list();
802 gchar *ext = work->data;
805 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
811 static void file_data_check_sidecars(const GList *basename_list)
813 /* basename_list contains the new group - first is the parent, then sorted sidecars */
814 /* all files in the list have ref count > 0 */
817 GList *s_work, *new_sidecars;
820 if (!basename_list) return;
823 DEBUG_2("basename start");
824 work = basename_list;
827 FileData *fd = work->data;
829 g_assert(fd->magick == FD_MAGICK);
830 DEBUG_2("basename: %p %s", fd, fd->name);
833 g_assert(fd->parent->magick == FD_MAGICK);
834 DEBUG_2(" parent: %p", fd->parent);
836 s_work = fd->sidecar_files;
839 FileData *sfd = s_work->data;
840 s_work = s_work->next;
841 g_assert(sfd->magick == FD_MAGICK);
842 DEBUG_2(" sidecar: %p %s", sfd, sfd->name);
845 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
848 parent_fd = basename_list->data;
850 /* check if the second and next entries of basename_list are already connected
851 as sidecars of the first entry (parent_fd) */
852 work = basename_list->next;
853 s_work = parent_fd->sidecar_files;
855 while (work && s_work)
857 if (work->data != s_work->data) break;
859 s_work = s_work->next;
862 if (!work && !s_work)
864 DEBUG_2("basename no change");
865 return; /* no change in grouping */
868 /* we have to regroup it */
870 /* first, disconnect everything and send notification*/
872 work = basename_list;
875 FileData *fd = work->data;
877 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
881 FileData *old_parent = fd->parent;
882 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
883 file_data_ref(old_parent);
884 file_data_disconnect_sidecar_file(old_parent, fd);
885 file_data_send_notification(old_parent, NOTIFY_REREAD);
886 file_data_unref(old_parent);
889 while (fd->sidecar_files)
891 FileData *sfd = fd->sidecar_files->data;
892 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
894 file_data_disconnect_sidecar_file(fd, sfd);
895 file_data_send_notification(sfd, NOTIFY_REREAD);
896 file_data_unref(sfd);
898 file_data_send_notification(fd, NOTIFY_GROUPING);
900 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
903 /* now we can form the new group */
904 work = basename_list->next;
908 FileData *sfd = work->data;
909 g_assert(sfd->magick == FD_MAGICK);
910 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
911 sfd->parent = parent_fd;
912 new_sidecars = g_list_prepend(new_sidecars, sfd);
915 g_assert(parent_fd->sidecar_files == NULL);
916 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
917 DEBUG_1("basename group changed for %s", parent_fd->path);
921 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
923 g_assert(target->magick == FD_MAGICK);
924 g_assert(sfd->magick == FD_MAGICK);
925 g_assert(g_list_find(target->sidecar_files, sfd));
927 file_data_ref(target);
930 g_assert(sfd->parent == target);
932 file_data_increment_version(sfd); /* increments both sfd and target */
934 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
936 g_free(sfd->extended_extension);
937 sfd->extended_extension = NULL;
939 file_data_unref(target);
940 file_data_unref(sfd);
943 /* disables / enables grouping for particular file, sends UPDATE notification */
944 void file_data_disable_grouping(FileData *fd, gboolean disable)
946 if (!fd->disable_grouping == !disable) return;
948 fd->disable_grouping = !!disable;
954 FileData *parent = file_data_ref(fd->parent);
955 file_data_disconnect_sidecar_file(parent, fd);
956 file_data_send_notification(parent, NOTIFY_GROUPING);
957 file_data_unref(parent);
959 else if (fd->sidecar_files)
961 GList *sidecar_files = filelist_copy(fd->sidecar_files);
962 GList *work = sidecar_files;
965 FileData *sfd = work->data;
967 file_data_disconnect_sidecar_file(fd, sfd);
968 file_data_send_notification(sfd, NOTIFY_GROUPING);
970 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
971 filelist_free(sidecar_files);
975 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
980 file_data_increment_version(fd);
981 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
983 file_data_send_notification(fd, NOTIFY_GROUPING);
986 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
993 FileData *fd = work->data;
995 file_data_disable_grouping(fd, disable);
1003 *-----------------------------------------------------------------------------
1005 *-----------------------------------------------------------------------------
1009 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
1012 if (!filelist_sort_ascend)
1019 switch (filelist_sort_method)
1024 if (fa->size < fb->size) return -1;
1025 if (fa->size > fb->size) return 1;
1026 /* fall back to name */
1029 if (fa->date < fb->date) return -1;
1030 if (fa->date > fb->date) return 1;
1031 /* fall back to name */
1034 if (fa->cdate < fb->cdate) return -1;
1035 if (fa->cdate > fb->cdate) return 1;
1036 /* fall back to name */
1039 if (fa->exifdate < fb->exifdate) return -1;
1040 if (fa->exifdate > fb->exifdate) return 1;
1041 /* fall back to name */
1043 #ifdef HAVE_STRVERSCMP
1045 ret = strverscmp(fa->name, fb->name);
1046 if (ret != 0) return ret;
1053 if (options->file_sort.case_sensitive)
1054 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1056 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1058 if (ret != 0) return ret;
1060 /* do not return 0 unless the files are really the same
1061 file_data_pool ensures that original_path is unique
1063 return strcmp(fa->original_path, fb->original_path);
1066 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1068 filelist_sort_method = method;
1069 filelist_sort_ascend = ascend;
1070 return filelist_sort_compare_filedata(fa, fb);
1073 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1075 return filelist_sort_compare_filedata(a, b);
1078 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1080 filelist_sort_method = method;
1081 filelist_sort_ascend = ascend;
1082 return g_list_sort(list, cb);
1085 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1087 filelist_sort_method = method;
1088 filelist_sort_ascend = ascend;
1089 return g_list_insert_sorted(list, data, cb);
1092 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1094 if (method == SORT_EXIFTIME)
1096 set_exif_time_data(list);
1098 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1101 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
1103 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1107 *-----------------------------------------------------------------------------
1108 * basename hash - grouping of sidecars in filelist
1109 *-----------------------------------------------------------------------------
1113 static GHashTable *file_data_basename_hash_new(void)
1115 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1118 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1121 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1123 list = g_hash_table_lookup(basename_hash, basename);
1127 DEBUG_1("TG: basename_hash not found for %s",fd->path);
1128 const gchar *parent_extension = registered_extension_from_path(basename);
1130 if (parent_extension)
1132 DEBUG_1("TG: parent extension %s",parent_extension);
1133 gchar *parent_basename = g_strndup(basename, parent_extension - basename);
1134 DEBUG_1("TG: parent basename %s",parent_basename);
1135 FileData *parent_fd = g_hash_table_lookup(file_data_pool, basename);
1138 DEBUG_1("TG: parent fd found");
1139 list = g_hash_table_lookup(basename_hash, parent_basename);
1140 if (!g_list_find(list, parent_fd))
1142 DEBUG_1("TG: parent fd doesn't fit");
1143 g_free(parent_basename);
1149 basename = parent_basename;
1150 fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
1156 if (!g_list_find(list, fd))
1158 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1159 g_hash_table_insert(basename_hash, basename, list);
1168 static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
1170 file_data_basename_hash_insert((GHashTable *)basename_hash, (FileData *)fd);
1173 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
1175 filelist_free((GList *)value);
1178 static void file_data_basename_hash_free(GHashTable *basename_hash)
1180 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1181 g_hash_table_destroy(basename_hash);
1185 *-----------------------------------------------------------------------------
1186 * handling sidecars in filelist
1187 *-----------------------------------------------------------------------------
1190 static GList *filelist_filter_out_sidecars(GList *flist)
1192 GList *work = flist;
1193 GList *flist_filtered = NULL;
1197 FileData *fd = work->data;
1200 if (fd->parent) /* remove fd's that are children */
1201 file_data_unref(fd);
1203 flist_filtered = g_list_prepend(flist_filtered, fd);
1207 return flist_filtered;
1210 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
1212 GList *basename_list = (GList *)value;
1213 file_data_check_sidecars(basename_list);
1217 static gboolean is_hidden_file(const gchar *name)
1219 if (name[0] != '.') return FALSE;
1220 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1225 *-----------------------------------------------------------------------------
1226 * the main filelist function
1227 *-----------------------------------------------------------------------------
1230 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1235 GList *dlist = NULL;
1236 GList *flist = NULL;
1237 GList *xmp_files = NULL;
1238 gint (*stat_func)(const gchar *path, struct stat *buf);
1239 GHashTable *basename_hash = NULL;
1241 g_assert(files || dirs);
1243 if (files) *files = NULL;
1244 if (dirs) *dirs = NULL;
1246 pathl = path_from_utf8(dir_path);
1247 if (!pathl) return FALSE;
1249 dp = opendir(pathl);
1256 if (files) basename_hash = file_data_basename_hash_new();
1258 if (follow_symlinks)
1263 while ((dir = readdir(dp)) != NULL)
1265 struct stat ent_sbuf;
1266 const gchar *name = dir->d_name;
1269 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1272 filepath = g_build_filename(pathl, name, NULL);
1273 if (stat_func(filepath, &ent_sbuf) >= 0)
1275 if (S_ISDIR(ent_sbuf.st_mode))
1277 /* we ignore the .thumbnails dir for cleanliness */
1279 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1280 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1281 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1282 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1284 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1289 if (files && filter_name_exists(name))
1291 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1292 flist = g_list_prepend(flist, fd);
1293 if (fd->sidecar_priority && !fd->disable_grouping)
1295 if (strcmp(fd->extension, ".xmp") != 0)
1296 file_data_basename_hash_insert(basename_hash, fd);
1298 xmp_files = g_list_append(xmp_files, fd);
1305 if (errno == EOVERFLOW)
1307 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1319 g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
1320 g_list_free(xmp_files);
1323 if (dirs) *dirs = dlist;
1327 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1329 *files = filelist_filter_out_sidecars(flist);
1331 if (basename_hash) file_data_basename_hash_free(basename_hash);
1333 // Call a separate function to initialize the exif datestamps for the found files..
1334 if (files) init_exif_time_data(*files);
1339 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1341 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1344 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1346 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1349 FileData *file_data_new_group(const gchar *path_utf8)
1356 if (!stat_utf8(path_utf8, &st))
1362 if (S_ISDIR(st.st_mode))
1363 return file_data_new(path_utf8, &st, TRUE);
1365 dir = remove_level_from_path(path_utf8);
1367 filelist_read_real(dir, &files, NULL, TRUE);
1369 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1370 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1376 filelist_free(files);
1382 void filelist_free(GList *list)
1389 file_data_unref((FileData *)work->data);
1397 GList *filelist_copy(GList *list)
1399 GList *new_list = NULL;
1410 new_list = g_list_prepend(new_list, file_data_ref(fd));
1413 return g_list_reverse(new_list);
1416 GList *filelist_from_path_list(GList *list)
1418 GList *new_list = NULL;
1429 new_list = g_list_prepend(new_list, file_data_new_group(path));
1432 return g_list_reverse(new_list);
1435 GList *filelist_to_path_list(GList *list)
1437 GList *new_list = NULL;
1448 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1451 return g_list_reverse(new_list);
1454 GList *filelist_filter(GList *list, gboolean is_dir_list)
1458 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1463 FileData *fd = (FileData *)(work->data);
1464 const gchar *name = fd->name;
1466 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1467 (!is_dir_list && !filter_name_exists(name)) ||
1468 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1469 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1473 list = g_list_remove_link(list, link);
1474 file_data_unref(fd);
1485 *-----------------------------------------------------------------------------
1486 * filelist recursive
1487 *-----------------------------------------------------------------------------
1490 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1492 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1495 GList *filelist_sort_path(GList *list)
1497 return g_list_sort(list, filelist_sort_path_cb);
1500 static void filelist_recursive_append(GList **list, GList *dirs)
1507 FileData *fd = (FileData *)(work->data);
1511 if (filelist_read(fd, &f, &d))
1513 f = filelist_filter(f, FALSE);
1514 f = filelist_sort_path(f);
1515 *list = g_list_concat(*list, f);
1517 d = filelist_filter(d, TRUE);
1518 d = filelist_sort_path(d);
1519 filelist_recursive_append(list, d);
1527 GList *filelist_recursive(FileData *dir_fd)
1532 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1533 list = filelist_filter(list, FALSE);
1534 list = filelist_sort_path(list);
1536 d = filelist_filter(d, TRUE);
1537 d = filelist_sort_path(d);
1538 filelist_recursive_append(&list, d);
1545 *-----------------------------------------------------------------------------
1546 * file modification support
1547 *-----------------------------------------------------------------------------
1551 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1553 if (!fdci && fd) fdci = fd->change;
1557 g_free(fdci->source);
1562 if (fd) fd->change = NULL;
1565 static gboolean file_data_can_write_directly(FileData *fd)
1567 return filter_name_is_writable(fd->extension);
1570 static gboolean file_data_can_write_sidecar(FileData *fd)
1572 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1575 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1577 gchar *sidecar_path = NULL;
1580 if (!file_data_can_write_sidecar(fd)) return NULL;
1582 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1583 gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1586 FileData *sfd = work->data;
1588 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1590 sidecar_path = g_strdup(sfd->path);
1594 g_free(extended_extension);
1596 if (!existing_only && !sidecar_path)
1598 if (options->metadata.sidecar_extended_name)
1599 sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1602 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1603 sidecar_path = g_strconcat(base, ".xmp", NULL);
1608 return sidecar_path;
1612 * marks and orientation
1615 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1616 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1617 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1618 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1620 gboolean file_data_get_mark(FileData *fd, gint n)
1622 gboolean valid = (fd->valid_marks & (1 << n));
1624 if (file_data_get_mark_func[n] && !valid)
1626 guint old = fd->marks;
1627 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1629 if (!value != !(fd->marks & (1 << n)))
1631 fd->marks = fd->marks ^ (1 << n);
1634 fd->valid_marks |= (1 << n);
1635 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1637 file_data_unref(fd);
1639 else if (!old && fd->marks)
1645 return !!(fd->marks & (1 << n));
1648 guint file_data_get_marks(FileData *fd)
1651 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1655 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1658 if (!value == !file_data_get_mark(fd, n)) return;
1660 if (file_data_set_mark_func[n])
1662 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1667 fd->marks = fd->marks ^ (1 << n);
1669 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1671 file_data_unref(fd);
1673 else if (!old && fd->marks)
1678 file_data_increment_version(fd);
1679 file_data_send_notification(fd, NOTIFY_MARKS);
1682 gboolean file_data_filter_marks(FileData *fd, guint filter)
1685 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1686 return ((fd->marks & filter) == filter);
1689 GList *file_data_filter_marks_list(GList *list, guint filter)
1696 FileData *fd = work->data;
1700 if (!file_data_filter_marks(fd, filter))
1702 list = g_list_remove_link(list, link);
1703 file_data_unref(fd);
1711 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1713 FileData *fd = value;
1714 file_data_increment_version(fd);
1715 file_data_send_notification(fd, NOTIFY_MARKS);
1718 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1720 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1722 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1724 file_data_get_mark_func[n] = get_mark_func;
1725 file_data_set_mark_func[n] = set_mark_func;
1726 file_data_mark_func_data[n] = data;
1727 file_data_destroy_mark_func[n] = notify;
1731 /* this effectively changes all known files */
1732 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1738 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1740 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1741 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1742 if (data) *data = file_data_mark_func_data[n];
1745 gint file_data_get_user_orientation(FileData *fd)
1747 return fd->user_orientation;
1750 void file_data_set_user_orientation(FileData *fd, gint value)
1752 if (fd->user_orientation == value) return;
1754 fd->user_orientation = value;
1755 file_data_increment_version(fd);
1756 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1761 * file_data - operates on the given fd
1762 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1766 /* return list of sidecar file extensions in a string */
1767 gchar *file_data_sc_list_to_string(FileData *fd)
1770 GString *result = g_string_new("");
1772 work = fd->sidecar_files;
1775 FileData *sfd = work->data;
1777 result = g_string_append(result, "+ ");
1778 result = g_string_append(result, sfd->extension);
1780 if (work) result = g_string_append_c(result, ' ');
1783 return g_string_free(result, FALSE);
1789 * add FileDataChangeInfo (see typedefs.h) for the given operation
1790 * uses file_data_add_change_info
1792 * fails if the fd->change already exists - change operations can't run in parallel
1793 * fd->change_info works as a lock
1795 * dest can be NULL - in this case the current name is used for now, it will
1800 FileDataChangeInfo types:
1802 MOVE - path is changed, name may be changed too
1803 RENAME - path remains unchanged, name is changed
1804 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1805 sidecar names are changed too, extensions are not changed
1807 UPDATE - file size, date or grouping has been changed
1810 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1812 FileDataChangeInfo *fdci;
1814 if (fd->change) return FALSE;
1816 fdci = g_new0(FileDataChangeInfo, 1);
1821 fdci->source = g_strdup(src);
1823 fdci->source = g_strdup(fd->path);
1826 fdci->dest = g_strdup(dest);
1833 static void file_data_planned_change_remove(FileData *fd)
1835 if (file_data_planned_change_hash &&
1836 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1838 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1840 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1841 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1842 file_data_unref(fd);
1843 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1845 g_hash_table_destroy(file_data_planned_change_hash);
1846 file_data_planned_change_hash = NULL;
1847 DEBUG_1("planned change: empty");
1854 void file_data_free_ci(FileData *fd)
1856 FileDataChangeInfo *fdci = fd->change;
1860 file_data_planned_change_remove(fd);
1862 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1864 g_free(fdci->source);
1872 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1874 FileDataChangeInfo *fdci = fd->change;
1876 fdci->regroup_when_finished = enable;
1879 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1883 if (fd->parent) fd = fd->parent;
1885 if (fd->change) return FALSE;
1887 work = fd->sidecar_files;
1890 FileData *sfd = work->data;
1892 if (sfd->change) return FALSE;
1896 file_data_add_ci(fd, type, NULL, NULL);
1898 work = fd->sidecar_files;
1901 FileData *sfd = work->data;
1903 file_data_add_ci(sfd, type, NULL, NULL);
1910 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1914 if (fd->parent) fd = fd->parent;
1916 if (!fd->change || fd->change->type != type) return FALSE;
1918 work = fd->sidecar_files;
1921 FileData *sfd = work->data;
1923 if (!sfd->change || sfd->change->type != type) return FALSE;
1931 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1933 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1934 file_data_sc_update_ci_copy(fd, dest_path);
1938 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1940 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1941 file_data_sc_update_ci_move(fd, dest_path);
1945 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1947 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1948 file_data_sc_update_ci_rename(fd, dest_path);
1952 gboolean file_data_sc_add_ci_delete(FileData *fd)
1954 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1957 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1959 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1960 file_data_sc_update_ci_unspecified(fd, dest_path);
1964 gboolean file_data_add_ci_write_metadata(FileData *fd)
1966 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1969 void file_data_sc_free_ci(FileData *fd)
1973 if (fd->parent) fd = fd->parent;
1975 file_data_free_ci(fd);
1977 work = fd->sidecar_files;
1980 FileData *sfd = work->data;
1982 file_data_free_ci(sfd);
1987 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1990 gboolean ret = TRUE;
1995 FileData *fd = work->data;
1997 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2004 static void file_data_sc_revert_ci_list(GList *fd_list)
2011 FileData *fd = work->data;
2013 file_data_sc_free_ci(fd);
2018 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2025 FileData *fd = work->data;
2027 if (!func(fd, dest))
2029 file_data_sc_revert_ci_list(work->prev);
2038 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2040 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2043 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2045 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2048 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2050 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2053 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2055 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2058 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2061 gboolean ret = TRUE;
2066 FileData *fd = work->data;
2068 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2075 void file_data_free_ci_list(GList *fd_list)
2082 FileData *fd = work->data;
2084 file_data_free_ci(fd);
2089 void file_data_sc_free_ci_list(GList *fd_list)
2096 FileData *fd = work->data;
2098 file_data_sc_free_ci(fd);
2104 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2105 * fails if fd->change does not exist or the change type does not match
2108 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2110 FileDataChangeType type = fd->change->type;
2112 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2116 if (!file_data_planned_change_hash)
2117 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2119 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2121 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2122 g_hash_table_remove(file_data_planned_change_hash, old_path);
2123 file_data_unref(fd);
2126 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
2131 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2132 g_hash_table_remove(file_data_planned_change_hash, new_path);
2133 file_data_unref(ofd);
2136 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2138 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2143 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2145 gchar *old_path = fd->change->dest;
2147 fd->change->dest = g_strdup(dest_path);
2148 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2152 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2154 const gchar *extension = registered_extension_from_path(fd->change->source);
2155 gchar *base = remove_extension_from_path(dest_path);
2156 gchar *old_path = fd->change->dest;
2158 fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2159 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2165 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2168 gchar *dest_path_full = NULL;
2170 if (fd->parent) fd = fd->parent;
2174 dest_path = fd->path;
2176 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2178 gchar *dir = remove_level_from_path(fd->path);
2180 dest_path_full = g_build_filename(dir, dest_path, NULL);
2182 dest_path = dest_path_full;
2184 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2186 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2187 dest_path = dest_path_full;
2190 file_data_update_ci_dest(fd, dest_path);
2192 work = fd->sidecar_files;
2195 FileData *sfd = work->data;
2197 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2201 g_free(dest_path_full);
2204 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2206 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2207 file_data_sc_update_ci(fd, dest_path);
2211 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2213 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2216 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2218 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2221 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2223 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2226 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2228 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2231 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2233 gboolean (*func)(FileData *, const gchar *))
2236 gboolean ret = TRUE;
2241 FileData *fd = work->data;
2243 if (!func(fd, dest)) ret = FALSE;
2250 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2252 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2255 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2257 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2260 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2262 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2267 * verify source and dest paths - dest image exists, etc.
2268 * it should detect all possible problems with the planned operation
2271 gint file_data_verify_ci(FileData *fd, GList *list)
2273 gint ret = CHANGE_OK;
2276 FileData *fd1 = NULL;
2280 DEBUG_1("Change checked: no change info: %s", fd->path);
2284 if (!isname(fd->path))
2286 /* this probably should not happen */
2287 ret |= CHANGE_NO_SRC;
2288 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2292 dir = remove_level_from_path(fd->path);
2294 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2295 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2296 fd->change->type != FILEDATA_CHANGE_RENAME &&
2297 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2300 ret |= CHANGE_WARN_UNSAVED_META;
2301 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2304 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2305 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2306 !access_file(fd->path, R_OK))
2308 ret |= CHANGE_NO_READ_PERM;
2309 DEBUG_1("Change checked: no read permission: %s", fd->path);
2311 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2312 !access_file(dir, W_OK))
2314 ret |= CHANGE_NO_WRITE_PERM_DIR;
2315 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2317 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2318 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2319 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2320 !access_file(fd->path, W_OK))
2322 ret |= CHANGE_WARN_NO_WRITE_PERM;
2323 DEBUG_1("Change checked: no write permission: %s", fd->path);
2325 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2326 - that means that there are no hard errors and warnings can be disabled
2327 - the destination is determined during the check
2329 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2331 /* determine destination file */
2332 gboolean have_dest = FALSE;
2333 gchar *dest_dir = NULL;
2335 if (options->metadata.save_in_image_file)
2337 if (file_data_can_write_directly(fd))
2339 /* we can write the file directly */
2340 if (access_file(fd->path, W_OK))
2346 if (options->metadata.warn_on_write_problems)
2348 ret |= CHANGE_WARN_NO_WRITE_PERM;
2349 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2353 else if (file_data_can_write_sidecar(fd))
2355 /* we can write sidecar */
2356 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2357 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2359 file_data_update_ci_dest(fd, sidecar);
2364 if (options->metadata.warn_on_write_problems)
2366 ret |= CHANGE_WARN_NO_WRITE_PERM;
2367 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2376 /* write private metadata file under ~/.geeqie */
2378 /* If an existing metadata file exists, we will try writing to
2379 * it's location regardless of the user's preference.
2381 gchar *metadata_path = NULL;
2383 /* but ignore XMP if we are not able to write it */
2384 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2386 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2388 if (metadata_path && !access_file(metadata_path, W_OK))
2390 g_free(metadata_path);
2391 metadata_path = NULL;
2398 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2399 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2401 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2403 metadata_path = g_build_filename(dest_dir, filename, NULL);
2407 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2409 file_data_update_ci_dest(fd, metadata_path);
2414 ret |= CHANGE_NO_WRITE_PERM_DEST;
2415 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2417 g_free(metadata_path);
2422 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2427 same = (strcmp(fd->path, fd->change->dest) == 0);
2431 const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2432 if (!dest_ext) dest_ext = "";
2433 if (!options->file_filter.disable_file_extension_checks)
2435 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2437 ret |= CHANGE_WARN_CHANGED_EXT;
2438 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2444 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2446 ret |= CHANGE_WARN_SAME;
2447 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2451 dest_dir = remove_level_from_path(fd->change->dest);
2453 if (!isdir(dest_dir))
2455 ret |= CHANGE_NO_DEST_DIR;
2456 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2458 else if (!access_file(dest_dir, W_OK))
2460 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2461 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2465 if (isfile(fd->change->dest))
2467 if (!access_file(fd->change->dest, W_OK))
2469 ret |= CHANGE_NO_WRITE_PERM_DEST;
2470 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2474 ret |= CHANGE_WARN_DEST_EXISTS;
2475 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2478 else if (isdir(fd->change->dest))
2480 ret |= CHANGE_DEST_EXISTS;
2481 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2488 /* During a rename operation, check if another planned destination file has
2491 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2492 fd->change->type == FILEDATA_CHANGE_COPY ||
2493 fd->change->type == FILEDATA_CHANGE_MOVE)
2500 if (fd1 != NULL && fd != fd1 )
2502 if (!strcmp(fd->change->dest, fd1->change->dest))
2504 ret |= CHANGE_DUPLICATE_DEST;
2510 fd->change->error = ret;
2511 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2518 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2523 ret = file_data_verify_ci(fd, list);
2525 work = fd->sidecar_files;
2528 FileData *sfd = work->data;
2530 ret |= file_data_verify_ci(sfd, list);
2537 gchar *file_data_get_error_string(gint error)
2539 GString *result = g_string_new("");
2541 if (error & CHANGE_NO_SRC)
2543 if (result->len > 0) g_string_append(result, ", ");
2544 g_string_append(result, _("file or directory does not exist"));
2547 if (error & CHANGE_DEST_EXISTS)
2549 if (result->len > 0) g_string_append(result, ", ");
2550 g_string_append(result, _("destination already exists"));
2553 if (error & CHANGE_NO_WRITE_PERM_DEST)
2555 if (result->len > 0) g_string_append(result, ", ");
2556 g_string_append(result, _("destination can't be overwritten"));
2559 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2561 if (result->len > 0) g_string_append(result, ", ");
2562 g_string_append(result, _("destination directory is not writable"));
2565 if (error & CHANGE_NO_DEST_DIR)
2567 if (result->len > 0) g_string_append(result, ", ");
2568 g_string_append(result, _("destination directory does not exist"));
2571 if (error & CHANGE_NO_WRITE_PERM_DIR)
2573 if (result->len > 0) g_string_append(result, ", ");
2574 g_string_append(result, _("source directory is not writable"));
2577 if (error & CHANGE_NO_READ_PERM)
2579 if (result->len > 0) g_string_append(result, ", ");
2580 g_string_append(result, _("no read permission"));
2583 if (error & CHANGE_WARN_NO_WRITE_PERM)
2585 if (result->len > 0) g_string_append(result, ", ");
2586 g_string_append(result, _("file is readonly"));
2589 if (error & CHANGE_WARN_DEST_EXISTS)
2591 if (result->len > 0) g_string_append(result, ", ");
2592 g_string_append(result, _("destination already exists and will be overwritten"));
2595 if (error & CHANGE_WARN_SAME)
2597 if (result->len > 0) g_string_append(result, ", ");
2598 g_string_append(result, _("source and destination are the same"));
2601 if (error & CHANGE_WARN_CHANGED_EXT)
2603 if (result->len > 0) g_string_append(result, ", ");
2604 g_string_append(result, _("source and destination have different extension"));
2607 if (error & CHANGE_WARN_UNSAVED_META)
2609 if (result->len > 0) g_string_append(result, ", ");
2610 g_string_append(result, _("there are unsaved metadata changes for the file"));
2613 if (error & CHANGE_DUPLICATE_DEST)
2615 if (result->len > 0) g_string_append(result, ", ");
2616 g_string_append(result, _("another destination file has the same filename"));
2619 return g_string_free(result, FALSE);
2622 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2625 gint all_errors = 0;
2626 gint common_errors = ~0;
2631 if (!list) return 0;
2633 num = g_list_length(list);
2634 errors = g_new(int, num);
2645 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2646 all_errors |= error;
2647 common_errors &= error;
2654 if (desc && all_errors)
2657 GString *result = g_string_new("");
2661 gchar *str = file_data_get_error_string(common_errors);
2662 g_string_append(result, str);
2663 g_string_append(result, "\n");
2677 error = errors[i] & ~common_errors;
2681 gchar *str = file_data_get_error_string(error);
2682 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2687 *desc = g_string_free(result, FALSE);
2696 * perform the change described by FileFataChangeInfo
2697 * it is used for internal operations,
2698 * this function actually operates with files on the filesystem
2699 * it should implement safe delete
2702 static gboolean file_data_perform_move(FileData *fd)
2704 g_assert(!strcmp(fd->change->source, fd->path));
2705 return move_file(fd->change->source, fd->change->dest);
2708 static gboolean file_data_perform_copy(FileData *fd)
2710 g_assert(!strcmp(fd->change->source, fd->path));
2711 return copy_file(fd->change->source, fd->change->dest);
2714 static gboolean file_data_perform_delete(FileData *fd)
2716 if (isdir(fd->path) && !islink(fd->path))
2717 return rmdir_utf8(fd->path);
2719 if (options->file_ops.safe_delete_enable)
2720 return file_util_safe_unlink(fd->path);
2722 return unlink_file(fd->path);
2725 gboolean file_data_perform_ci(FileData *fd)
2727 FileDataChangeType type = fd->change->type;
2731 case FILEDATA_CHANGE_MOVE:
2732 return file_data_perform_move(fd);
2733 case FILEDATA_CHANGE_COPY:
2734 return file_data_perform_copy(fd);
2735 case FILEDATA_CHANGE_RENAME:
2736 return file_data_perform_move(fd); /* the same as move */
2737 case FILEDATA_CHANGE_DELETE:
2738 return file_data_perform_delete(fd);
2739 case FILEDATA_CHANGE_WRITE_METADATA:
2740 return metadata_write_perform(fd);
2741 case FILEDATA_CHANGE_UNSPECIFIED:
2742 /* nothing to do here */
2750 gboolean file_data_sc_perform_ci(FileData *fd)
2753 gboolean ret = TRUE;
2754 FileDataChangeType type = fd->change->type;
2756 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2758 work = fd->sidecar_files;
2761 FileData *sfd = work->data;
2763 if (!file_data_perform_ci(sfd)) ret = FALSE;
2767 if (!file_data_perform_ci(fd)) ret = FALSE;
2773 * updates FileData structure according to FileDataChangeInfo
2776 gboolean file_data_apply_ci(FileData *fd)
2778 FileDataChangeType type = fd->change->type;
2781 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2783 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2784 file_data_planned_change_remove(fd);
2786 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2788 /* this change overwrites another file which is already known to other modules
2789 renaming fd would create duplicate FileData structure
2790 the best thing we can do is nothing
2791 FIXME: maybe we could copy stuff like marks
2793 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2797 file_data_set_path(fd, fd->change->dest);
2800 file_data_increment_version(fd);
2801 file_data_send_notification(fd, NOTIFY_CHANGE);
2806 gboolean file_data_sc_apply_ci(FileData *fd)
2809 FileDataChangeType type = fd->change->type;
2811 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2813 work = fd->sidecar_files;
2816 FileData *sfd = work->data;
2818 file_data_apply_ci(sfd);
2822 file_data_apply_ci(fd);
2827 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2830 if (fd->parent) fd = fd->parent;
2831 if (!g_list_find(list, fd)) return FALSE;
2833 work = fd->sidecar_files;
2836 if (!g_list_find(list, work->data)) return FALSE;
2842 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2847 /* change partial groups to independent files */
2852 FileData *fd = work->data;
2855 if (!file_data_list_contains_whole_group(list, fd))
2857 file_data_disable_grouping(fd, TRUE);
2860 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2866 /* remove sidecars from the list,
2867 they can be still acessed via main_fd->sidecar_files */
2871 FileData *fd = work->data;
2875 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2877 out = g_list_prepend(out, file_data_ref(fd));
2881 filelist_free(list);
2882 out = g_list_reverse(out);
2892 * notify other modules about the change described by FileDataChangeInfo
2895 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2896 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2897 implementation in view_file_list.c */
2900 typedef struct _NotifyIdleData NotifyIdleData;
2902 struct _NotifyIdleData {
2908 typedef struct _NotifyData NotifyData;
2910 struct _NotifyData {
2911 FileDataNotifyFunc func;
2913 NotifyPriority priority;
2916 static GList *notify_func_list = NULL;
2918 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2920 NotifyData *nda = (NotifyData *)a;
2921 NotifyData *ndb = (NotifyData *)b;
2923 if (nda->priority < ndb->priority) return -1;
2924 if (nda->priority > ndb->priority) return 1;
2928 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2931 GList *work = notify_func_list;
2935 NotifyData *nd = (NotifyData *)work->data;
2937 if (nd->func == func && nd->data == data)
2939 g_warning("Notify func already registered");
2945 nd = g_new(NotifyData, 1);
2948 nd->priority = priority;
2950 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2951 DEBUG_2("Notify func registered: %p", nd);
2956 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2958 GList *work = notify_func_list;
2962 NotifyData *nd = (NotifyData *)work->data;
2964 if (nd->func == func && nd->data == data)
2966 notify_func_list = g_list_delete_link(notify_func_list, work);
2968 DEBUG_2("Notify func unregistered: %p", nd);
2974 g_warning("Notify func not found");
2979 gboolean file_data_send_notification_idle_cb(gpointer data)
2981 NotifyIdleData *nid = (NotifyIdleData *)data;
2982 GList *work = notify_func_list;
2986 NotifyData *nd = (NotifyData *)work->data;
2988 nd->func(nid->fd, nid->type, nd->data);
2991 file_data_unref(nid->fd);
2996 void file_data_send_notification(FileData *fd, NotifyType type)
2998 GList *work = notify_func_list;
3002 NotifyData *nd = (NotifyData *)work->data;
3004 nd->func(fd, type, nd->data);
3008 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3009 nid->fd = file_data_ref(fd);
3011 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3015 static GHashTable *file_data_monitor_pool = NULL;
3016 static guint realtime_monitor_id = 0; /* event source id */
3018 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
3022 file_data_check_changed_files(fd);
3024 DEBUG_1("monitor %s", fd->path);
3027 static gboolean realtime_monitor_cb(gpointer data)
3029 if (!options->update_on_time_change) return TRUE;
3030 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
3034 gboolean file_data_register_real_time_monitor(FileData *fd)
3040 if (!file_data_monitor_pool)
3041 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3043 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3045 DEBUG_1("Register realtime %d %s", count, fd->path);
3048 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3050 if (!realtime_monitor_id)
3052 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
3058 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3062 g_assert(file_data_monitor_pool);
3064 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3066 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3068 g_assert(count > 0);
3073 g_hash_table_remove(file_data_monitor_pool, fd);
3075 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3077 file_data_unref(fd);
3079 if (g_hash_table_size(file_data_monitor_pool) == 0)
3081 g_source_remove(realtime_monitor_id);
3082 realtime_monitor_id = 0;
3088 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */