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;
432 if (disable_sidecars) fd->disable_grouping = TRUE;
434 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
439 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
441 gchar *path_utf8 = path_to_utf8(path);
442 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
448 FileData *file_data_new_simple(const gchar *path_utf8)
453 if (!stat_utf8(path_utf8, &st))
459 fd = g_hash_table_lookup(file_data_pool, path_utf8);
460 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
469 void read_exif_time_data(FileData *file)
471 if (file->exifdate > 0)
473 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
477 file->exif = exif_read_fd(file);
481 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
482 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), file, file->path);
487 uint year, month, day, hour, min, sec;
489 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
490 time_str.tm_year = year - 1900;
491 time_str.tm_mon = month - 1;
492 time_str.tm_mday = day;
493 time_str.tm_hour = hour;
494 time_str.tm_min = min;
495 time_str.tm_sec = sec;
496 time_str.tm_isdst = 0;
498 file->exifdate = mktime(&time_str);
504 void set_exif_time_data(GList *files)
506 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
510 FileData *file = files->data;
512 read_exif_time_data(file);
517 FileData *file_data_new_no_grouping(const gchar *path_utf8)
521 if (!stat_utf8(path_utf8, &st))
527 return file_data_new(path_utf8, &st, TRUE);
530 FileData *file_data_new_dir(const gchar *path_utf8)
534 if (!stat_utf8(path_utf8, &st))
540 /* dir or non-existing yet */
541 g_assert(S_ISDIR(st.st_mode));
543 return file_data_new(path_utf8, &st, TRUE);
547 *-----------------------------------------------------------------------------
549 *-----------------------------------------------------------------------------
552 #ifdef DEBUG_FILEDATA
553 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
555 FileData *file_data_ref(FileData *fd)
558 if (fd == NULL) return NULL;
559 if (fd->magick != FD_MAGICK)
560 #ifdef DEBUG_FILEDATA
561 DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
563 DEBUG_0("fd magick mismatch fd=%p", fd);
565 g_assert(fd->magick == FD_MAGICK);
568 #ifdef DEBUG_FILEDATA
569 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", fd, fd->ref, fd->path, file, line);
571 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
576 static void file_data_free(FileData *fd)
578 g_assert(fd->magick == FD_MAGICK);
579 g_assert(fd->ref == 0);
580 g_assert(!fd->locked);
582 #ifdef DEBUG_FILEDATA
583 global_file_data_count--;
584 DEBUG_2("file data count--: %d", global_file_data_count);
587 metadata_cache_free(fd);
588 g_hash_table_remove(file_data_pool, fd->original_path);
591 g_free(fd->original_path);
592 g_free(fd->collate_key_name);
593 g_free(fd->collate_key_name_nocase);
594 g_free(fd->extended_extension);
595 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
596 histmap_free(fd->histmap);
598 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
600 file_data_change_info_free(NULL, fd);
605 * \brief Checks if the FileData is referenced
607 * Checks the refcount and whether the FileData is locked.
609 static gboolean file_data_check_has_ref(FileData *fd)
611 return fd->ref > 0 || fd->locked;
615 * \brief Consider freeing a FileData.
617 * This function will free a FileData and its children provided that neither its parent nor it has
618 * a positive refcount, and provided that neither is locked.
620 static void file_data_consider_free(FileData *fd)
623 FileData *parent = fd->parent ? fd->parent : fd;
625 g_assert(fd->magick == FD_MAGICK);
626 if (file_data_check_has_ref(fd)) return;
627 if (file_data_check_has_ref(parent)) return;
629 work = parent->sidecar_files;
632 FileData *sfd = work->data;
633 if (file_data_check_has_ref(sfd)) return;
637 /* Neither the parent nor the siblings are referenced, so we can free everything */
638 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
639 fd->path, fd->parent ? parent->path : "-");
641 work = parent->sidecar_files;
644 FileData *sfd = work->data;
649 g_list_free(parent->sidecar_files);
650 parent->sidecar_files = NULL;
652 file_data_free(parent);
655 #ifdef DEBUG_FILEDATA
656 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
658 void file_data_unref(FileData *fd)
661 if (fd == NULL) return;
662 if (fd->magick != FD_MAGICK)
663 #ifdef DEBUG_FILEDATA
664 DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
666 DEBUG_0("fd magick mismatch fd=%p", fd);
668 g_assert(fd->magick == FD_MAGICK);
671 #ifdef DEBUG_FILEDATA
672 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", fd, fd->ref, fd->locked, fd->path,
675 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
678 // Free FileData if it's no longer ref'd
679 file_data_consider_free(fd);
683 * \brief Lock the FileData in memory.
685 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
686 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
687 * even if the code would continue to function properly even if the FileData were freed. Code that
688 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
690 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
691 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
693 void file_data_lock(FileData *fd)
695 if (fd == NULL) return;
696 if (fd->magick != FD_MAGICK) DEBUG_0("fd magick mismatch fd=%p", fd);
698 g_assert(fd->magick == FD_MAGICK);
701 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
705 * \brief Reset the maintain-FileData-in-memory lock
707 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
708 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
709 * keeping it from being freed.
711 void file_data_unlock(FileData *fd)
713 if (fd == NULL) return;
714 if (fd->magick != FD_MAGICK) DEBUG_0("fd magick mismatch fd=%p", fd);
716 g_assert(fd->magick == FD_MAGICK);
719 // Free FileData if it's no longer ref'd
720 file_data_consider_free(fd);
724 * \brief Lock all of the FileDatas in the provided list
726 * \see file_data_lock(FileData)
728 void file_data_lock_list(GList *list)
735 FileData *fd = work->data;
742 * \brief Unlock all of the FileDatas in the provided list
744 * \see file_data_unlock(FileData)
746 void file_data_unlock_list(GList *list)
753 FileData *fd = work->data;
755 file_data_unlock(fd);
760 *-----------------------------------------------------------------------------
761 * sidecar file info struct
762 *-----------------------------------------------------------------------------
765 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
767 const FileData *fda = a;
768 const FileData *fdb = b;
770 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
771 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
773 return strcmp(fdb->extension, fda->extension);
777 static gint sidecar_file_priority(const gchar *extension)
782 if (extension == NULL)
785 work = sidecar_ext_get_list();
788 gchar *ext = work->data;
791 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
797 static void file_data_check_sidecars(const GList *basename_list)
799 /* basename_list contains the new group - first is the parent, then sorted sidecars */
800 /* all files in the list have ref count > 0 */
803 GList *s_work, *new_sidecars;
806 if (!basename_list) return;
809 DEBUG_2("basename start");
810 work = basename_list;
813 FileData *fd = work->data;
815 g_assert(fd->magick == FD_MAGICK);
816 DEBUG_2("basename: %p %s", fd, fd->name);
819 g_assert(fd->parent->magick == FD_MAGICK);
820 DEBUG_2(" parent: %p", fd->parent);
822 s_work = fd->sidecar_files;
825 FileData *sfd = s_work->data;
826 s_work = s_work->next;
827 g_assert(sfd->magick == FD_MAGICK);
828 DEBUG_2(" sidecar: %p %s", sfd, sfd->name);
831 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
834 parent_fd = basename_list->data;
836 /* check if the second and next entries of basename_list are already connected
837 as sidecars of the first entry (parent_fd) */
838 work = basename_list->next;
839 s_work = parent_fd->sidecar_files;
841 while (work && s_work)
843 if (work->data != s_work->data) break;
845 s_work = s_work->next;
848 if (!work && !s_work)
850 DEBUG_2("basename no change");
851 return; /* no change in grouping */
854 /* we have to regroup it */
856 /* first, disconnect everything and send notification*/
858 work = basename_list;
861 FileData *fd = work->data;
863 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
867 FileData *old_parent = fd->parent;
868 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
869 file_data_ref(old_parent);
870 file_data_disconnect_sidecar_file(old_parent, fd);
871 file_data_send_notification(old_parent, NOTIFY_REREAD);
872 file_data_unref(old_parent);
875 while (fd->sidecar_files)
877 FileData *sfd = fd->sidecar_files->data;
878 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
880 file_data_disconnect_sidecar_file(fd, sfd);
881 file_data_send_notification(sfd, NOTIFY_REREAD);
882 file_data_unref(sfd);
884 file_data_send_notification(fd, NOTIFY_GROUPING);
886 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
889 /* now we can form the new group */
890 work = basename_list->next;
894 FileData *sfd = work->data;
895 g_assert(sfd->magick == FD_MAGICK);
896 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
897 sfd->parent = parent_fd;
898 new_sidecars = g_list_prepend(new_sidecars, sfd);
901 g_assert(parent_fd->sidecar_files == NULL);
902 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
903 DEBUG_1("basename group changed for %s", parent_fd->path);
907 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
909 g_assert(target->magick == FD_MAGICK);
910 g_assert(sfd->magick == FD_MAGICK);
911 g_assert(g_list_find(target->sidecar_files, sfd));
913 file_data_ref(target);
916 g_assert(sfd->parent == target);
918 file_data_increment_version(sfd); /* increments both sfd and target */
920 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
922 g_free(sfd->extended_extension);
923 sfd->extended_extension = NULL;
925 file_data_unref(target);
926 file_data_unref(sfd);
929 /* disables / enables grouping for particular file, sends UPDATE notification */
930 void file_data_disable_grouping(FileData *fd, gboolean disable)
932 if (!fd->disable_grouping == !disable) return;
934 fd->disable_grouping = !!disable;
940 FileData *parent = file_data_ref(fd->parent);
941 file_data_disconnect_sidecar_file(parent, fd);
942 file_data_send_notification(parent, NOTIFY_GROUPING);
943 file_data_unref(parent);
945 else if (fd->sidecar_files)
947 GList *sidecar_files = filelist_copy(fd->sidecar_files);
948 GList *work = sidecar_files;
951 FileData *sfd = work->data;
953 file_data_disconnect_sidecar_file(fd, sfd);
954 file_data_send_notification(sfd, NOTIFY_GROUPING);
956 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
957 filelist_free(sidecar_files);
961 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
966 file_data_increment_version(fd);
967 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
969 file_data_send_notification(fd, NOTIFY_GROUPING);
972 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
979 FileData *fd = work->data;
981 file_data_disable_grouping(fd, disable);
989 *-----------------------------------------------------------------------------
991 *-----------------------------------------------------------------------------
995 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
998 if (!filelist_sort_ascend)
1005 switch (filelist_sort_method)
1010 if (fa->size < fb->size) return -1;
1011 if (fa->size > fb->size) return 1;
1012 /* fall back to name */
1015 if (fa->date < fb->date) return -1;
1016 if (fa->date > fb->date) return 1;
1017 /* fall back to name */
1020 if (fa->cdate < fb->cdate) return -1;
1021 if (fa->cdate > fb->cdate) return 1;
1022 /* fall back to name */
1025 if (fa->exifdate < fb->exifdate) return -1;
1026 if (fa->exifdate > fb->exifdate) return 1;
1027 /* fall back to name */
1029 #ifdef HAVE_STRVERSCMP
1031 ret = strverscmp(fa->name, fb->name);
1032 if (ret != 0) return ret;
1039 if (options->file_sort.case_sensitive)
1040 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1042 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1044 if (ret != 0) return ret;
1046 /* do not return 0 unless the files are really the same
1047 file_data_pool ensures that original_path is unique
1049 return strcmp(fa->original_path, fb->original_path);
1052 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1054 filelist_sort_method = method;
1055 filelist_sort_ascend = ascend;
1056 return filelist_sort_compare_filedata(fa, fb);
1059 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1061 return filelist_sort_compare_filedata(a, b);
1064 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1066 filelist_sort_method = method;
1067 filelist_sort_ascend = ascend;
1068 return g_list_sort(list, cb);
1071 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1073 filelist_sort_method = method;
1074 filelist_sort_ascend = ascend;
1075 return g_list_insert_sorted(list, data, cb);
1078 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1080 if (method == SORT_EXIFTIME)
1082 set_exif_time_data(list);
1084 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1087 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
1089 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1093 *-----------------------------------------------------------------------------
1094 * basename hash - grouping of sidecars in filelist
1095 *-----------------------------------------------------------------------------
1099 static GHashTable *file_data_basename_hash_new(void)
1101 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1104 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1107 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1109 list = g_hash_table_lookup(basename_hash, basename);
1113 DEBUG_1("TG: basename_hash not found for %s",fd->path);
1114 const gchar *parent_extension = registered_extension_from_path(basename);
1116 if (parent_extension)
1118 DEBUG_1("TG: parent extension %s",parent_extension);
1119 gchar *parent_basename = g_strndup(basename, parent_extension - basename);
1120 DEBUG_1("TG: parent basename %s",parent_basename);
1121 FileData *parent_fd = g_hash_table_lookup(file_data_pool, basename);
1124 DEBUG_1("TG: parent fd found");
1125 list = g_hash_table_lookup(basename_hash, parent_basename);
1126 if (!g_list_find(list, parent_fd))
1128 DEBUG_1("TG: parent fd doesn't fit");
1129 g_free(parent_basename);
1135 basename = parent_basename;
1136 fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
1142 if (!g_list_find(list, fd))
1144 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1145 g_hash_table_insert(basename_hash, basename, list);
1154 static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
1156 file_data_basename_hash_insert((GHashTable *)basename_hash, (FileData *)fd);
1159 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
1161 filelist_free((GList *)value);
1164 static void file_data_basename_hash_free(GHashTable *basename_hash)
1166 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1167 g_hash_table_destroy(basename_hash);
1171 *-----------------------------------------------------------------------------
1172 * handling sidecars in filelist
1173 *-----------------------------------------------------------------------------
1176 static GList *filelist_filter_out_sidecars(GList *flist)
1178 GList *work = flist;
1179 GList *flist_filtered = NULL;
1183 FileData *fd = work->data;
1186 if (fd->parent) /* remove fd's that are children */
1187 file_data_unref(fd);
1189 flist_filtered = g_list_prepend(flist_filtered, fd);
1193 return flist_filtered;
1196 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
1198 GList *basename_list = (GList *)value;
1199 file_data_check_sidecars(basename_list);
1203 static gboolean is_hidden_file(const gchar *name)
1205 if (name[0] != '.') return FALSE;
1206 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1211 *-----------------------------------------------------------------------------
1212 * the main filelist function
1213 *-----------------------------------------------------------------------------
1216 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1221 GList *dlist = NULL;
1222 GList *flist = NULL;
1223 GList *xmp_files = NULL;
1224 gint (*stat_func)(const gchar *path, struct stat *buf);
1225 GHashTable *basename_hash = NULL;
1227 g_assert(files || dirs);
1229 if (files) *files = NULL;
1230 if (dirs) *dirs = NULL;
1232 pathl = path_from_utf8(dir_path);
1233 if (!pathl) return FALSE;
1235 dp = opendir(pathl);
1242 if (files) basename_hash = file_data_basename_hash_new();
1244 if (follow_symlinks)
1249 while ((dir = readdir(dp)) != NULL)
1251 struct stat ent_sbuf;
1252 const gchar *name = dir->d_name;
1255 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1258 filepath = g_build_filename(pathl, name, NULL);
1259 if (stat_func(filepath, &ent_sbuf) >= 0)
1261 if (S_ISDIR(ent_sbuf.st_mode))
1263 /* we ignore the .thumbnails dir for cleanliness */
1265 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1266 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1267 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1268 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1270 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1275 if (files && filter_name_exists(name))
1277 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1278 flist = g_list_prepend(flist, fd);
1279 if (fd->sidecar_priority && !fd->disable_grouping)
1281 if (strcmp(fd->extension, ".xmp") != 0)
1282 file_data_basename_hash_insert(basename_hash, fd);
1284 xmp_files = g_list_append(xmp_files, fd);
1291 if (errno == EOVERFLOW)
1293 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1305 g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
1306 g_list_free(xmp_files);
1309 if (dirs) *dirs = dlist;
1313 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1315 *files = filelist_filter_out_sidecars(flist);
1317 if (basename_hash) file_data_basename_hash_free(basename_hash);
1322 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1324 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1327 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1329 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1332 FileData *file_data_new_group(const gchar *path_utf8)
1339 if (!stat_utf8(path_utf8, &st))
1345 if (S_ISDIR(st.st_mode))
1346 return file_data_new(path_utf8, &st, TRUE);
1348 dir = remove_level_from_path(path_utf8);
1350 filelist_read_real(dir, &files, NULL, TRUE);
1352 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1353 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1359 filelist_free(files);
1365 void filelist_free(GList *list)
1372 file_data_unref((FileData *)work->data);
1380 GList *filelist_copy(GList *list)
1382 GList *new_list = NULL;
1393 new_list = g_list_prepend(new_list, file_data_ref(fd));
1396 return g_list_reverse(new_list);
1399 GList *filelist_from_path_list(GList *list)
1401 GList *new_list = NULL;
1412 new_list = g_list_prepend(new_list, file_data_new_group(path));
1415 return g_list_reverse(new_list);
1418 GList *filelist_to_path_list(GList *list)
1420 GList *new_list = NULL;
1431 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1434 return g_list_reverse(new_list);
1437 GList *filelist_filter(GList *list, gboolean is_dir_list)
1441 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1446 FileData *fd = (FileData *)(work->data);
1447 const gchar *name = fd->name;
1449 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1450 (!is_dir_list && !filter_name_exists(name)) ||
1451 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1452 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1456 list = g_list_remove_link(list, link);
1457 file_data_unref(fd);
1468 *-----------------------------------------------------------------------------
1469 * filelist recursive
1470 *-----------------------------------------------------------------------------
1473 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1475 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1478 GList *filelist_sort_path(GList *list)
1480 return g_list_sort(list, filelist_sort_path_cb);
1483 static void filelist_recursive_append(GList **list, GList *dirs)
1490 FileData *fd = (FileData *)(work->data);
1494 if (filelist_read(fd, &f, &d))
1496 f = filelist_filter(f, FALSE);
1497 f = filelist_sort_path(f);
1498 *list = g_list_concat(*list, f);
1500 d = filelist_filter(d, TRUE);
1501 d = filelist_sort_path(d);
1502 filelist_recursive_append(list, d);
1510 GList *filelist_recursive(FileData *dir_fd)
1515 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1516 list = filelist_filter(list, FALSE);
1517 list = filelist_sort_path(list);
1519 d = filelist_filter(d, TRUE);
1520 d = filelist_sort_path(d);
1521 filelist_recursive_append(&list, d);
1528 *-----------------------------------------------------------------------------
1529 * file modification support
1530 *-----------------------------------------------------------------------------
1534 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1536 if (!fdci && fd) fdci = fd->change;
1540 g_free(fdci->source);
1545 if (fd) fd->change = NULL;
1548 static gboolean file_data_can_write_directly(FileData *fd)
1550 return filter_name_is_writable(fd->extension);
1553 static gboolean file_data_can_write_sidecar(FileData *fd)
1555 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1558 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1560 gchar *sidecar_path = NULL;
1563 if (!file_data_can_write_sidecar(fd)) return NULL;
1565 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1566 gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1569 FileData *sfd = work->data;
1571 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1573 sidecar_path = g_strdup(sfd->path);
1577 g_free(extended_extension);
1579 if (!existing_only && !sidecar_path)
1581 if (options->metadata.sidecar_extended_name)
1582 sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1585 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1586 sidecar_path = g_strconcat(base, ".xmp", NULL);
1591 return sidecar_path;
1595 * marks and orientation
1598 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1599 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1600 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1601 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1603 gboolean file_data_get_mark(FileData *fd, gint n)
1605 gboolean valid = (fd->valid_marks & (1 << n));
1607 if (file_data_get_mark_func[n] && !valid)
1609 guint old = fd->marks;
1610 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1612 if (!value != !(fd->marks & (1 << n)))
1614 fd->marks = fd->marks ^ (1 << n);
1617 fd->valid_marks |= (1 << n);
1618 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1620 file_data_unref(fd);
1622 else if (!old && fd->marks)
1628 return !!(fd->marks & (1 << n));
1631 guint file_data_get_marks(FileData *fd)
1634 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1638 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1641 if (!value == !file_data_get_mark(fd, n)) return;
1643 if (file_data_set_mark_func[n])
1645 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1650 fd->marks = fd->marks ^ (1 << n);
1652 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1654 file_data_unref(fd);
1656 else if (!old && fd->marks)
1661 file_data_increment_version(fd);
1662 file_data_send_notification(fd, NOTIFY_MARKS);
1665 gboolean file_data_filter_marks(FileData *fd, guint filter)
1668 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1669 return ((fd->marks & filter) == filter);
1672 GList *file_data_filter_marks_list(GList *list, guint filter)
1679 FileData *fd = work->data;
1683 if (!file_data_filter_marks(fd, filter))
1685 list = g_list_remove_link(list, link);
1686 file_data_unref(fd);
1694 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1696 FileData *fd = value;
1697 file_data_increment_version(fd);
1698 file_data_send_notification(fd, NOTIFY_MARKS);
1701 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1703 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1705 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1707 file_data_get_mark_func[n] = get_mark_func;
1708 file_data_set_mark_func[n] = set_mark_func;
1709 file_data_mark_func_data[n] = data;
1710 file_data_destroy_mark_func[n] = notify;
1714 /* this effectively changes all known files */
1715 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1721 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1723 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1724 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1725 if (data) *data = file_data_mark_func_data[n];
1728 gint file_data_get_user_orientation(FileData *fd)
1730 return fd->user_orientation;
1733 void file_data_set_user_orientation(FileData *fd, gint value)
1735 if (fd->user_orientation == value) return;
1737 fd->user_orientation = value;
1738 file_data_increment_version(fd);
1739 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1744 * file_data - operates on the given fd
1745 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1749 /* return list of sidecar file extensions in a string */
1750 gchar *file_data_sc_list_to_string(FileData *fd)
1753 GString *result = g_string_new("");
1755 work = fd->sidecar_files;
1758 FileData *sfd = work->data;
1760 result = g_string_append(result, "+ ");
1761 result = g_string_append(result, sfd->extension);
1763 if (work) result = g_string_append_c(result, ' ');
1766 return g_string_free(result, FALSE);
1772 * add FileDataChangeInfo (see typedefs.h) for the given operation
1773 * uses file_data_add_change_info
1775 * fails if the fd->change already exists - change operations can't run in parallel
1776 * fd->change_info works as a lock
1778 * dest can be NULL - in this case the current name is used for now, it will
1783 FileDataChangeInfo types:
1785 MOVE - path is changed, name may be changed too
1786 RENAME - path remains unchanged, name is changed
1787 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1788 sidecar names are changed too, extensions are not changed
1790 UPDATE - file size, date or grouping has been changed
1793 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1795 FileDataChangeInfo *fdci;
1797 if (fd->change) return FALSE;
1799 fdci = g_new0(FileDataChangeInfo, 1);
1804 fdci->source = g_strdup(src);
1806 fdci->source = g_strdup(fd->path);
1809 fdci->dest = g_strdup(dest);
1816 static void file_data_planned_change_remove(FileData *fd)
1818 if (file_data_planned_change_hash &&
1819 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1821 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1823 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1824 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1825 file_data_unref(fd);
1826 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1828 g_hash_table_destroy(file_data_planned_change_hash);
1829 file_data_planned_change_hash = NULL;
1830 DEBUG_1("planned change: empty");
1837 void file_data_free_ci(FileData *fd)
1839 FileDataChangeInfo *fdci = fd->change;
1843 file_data_planned_change_remove(fd);
1845 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1847 g_free(fdci->source);
1855 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1857 FileDataChangeInfo *fdci = fd->change;
1859 fdci->regroup_when_finished = enable;
1862 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1866 if (fd->parent) fd = fd->parent;
1868 if (fd->change) return FALSE;
1870 work = fd->sidecar_files;
1873 FileData *sfd = work->data;
1875 if (sfd->change) return FALSE;
1879 file_data_add_ci(fd, type, NULL, NULL);
1881 work = fd->sidecar_files;
1884 FileData *sfd = work->data;
1886 file_data_add_ci(sfd, type, NULL, NULL);
1893 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1897 if (fd->parent) fd = fd->parent;
1899 if (!fd->change || fd->change->type != type) return FALSE;
1901 work = fd->sidecar_files;
1904 FileData *sfd = work->data;
1906 if (!sfd->change || sfd->change->type != type) return FALSE;
1914 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1916 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1917 file_data_sc_update_ci_copy(fd, dest_path);
1921 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1923 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1924 file_data_sc_update_ci_move(fd, dest_path);
1928 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1930 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1931 file_data_sc_update_ci_rename(fd, dest_path);
1935 gboolean file_data_sc_add_ci_delete(FileData *fd)
1937 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1940 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1942 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1943 file_data_sc_update_ci_unspecified(fd, dest_path);
1947 gboolean file_data_add_ci_write_metadata(FileData *fd)
1949 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1952 void file_data_sc_free_ci(FileData *fd)
1956 if (fd->parent) fd = fd->parent;
1958 file_data_free_ci(fd);
1960 work = fd->sidecar_files;
1963 FileData *sfd = work->data;
1965 file_data_free_ci(sfd);
1970 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1973 gboolean ret = TRUE;
1978 FileData *fd = work->data;
1980 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1987 static void file_data_sc_revert_ci_list(GList *fd_list)
1994 FileData *fd = work->data;
1996 file_data_sc_free_ci(fd);
2001 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2008 FileData *fd = work->data;
2010 if (!func(fd, dest))
2012 file_data_sc_revert_ci_list(work->prev);
2021 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2023 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2026 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2028 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2031 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2033 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2036 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2038 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2041 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2044 gboolean ret = TRUE;
2049 FileData *fd = work->data;
2051 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2058 void file_data_free_ci_list(GList *fd_list)
2065 FileData *fd = work->data;
2067 file_data_free_ci(fd);
2072 void file_data_sc_free_ci_list(GList *fd_list)
2079 FileData *fd = work->data;
2081 file_data_sc_free_ci(fd);
2087 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2088 * fails if fd->change does not exist or the change type does not match
2091 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2093 FileDataChangeType type = fd->change->type;
2095 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2099 if (!file_data_planned_change_hash)
2100 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2102 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2104 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2105 g_hash_table_remove(file_data_planned_change_hash, old_path);
2106 file_data_unref(fd);
2109 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
2114 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2115 g_hash_table_remove(file_data_planned_change_hash, new_path);
2116 file_data_unref(ofd);
2119 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2121 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2126 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2128 gchar *old_path = fd->change->dest;
2130 fd->change->dest = g_strdup(dest_path);
2131 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2135 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2137 const gchar *extension = registered_extension_from_path(fd->change->source);
2138 gchar *base = remove_extension_from_path(dest_path);
2139 gchar *old_path = fd->change->dest;
2141 fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2142 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2148 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2151 gchar *dest_path_full = NULL;
2153 if (fd->parent) fd = fd->parent;
2157 dest_path = fd->path;
2159 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2161 gchar *dir = remove_level_from_path(fd->path);
2163 dest_path_full = g_build_filename(dir, dest_path, NULL);
2165 dest_path = dest_path_full;
2167 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2169 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2170 dest_path = dest_path_full;
2173 file_data_update_ci_dest(fd, dest_path);
2175 work = fd->sidecar_files;
2178 FileData *sfd = work->data;
2180 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2184 g_free(dest_path_full);
2187 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2189 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2190 file_data_sc_update_ci(fd, dest_path);
2194 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2196 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2199 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2201 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2204 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2206 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2209 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2211 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2214 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2216 gboolean (*func)(FileData *, const gchar *))
2219 gboolean ret = TRUE;
2224 FileData *fd = work->data;
2226 if (!func(fd, dest)) ret = FALSE;
2233 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2235 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2238 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2240 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2243 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2245 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2250 * verify source and dest paths - dest image exists, etc.
2251 * it should detect all possible problems with the planned operation
2254 gint file_data_verify_ci(FileData *fd, GList *list)
2256 gint ret = CHANGE_OK;
2259 FileData *fd1 = NULL;
2263 DEBUG_1("Change checked: no change info: %s", fd->path);
2267 if (!isname(fd->path))
2269 /* this probably should not happen */
2270 ret |= CHANGE_NO_SRC;
2271 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2275 dir = remove_level_from_path(fd->path);
2277 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2278 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2279 fd->change->type != FILEDATA_CHANGE_RENAME &&
2280 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2283 ret |= CHANGE_WARN_UNSAVED_META;
2284 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2287 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2288 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2289 !access_file(fd->path, R_OK))
2291 ret |= CHANGE_NO_READ_PERM;
2292 DEBUG_1("Change checked: no read permission: %s", fd->path);
2294 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2295 !access_file(dir, W_OK))
2297 ret |= CHANGE_NO_WRITE_PERM_DIR;
2298 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2300 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2301 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2302 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2303 !access_file(fd->path, W_OK))
2305 ret |= CHANGE_WARN_NO_WRITE_PERM;
2306 DEBUG_1("Change checked: no write permission: %s", fd->path);
2308 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2309 - that means that there are no hard errors and warnings can be disabled
2310 - the destination is determined during the check
2312 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2314 /* determine destination file */
2315 gboolean have_dest = FALSE;
2316 gchar *dest_dir = NULL;
2318 if (options->metadata.save_in_image_file)
2320 if (file_data_can_write_directly(fd))
2322 /* we can write the file directly */
2323 if (access_file(fd->path, W_OK))
2329 if (options->metadata.warn_on_write_problems)
2331 ret |= CHANGE_WARN_NO_WRITE_PERM;
2332 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2336 else if (file_data_can_write_sidecar(fd))
2338 /* we can write sidecar */
2339 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2340 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2342 file_data_update_ci_dest(fd, sidecar);
2347 if (options->metadata.warn_on_write_problems)
2349 ret |= CHANGE_WARN_NO_WRITE_PERM;
2350 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2359 /* write private metadata file under ~/.geeqie */
2361 /* If an existing metadata file exists, we will try writing to
2362 * it's location regardless of the user's preference.
2364 gchar *metadata_path = NULL;
2366 /* but ignore XMP if we are not able to write it */
2367 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2369 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2371 if (metadata_path && !access_file(metadata_path, W_OK))
2373 g_free(metadata_path);
2374 metadata_path = NULL;
2381 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2382 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2384 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2386 metadata_path = g_build_filename(dest_dir, filename, NULL);
2390 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2392 file_data_update_ci_dest(fd, metadata_path);
2397 ret |= CHANGE_NO_WRITE_PERM_DEST;
2398 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2400 g_free(metadata_path);
2405 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2410 same = (strcmp(fd->path, fd->change->dest) == 0);
2414 const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2415 if (!dest_ext) dest_ext = "";
2416 if (!options->file_filter.disable_file_extension_checks)
2418 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2420 ret |= CHANGE_WARN_CHANGED_EXT;
2421 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2427 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2429 ret |= CHANGE_WARN_SAME;
2430 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2434 dest_dir = remove_level_from_path(fd->change->dest);
2436 if (!isdir(dest_dir))
2438 ret |= CHANGE_NO_DEST_DIR;
2439 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2441 else if (!access_file(dest_dir, W_OK))
2443 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2444 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2448 if (isfile(fd->change->dest))
2450 if (!access_file(fd->change->dest, W_OK))
2452 ret |= CHANGE_NO_WRITE_PERM_DEST;
2453 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2457 ret |= CHANGE_WARN_DEST_EXISTS;
2458 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2461 else if (isdir(fd->change->dest))
2463 ret |= CHANGE_DEST_EXISTS;
2464 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2471 /* During a rename operation, check if another planned destination file has
2474 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2475 fd->change->type == FILEDATA_CHANGE_COPY ||
2476 fd->change->type == FILEDATA_CHANGE_MOVE)
2483 if (fd1 != NULL && fd != fd1 )
2485 if (!strcmp(fd->change->dest, fd1->change->dest))
2487 ret |= CHANGE_DUPLICATE_DEST;
2493 fd->change->error = ret;
2494 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2501 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2506 ret = file_data_verify_ci(fd, list);
2508 work = fd->sidecar_files;
2511 FileData *sfd = work->data;
2513 ret |= file_data_verify_ci(sfd, list);
2520 gchar *file_data_get_error_string(gint error)
2522 GString *result = g_string_new("");
2524 if (error & CHANGE_NO_SRC)
2526 if (result->len > 0) g_string_append(result, ", ");
2527 g_string_append(result, _("file or directory does not exist"));
2530 if (error & CHANGE_DEST_EXISTS)
2532 if (result->len > 0) g_string_append(result, ", ");
2533 g_string_append(result, _("destination already exists"));
2536 if (error & CHANGE_NO_WRITE_PERM_DEST)
2538 if (result->len > 0) g_string_append(result, ", ");
2539 g_string_append(result, _("destination can't be overwritten"));
2542 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2544 if (result->len > 0) g_string_append(result, ", ");
2545 g_string_append(result, _("destination directory is not writable"));
2548 if (error & CHANGE_NO_DEST_DIR)
2550 if (result->len > 0) g_string_append(result, ", ");
2551 g_string_append(result, _("destination directory does not exist"));
2554 if (error & CHANGE_NO_WRITE_PERM_DIR)
2556 if (result->len > 0) g_string_append(result, ", ");
2557 g_string_append(result, _("source directory is not writable"));
2560 if (error & CHANGE_NO_READ_PERM)
2562 if (result->len > 0) g_string_append(result, ", ");
2563 g_string_append(result, _("no read permission"));
2566 if (error & CHANGE_WARN_NO_WRITE_PERM)
2568 if (result->len > 0) g_string_append(result, ", ");
2569 g_string_append(result, _("file is readonly"));
2572 if (error & CHANGE_WARN_DEST_EXISTS)
2574 if (result->len > 0) g_string_append(result, ", ");
2575 g_string_append(result, _("destination already exists and will be overwritten"));
2578 if (error & CHANGE_WARN_SAME)
2580 if (result->len > 0) g_string_append(result, ", ");
2581 g_string_append(result, _("source and destination are the same"));
2584 if (error & CHANGE_WARN_CHANGED_EXT)
2586 if (result->len > 0) g_string_append(result, ", ");
2587 g_string_append(result, _("source and destination have different extension"));
2590 if (error & CHANGE_WARN_UNSAVED_META)
2592 if (result->len > 0) g_string_append(result, ", ");
2593 g_string_append(result, _("there are unsaved metadata changes for the file"));
2596 if (error & CHANGE_DUPLICATE_DEST)
2598 if (result->len > 0) g_string_append(result, ", ");
2599 g_string_append(result, _("another destination file has the same filename"));
2602 return g_string_free(result, FALSE);
2605 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2608 gint all_errors = 0;
2609 gint common_errors = ~0;
2614 if (!list) return 0;
2616 num = g_list_length(list);
2617 errors = g_new(int, num);
2628 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2629 all_errors |= error;
2630 common_errors &= error;
2637 if (desc && all_errors)
2640 GString *result = g_string_new("");
2644 gchar *str = file_data_get_error_string(common_errors);
2645 g_string_append(result, str);
2646 g_string_append(result, "\n");
2660 error = errors[i] & ~common_errors;
2664 gchar *str = file_data_get_error_string(error);
2665 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2670 *desc = g_string_free(result, FALSE);
2679 * perform the change described by FileFataChangeInfo
2680 * it is used for internal operations,
2681 * this function actually operates with files on the filesystem
2682 * it should implement safe delete
2685 static gboolean file_data_perform_move(FileData *fd)
2687 g_assert(!strcmp(fd->change->source, fd->path));
2688 return move_file(fd->change->source, fd->change->dest);
2691 static gboolean file_data_perform_copy(FileData *fd)
2693 g_assert(!strcmp(fd->change->source, fd->path));
2694 return copy_file(fd->change->source, fd->change->dest);
2697 static gboolean file_data_perform_delete(FileData *fd)
2699 if (isdir(fd->path) && !islink(fd->path))
2700 return rmdir_utf8(fd->path);
2702 if (options->file_ops.safe_delete_enable)
2703 return file_util_safe_unlink(fd->path);
2705 return unlink_file(fd->path);
2708 gboolean file_data_perform_ci(FileData *fd)
2710 FileDataChangeType type = fd->change->type;
2714 case FILEDATA_CHANGE_MOVE:
2715 return file_data_perform_move(fd);
2716 case FILEDATA_CHANGE_COPY:
2717 return file_data_perform_copy(fd);
2718 case FILEDATA_CHANGE_RENAME:
2719 return file_data_perform_move(fd); /* the same as move */
2720 case FILEDATA_CHANGE_DELETE:
2721 return file_data_perform_delete(fd);
2722 case FILEDATA_CHANGE_WRITE_METADATA:
2723 return metadata_write_perform(fd);
2724 case FILEDATA_CHANGE_UNSPECIFIED:
2725 /* nothing to do here */
2733 gboolean file_data_sc_perform_ci(FileData *fd)
2736 gboolean ret = TRUE;
2737 FileDataChangeType type = fd->change->type;
2739 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2741 work = fd->sidecar_files;
2744 FileData *sfd = work->data;
2746 if (!file_data_perform_ci(sfd)) ret = FALSE;
2750 if (!file_data_perform_ci(fd)) ret = FALSE;
2756 * updates FileData structure according to FileDataChangeInfo
2759 gboolean file_data_apply_ci(FileData *fd)
2761 FileDataChangeType type = fd->change->type;
2764 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2766 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2767 file_data_planned_change_remove(fd);
2769 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2771 /* this change overwrites another file which is already known to other modules
2772 renaming fd would create duplicate FileData structure
2773 the best thing we can do is nothing
2774 FIXME: maybe we could copy stuff like marks
2776 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2780 file_data_set_path(fd, fd->change->dest);
2783 file_data_increment_version(fd);
2784 file_data_send_notification(fd, NOTIFY_CHANGE);
2789 gboolean file_data_sc_apply_ci(FileData *fd)
2792 FileDataChangeType type = fd->change->type;
2794 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2796 work = fd->sidecar_files;
2799 FileData *sfd = work->data;
2801 file_data_apply_ci(sfd);
2805 file_data_apply_ci(fd);
2810 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2813 if (fd->parent) fd = fd->parent;
2814 if (!g_list_find(list, fd)) return FALSE;
2816 work = fd->sidecar_files;
2819 if (!g_list_find(list, work->data)) return FALSE;
2825 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2830 /* change partial groups to independent files */
2835 FileData *fd = work->data;
2838 if (!file_data_list_contains_whole_group(list, fd))
2840 file_data_disable_grouping(fd, TRUE);
2843 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2849 /* remove sidecars from the list,
2850 they can be still acessed via main_fd->sidecar_files */
2854 FileData *fd = work->data;
2858 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2860 out = g_list_prepend(out, file_data_ref(fd));
2864 filelist_free(list);
2865 out = g_list_reverse(out);
2875 * notify other modules about the change described by FileDataChangeInfo
2878 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2879 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2880 implementation in view_file_list.c */
2883 typedef struct _NotifyIdleData NotifyIdleData;
2885 struct _NotifyIdleData {
2891 typedef struct _NotifyData NotifyData;
2893 struct _NotifyData {
2894 FileDataNotifyFunc func;
2896 NotifyPriority priority;
2899 static GList *notify_func_list = NULL;
2901 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2903 NotifyData *nda = (NotifyData *)a;
2904 NotifyData *ndb = (NotifyData *)b;
2906 if (nda->priority < ndb->priority) return -1;
2907 if (nda->priority > ndb->priority) return 1;
2911 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2914 GList *work = notify_func_list;
2918 NotifyData *nd = (NotifyData *)work->data;
2920 if (nd->func == func && nd->data == data)
2922 g_warning("Notify func already registered");
2928 nd = g_new(NotifyData, 1);
2931 nd->priority = priority;
2933 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2934 DEBUG_2("Notify func registered: %p", nd);
2939 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2941 GList *work = notify_func_list;
2945 NotifyData *nd = (NotifyData *)work->data;
2947 if (nd->func == func && nd->data == data)
2949 notify_func_list = g_list_delete_link(notify_func_list, work);
2951 DEBUG_2("Notify func unregistered: %p", nd);
2957 g_warning("Notify func not found");
2962 gboolean file_data_send_notification_idle_cb(gpointer data)
2964 NotifyIdleData *nid = (NotifyIdleData *)data;
2965 GList *work = notify_func_list;
2969 NotifyData *nd = (NotifyData *)work->data;
2971 nd->func(nid->fd, nid->type, nd->data);
2974 file_data_unref(nid->fd);
2979 void file_data_send_notification(FileData *fd, NotifyType type)
2981 GList *work = notify_func_list;
2985 NotifyData *nd = (NotifyData *)work->data;
2987 nd->func(fd, type, nd->data);
2991 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
2992 nid->fd = file_data_ref(fd);
2994 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
2998 static GHashTable *file_data_monitor_pool = NULL;
2999 static guint realtime_monitor_id = 0; /* event source id */
3001 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
3005 file_data_check_changed_files(fd);
3007 DEBUG_1("monitor %s", fd->path);
3010 static gboolean realtime_monitor_cb(gpointer data)
3012 if (!options->update_on_time_change) return TRUE;
3013 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
3017 gboolean file_data_register_real_time_monitor(FileData *fd)
3023 if (!file_data_monitor_pool)
3024 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3026 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3028 DEBUG_1("Register realtime %d %s", count, fd->path);
3031 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3033 if (!realtime_monitor_id)
3035 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
3041 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3045 g_assert(file_data_monitor_pool);
3047 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3049 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3051 g_assert(count > 0);
3056 g_hash_table_remove(file_data_monitor_pool, fd);
3058 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3060 file_data_unref(fd);
3062 if (g_hash_table_size(file_data_monitor_pool) == 0)
3064 g_source_remove(realtime_monitor_id);
3065 realtime_monitor_id = 0;
3071 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */