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;
433 if (disable_sidecars) fd->disable_grouping = TRUE;
435 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
440 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
442 gchar *path_utf8 = path_to_utf8(path);
443 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
449 FileData *file_data_new_simple(const gchar *path_utf8)
454 if (!stat_utf8(path_utf8, &st))
460 fd = g_hash_table_lookup(file_data_pool, path_utf8);
461 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
470 void read_exif_time_data(FileData *file)
472 if (file->exifdate > 0)
474 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
478 file->exif = exif_read_fd(file);
482 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
483 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), file, file->path);
488 uint year, month, day, hour, min, sec;
490 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
491 time_str.tm_year = year - 1900;
492 time_str.tm_mon = month - 1;
493 time_str.tm_mday = day;
494 time_str.tm_hour = hour;
495 time_str.tm_min = min;
496 time_str.tm_sec = sec;
497 time_str.tm_isdst = 0;
499 file->exifdate = mktime(&time_str);
505 void set_exif_time_data(GList *files)
507 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
511 FileData *file = files->data;
513 read_exif_time_data(file);
518 void set_rating_data(GList *files)
521 DEBUG_1("%s set_rating_data: ...", get_exec_time());
525 FileData *file = files->data;
526 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
529 file->rating = atoi(rating_str);
536 FileData *file_data_new_no_grouping(const gchar *path_utf8)
540 if (!stat_utf8(path_utf8, &st))
546 return file_data_new(path_utf8, &st, TRUE);
549 FileData *file_data_new_dir(const gchar *path_utf8)
553 if (!stat_utf8(path_utf8, &st))
559 /* dir or non-existing yet */
560 g_assert(S_ISDIR(st.st_mode));
562 return file_data_new(path_utf8, &st, TRUE);
566 *-----------------------------------------------------------------------------
568 *-----------------------------------------------------------------------------
571 #ifdef DEBUG_FILEDATA
572 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
574 FileData *file_data_ref(FileData *fd)
577 if (fd == NULL) return NULL;
578 if (fd->magick != FD_MAGICK)
579 #ifdef DEBUG_FILEDATA
580 DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
582 DEBUG_0("fd magick mismatch fd=%p", fd);
584 g_assert(fd->magick == FD_MAGICK);
587 #ifdef DEBUG_FILEDATA
588 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", fd, fd->ref, fd->path, file, line);
590 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
595 static void file_data_free(FileData *fd)
597 g_assert(fd->magick == FD_MAGICK);
598 g_assert(fd->ref == 0);
599 g_assert(!fd->locked);
601 #ifdef DEBUG_FILEDATA
602 global_file_data_count--;
603 DEBUG_2("file data count--: %d", global_file_data_count);
606 metadata_cache_free(fd);
607 g_hash_table_remove(file_data_pool, fd->original_path);
610 g_free(fd->original_path);
611 g_free(fd->collate_key_name);
612 g_free(fd->collate_key_name_nocase);
613 g_free(fd->extended_extension);
614 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
615 histmap_free(fd->histmap);
617 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
619 file_data_change_info_free(NULL, fd);
624 * \brief Checks if the FileData is referenced
626 * Checks the refcount and whether the FileData is locked.
628 static gboolean file_data_check_has_ref(FileData *fd)
630 return fd->ref > 0 || fd->locked;
634 * \brief Consider freeing a FileData.
636 * This function will free a FileData and its children provided that neither its parent nor it has
637 * a positive refcount, and provided that neither is locked.
639 static void file_data_consider_free(FileData *fd)
642 FileData *parent = fd->parent ? fd->parent : fd;
644 g_assert(fd->magick == FD_MAGICK);
645 if (file_data_check_has_ref(fd)) return;
646 if (file_data_check_has_ref(parent)) return;
648 work = parent->sidecar_files;
651 FileData *sfd = work->data;
652 if (file_data_check_has_ref(sfd)) return;
656 /* Neither the parent nor the siblings are referenced, so we can free everything */
657 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
658 fd->path, fd->parent ? parent->path : "-");
660 work = parent->sidecar_files;
663 FileData *sfd = work->data;
668 g_list_free(parent->sidecar_files);
669 parent->sidecar_files = NULL;
671 file_data_free(parent);
674 #ifdef DEBUG_FILEDATA
675 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
677 void file_data_unref(FileData *fd)
680 if (fd == NULL) return;
681 if (fd->magick != FD_MAGICK)
682 #ifdef DEBUG_FILEDATA
683 DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
685 DEBUG_0("fd magick mismatch fd=%p", fd);
687 g_assert(fd->magick == FD_MAGICK);
690 #ifdef DEBUG_FILEDATA
691 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", fd, fd->ref, fd->locked, fd->path,
694 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
697 // Free FileData if it's no longer ref'd
698 file_data_consider_free(fd);
702 * \brief Lock the FileData in memory.
704 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
705 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
706 * even if the code would continue to function properly even if the FileData were freed. Code that
707 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
709 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
710 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
712 void file_data_lock(FileData *fd)
714 if (fd == NULL) return;
715 if (fd->magick != FD_MAGICK) DEBUG_0("fd magick mismatch fd=%p", fd);
717 g_assert(fd->magick == FD_MAGICK);
720 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
724 * \brief Reset the maintain-FileData-in-memory lock
726 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
727 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
728 * keeping it from being freed.
730 void file_data_unlock(FileData *fd)
732 if (fd == NULL) return;
733 if (fd->magick != FD_MAGICK) DEBUG_0("fd magick mismatch fd=%p", fd);
735 g_assert(fd->magick == FD_MAGICK);
738 // Free FileData if it's no longer ref'd
739 file_data_consider_free(fd);
743 * \brief Lock all of the FileDatas in the provided list
745 * \see file_data_lock(FileData)
747 void file_data_lock_list(GList *list)
754 FileData *fd = work->data;
761 * \brief Unlock all of the FileDatas in the provided list
763 * \see file_data_unlock(FileData)
765 void file_data_unlock_list(GList *list)
772 FileData *fd = work->data;
774 file_data_unlock(fd);
779 *-----------------------------------------------------------------------------
780 * sidecar file info struct
781 *-----------------------------------------------------------------------------
784 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
786 const FileData *fda = a;
787 const FileData *fdb = b;
789 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
790 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
792 return strcmp(fdb->extension, fda->extension);
796 static gint sidecar_file_priority(const gchar *extension)
801 if (extension == NULL)
804 work = sidecar_ext_get_list();
807 gchar *ext = work->data;
810 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
816 static void file_data_check_sidecars(const GList *basename_list)
818 /* basename_list contains the new group - first is the parent, then sorted sidecars */
819 /* all files in the list have ref count > 0 */
822 GList *s_work, *new_sidecars;
825 if (!basename_list) return;
828 DEBUG_2("basename start");
829 work = basename_list;
832 FileData *fd = work->data;
834 g_assert(fd->magick == FD_MAGICK);
835 DEBUG_2("basename: %p %s", fd, fd->name);
838 g_assert(fd->parent->magick == FD_MAGICK);
839 DEBUG_2(" parent: %p", fd->parent);
841 s_work = fd->sidecar_files;
844 FileData *sfd = s_work->data;
845 s_work = s_work->next;
846 g_assert(sfd->magick == FD_MAGICK);
847 DEBUG_2(" sidecar: %p %s", sfd, sfd->name);
850 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
853 parent_fd = basename_list->data;
855 /* check if the second and next entries of basename_list are already connected
856 as sidecars of the first entry (parent_fd) */
857 work = basename_list->next;
858 s_work = parent_fd->sidecar_files;
860 while (work && s_work)
862 if (work->data != s_work->data) break;
864 s_work = s_work->next;
867 if (!work && !s_work)
869 DEBUG_2("basename no change");
870 return; /* no change in grouping */
873 /* we have to regroup it */
875 /* first, disconnect everything and send notification*/
877 work = basename_list;
880 FileData *fd = work->data;
882 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
886 FileData *old_parent = fd->parent;
887 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
888 file_data_ref(old_parent);
889 file_data_disconnect_sidecar_file(old_parent, fd);
890 file_data_send_notification(old_parent, NOTIFY_REREAD);
891 file_data_unref(old_parent);
894 while (fd->sidecar_files)
896 FileData *sfd = fd->sidecar_files->data;
897 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
899 file_data_disconnect_sidecar_file(fd, sfd);
900 file_data_send_notification(sfd, NOTIFY_REREAD);
901 file_data_unref(sfd);
903 file_data_send_notification(fd, NOTIFY_GROUPING);
905 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
908 /* now we can form the new group */
909 work = basename_list->next;
913 FileData *sfd = work->data;
914 g_assert(sfd->magick == FD_MAGICK);
915 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
916 sfd->parent = parent_fd;
917 new_sidecars = g_list_prepend(new_sidecars, sfd);
920 g_assert(parent_fd->sidecar_files == NULL);
921 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
922 DEBUG_1("basename group changed for %s", parent_fd->path);
926 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
928 g_assert(target->magick == FD_MAGICK);
929 g_assert(sfd->magick == FD_MAGICK);
930 g_assert(g_list_find(target->sidecar_files, sfd));
932 file_data_ref(target);
935 g_assert(sfd->parent == target);
937 file_data_increment_version(sfd); /* increments both sfd and target */
939 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
941 g_free(sfd->extended_extension);
942 sfd->extended_extension = NULL;
944 file_data_unref(target);
945 file_data_unref(sfd);
948 /* disables / enables grouping for particular file, sends UPDATE notification */
949 void file_data_disable_grouping(FileData *fd, gboolean disable)
951 if (!fd->disable_grouping == !disable) return;
953 fd->disable_grouping = !!disable;
959 FileData *parent = file_data_ref(fd->parent);
960 file_data_disconnect_sidecar_file(parent, fd);
961 file_data_send_notification(parent, NOTIFY_GROUPING);
962 file_data_unref(parent);
964 else if (fd->sidecar_files)
966 GList *sidecar_files = filelist_copy(fd->sidecar_files);
967 GList *work = sidecar_files;
970 FileData *sfd = work->data;
972 file_data_disconnect_sidecar_file(fd, sfd);
973 file_data_send_notification(sfd, NOTIFY_GROUPING);
975 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
976 filelist_free(sidecar_files);
980 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
985 file_data_increment_version(fd);
986 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
988 file_data_send_notification(fd, NOTIFY_GROUPING);
991 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
998 FileData *fd = work->data;
1000 file_data_disable_grouping(fd, disable);
1008 *-----------------------------------------------------------------------------
1010 *-----------------------------------------------------------------------------
1014 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
1017 if (!filelist_sort_ascend)
1024 switch (filelist_sort_method)
1029 if (fa->size < fb->size) return -1;
1030 if (fa->size > fb->size) return 1;
1031 /* fall back to name */
1034 if (fa->date < fb->date) return -1;
1035 if (fa->date > fb->date) return 1;
1036 /* fall back to name */
1039 if (fa->cdate < fb->cdate) return -1;
1040 if (fa->cdate > fb->cdate) return 1;
1041 /* fall back to name */
1044 if (fa->exifdate < fb->exifdate) return -1;
1045 if (fa->exifdate > fb->exifdate) return 1;
1046 /* fall back to name */
1049 if (fa->rating < fb->rating) return -1;
1050 if (fa->rating > fb->rating) return 1;
1051 /* fall back to name */
1053 #ifdef HAVE_STRVERSCMP
1055 ret = strverscmp(fa->name, fb->name);
1056 if (ret != 0) return ret;
1063 if (options->file_sort.case_sensitive)
1064 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1066 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1068 if (ret != 0) return ret;
1070 /* do not return 0 unless the files are really the same
1071 file_data_pool ensures that original_path is unique
1073 return strcmp(fa->original_path, fb->original_path);
1076 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1078 filelist_sort_method = method;
1079 filelist_sort_ascend = ascend;
1080 return filelist_sort_compare_filedata(fa, fb);
1083 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1085 return filelist_sort_compare_filedata(a, b);
1088 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1090 filelist_sort_method = method;
1091 filelist_sort_ascend = ascend;
1092 return g_list_sort(list, cb);
1095 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1097 filelist_sort_method = method;
1098 filelist_sort_ascend = ascend;
1099 return g_list_insert_sorted(list, data, cb);
1102 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1104 if (method == SORT_EXIFTIME)
1106 set_exif_time_data(list);
1108 if (method == SORT_RATING)
1110 set_rating_data(list);
1112 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1115 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
1117 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1121 *-----------------------------------------------------------------------------
1122 * basename hash - grouping of sidecars in filelist
1123 *-----------------------------------------------------------------------------
1127 static GHashTable *file_data_basename_hash_new(void)
1129 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1132 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1135 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1137 list = g_hash_table_lookup(basename_hash, basename);
1141 DEBUG_1("TG: basename_hash not found for %s",fd->path);
1142 const gchar *parent_extension = registered_extension_from_path(basename);
1144 if (parent_extension)
1146 DEBUG_1("TG: parent extension %s",parent_extension);
1147 gchar *parent_basename = g_strndup(basename, parent_extension - basename);
1148 DEBUG_1("TG: parent basename %s",parent_basename);
1149 FileData *parent_fd = g_hash_table_lookup(file_data_pool, basename);
1152 DEBUG_1("TG: parent fd found");
1153 list = g_hash_table_lookup(basename_hash, parent_basename);
1154 if (!g_list_find(list, parent_fd))
1156 DEBUG_1("TG: parent fd doesn't fit");
1157 g_free(parent_basename);
1163 basename = parent_basename;
1164 fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
1170 if (!g_list_find(list, fd))
1172 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1173 g_hash_table_insert(basename_hash, basename, list);
1182 static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
1184 file_data_basename_hash_insert((GHashTable *)basename_hash, (FileData *)fd);
1187 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
1189 filelist_free((GList *)value);
1192 static void file_data_basename_hash_free(GHashTable *basename_hash)
1194 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1195 g_hash_table_destroy(basename_hash);
1199 *-----------------------------------------------------------------------------
1200 * handling sidecars in filelist
1201 *-----------------------------------------------------------------------------
1204 static GList *filelist_filter_out_sidecars(GList *flist)
1206 GList *work = flist;
1207 GList *flist_filtered = NULL;
1211 FileData *fd = work->data;
1214 if (fd->parent) /* remove fd's that are children */
1215 file_data_unref(fd);
1217 flist_filtered = g_list_prepend(flist_filtered, fd);
1221 return flist_filtered;
1224 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
1226 GList *basename_list = (GList *)value;
1227 file_data_check_sidecars(basename_list);
1231 static gboolean is_hidden_file(const gchar *name)
1233 if (name[0] != '.') return FALSE;
1234 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1239 *-----------------------------------------------------------------------------
1240 * the main filelist function
1241 *-----------------------------------------------------------------------------
1244 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1249 GList *dlist = NULL;
1250 GList *flist = NULL;
1251 GList *xmp_files = NULL;
1252 gint (*stat_func)(const gchar *path, struct stat *buf);
1253 GHashTable *basename_hash = NULL;
1255 g_assert(files || dirs);
1257 if (files) *files = NULL;
1258 if (dirs) *dirs = NULL;
1260 pathl = path_from_utf8(dir_path);
1261 if (!pathl) return FALSE;
1263 dp = opendir(pathl);
1270 if (files) basename_hash = file_data_basename_hash_new();
1272 if (follow_symlinks)
1277 while ((dir = readdir(dp)) != NULL)
1279 struct stat ent_sbuf;
1280 const gchar *name = dir->d_name;
1283 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1286 filepath = g_build_filename(pathl, name, NULL);
1287 if (stat_func(filepath, &ent_sbuf) >= 0)
1289 if (S_ISDIR(ent_sbuf.st_mode))
1291 /* we ignore the .thumbnails dir for cleanliness */
1293 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1294 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1295 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1296 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1298 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1303 if (files && filter_name_exists(name))
1305 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1306 flist = g_list_prepend(flist, fd);
1307 if (fd->sidecar_priority && !fd->disable_grouping)
1309 if (strcmp(fd->extension, ".xmp") != 0)
1310 file_data_basename_hash_insert(basename_hash, fd);
1312 xmp_files = g_list_append(xmp_files, fd);
1319 if (errno == EOVERFLOW)
1321 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1333 g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
1334 g_list_free(xmp_files);
1337 if (dirs) *dirs = dlist;
1341 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1343 *files = filelist_filter_out_sidecars(flist);
1345 if (basename_hash) file_data_basename_hash_free(basename_hash);
1350 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1352 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1355 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1357 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1360 FileData *file_data_new_group(const gchar *path_utf8)
1367 if (!stat_utf8(path_utf8, &st))
1373 if (S_ISDIR(st.st_mode))
1374 return file_data_new(path_utf8, &st, TRUE);
1376 dir = remove_level_from_path(path_utf8);
1378 filelist_read_real(dir, &files, NULL, TRUE);
1380 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1381 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1387 filelist_free(files);
1393 void filelist_free(GList *list)
1400 file_data_unref((FileData *)work->data);
1408 GList *filelist_copy(GList *list)
1410 GList *new_list = NULL;
1421 new_list = g_list_prepend(new_list, file_data_ref(fd));
1424 return g_list_reverse(new_list);
1427 GList *filelist_from_path_list(GList *list)
1429 GList *new_list = NULL;
1440 new_list = g_list_prepend(new_list, file_data_new_group(path));
1443 return g_list_reverse(new_list);
1446 GList *filelist_to_path_list(GList *list)
1448 GList *new_list = NULL;
1459 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1462 return g_list_reverse(new_list);
1465 GList *filelist_filter(GList *list, gboolean is_dir_list)
1469 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1474 FileData *fd = (FileData *)(work->data);
1475 const gchar *name = fd->name;
1477 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1478 (!is_dir_list && !filter_name_exists(name)) ||
1479 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1480 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1484 list = g_list_remove_link(list, link);
1485 file_data_unref(fd);
1496 *-----------------------------------------------------------------------------
1497 * filelist recursive
1498 *-----------------------------------------------------------------------------
1501 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1503 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1506 GList *filelist_sort_path(GList *list)
1508 return g_list_sort(list, filelist_sort_path_cb);
1511 static void filelist_recursive_append(GList **list, GList *dirs)
1518 FileData *fd = (FileData *)(work->data);
1522 if (filelist_read(fd, &f, &d))
1524 f = filelist_filter(f, FALSE);
1525 f = filelist_sort_path(f);
1526 *list = g_list_concat(*list, f);
1528 d = filelist_filter(d, TRUE);
1529 d = filelist_sort_path(d);
1530 filelist_recursive_append(list, d);
1538 GList *filelist_recursive(FileData *dir_fd)
1543 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1544 list = filelist_filter(list, FALSE);
1545 list = filelist_sort_path(list);
1547 d = filelist_filter(d, TRUE);
1548 d = filelist_sort_path(d);
1549 filelist_recursive_append(&list, d);
1556 *-----------------------------------------------------------------------------
1557 * file modification support
1558 *-----------------------------------------------------------------------------
1562 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1564 if (!fdci && fd) fdci = fd->change;
1568 g_free(fdci->source);
1573 if (fd) fd->change = NULL;
1576 static gboolean file_data_can_write_directly(FileData *fd)
1578 return filter_name_is_writable(fd->extension);
1581 static gboolean file_data_can_write_sidecar(FileData *fd)
1583 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1586 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1588 gchar *sidecar_path = NULL;
1591 if (!file_data_can_write_sidecar(fd)) return NULL;
1593 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1594 gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1597 FileData *sfd = work->data;
1599 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1601 sidecar_path = g_strdup(sfd->path);
1605 g_free(extended_extension);
1607 if (!existing_only && !sidecar_path)
1609 if (options->metadata.sidecar_extended_name)
1610 sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1613 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1614 sidecar_path = g_strconcat(base, ".xmp", NULL);
1619 return sidecar_path;
1623 * marks and orientation
1626 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1627 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1628 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1629 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1631 gboolean file_data_get_mark(FileData *fd, gint n)
1633 gboolean valid = (fd->valid_marks & (1 << n));
1635 if (file_data_get_mark_func[n] && !valid)
1637 guint old = fd->marks;
1638 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1640 if (!value != !(fd->marks & (1 << n)))
1642 fd->marks = fd->marks ^ (1 << n);
1645 fd->valid_marks |= (1 << n);
1646 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1648 file_data_unref(fd);
1650 else if (!old && fd->marks)
1656 return !!(fd->marks & (1 << n));
1659 guint file_data_get_marks(FileData *fd)
1662 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1666 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1669 if (!value == !file_data_get_mark(fd, n)) return;
1671 if (file_data_set_mark_func[n])
1673 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1678 fd->marks = fd->marks ^ (1 << n);
1680 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1682 file_data_unref(fd);
1684 else if (!old && fd->marks)
1689 file_data_increment_version(fd);
1690 file_data_send_notification(fd, NOTIFY_MARKS);
1693 gboolean file_data_filter_marks(FileData *fd, guint filter)
1696 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1697 return ((fd->marks & filter) == filter);
1700 GList *file_data_filter_marks_list(GList *list, guint filter)
1707 FileData *fd = work->data;
1711 if (!file_data_filter_marks(fd, filter))
1713 list = g_list_remove_link(list, link);
1714 file_data_unref(fd);
1722 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1724 FileData *fd = value;
1725 file_data_increment_version(fd);
1726 file_data_send_notification(fd, NOTIFY_MARKS);
1729 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1731 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1733 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1735 file_data_get_mark_func[n] = get_mark_func;
1736 file_data_set_mark_func[n] = set_mark_func;
1737 file_data_mark_func_data[n] = data;
1738 file_data_destroy_mark_func[n] = notify;
1740 if (get_mark_func && file_data_pool)
1742 /* this effectively changes all known files */
1743 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1749 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1751 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1752 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1753 if (data) *data = file_data_mark_func_data[n];
1756 gint file_data_get_user_orientation(FileData *fd)
1758 return fd->user_orientation;
1761 void file_data_set_user_orientation(FileData *fd, gint value)
1763 if (fd->user_orientation == value) return;
1765 fd->user_orientation = value;
1766 file_data_increment_version(fd);
1767 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1772 * file_data - operates on the given fd
1773 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1777 /* return list of sidecar file extensions in a string */
1778 gchar *file_data_sc_list_to_string(FileData *fd)
1781 GString *result = g_string_new("");
1783 work = fd->sidecar_files;
1786 FileData *sfd = work->data;
1788 result = g_string_append(result, "+ ");
1789 result = g_string_append(result, sfd->extension);
1791 if (work) result = g_string_append_c(result, ' ');
1794 return g_string_free(result, FALSE);
1800 * add FileDataChangeInfo (see typedefs.h) for the given operation
1801 * uses file_data_add_change_info
1803 * fails if the fd->change already exists - change operations can't run in parallel
1804 * fd->change_info works as a lock
1806 * dest can be NULL - in this case the current name is used for now, it will
1811 FileDataChangeInfo types:
1813 MOVE - path is changed, name may be changed too
1814 RENAME - path remains unchanged, name is changed
1815 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1816 sidecar names are changed too, extensions are not changed
1818 UPDATE - file size, date or grouping has been changed
1821 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1823 FileDataChangeInfo *fdci;
1825 if (fd->change) return FALSE;
1827 fdci = g_new0(FileDataChangeInfo, 1);
1832 fdci->source = g_strdup(src);
1834 fdci->source = g_strdup(fd->path);
1837 fdci->dest = g_strdup(dest);
1844 static void file_data_planned_change_remove(FileData *fd)
1846 if (file_data_planned_change_hash &&
1847 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1849 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1851 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1852 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1853 file_data_unref(fd);
1854 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1856 g_hash_table_destroy(file_data_planned_change_hash);
1857 file_data_planned_change_hash = NULL;
1858 DEBUG_1("planned change: empty");
1865 void file_data_free_ci(FileData *fd)
1867 FileDataChangeInfo *fdci = fd->change;
1871 file_data_planned_change_remove(fd);
1873 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1875 g_free(fdci->source);
1883 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1885 FileDataChangeInfo *fdci = fd->change;
1887 fdci->regroup_when_finished = enable;
1890 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1894 if (fd->parent) fd = fd->parent;
1896 if (fd->change) return FALSE;
1898 work = fd->sidecar_files;
1901 FileData *sfd = work->data;
1903 if (sfd->change) return FALSE;
1907 file_data_add_ci(fd, type, NULL, NULL);
1909 work = fd->sidecar_files;
1912 FileData *sfd = work->data;
1914 file_data_add_ci(sfd, type, NULL, NULL);
1921 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1925 if (fd->parent) fd = fd->parent;
1927 if (!fd->change || fd->change->type != type) return FALSE;
1929 work = fd->sidecar_files;
1932 FileData *sfd = work->data;
1934 if (!sfd->change || sfd->change->type != type) return FALSE;
1942 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1944 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1945 file_data_sc_update_ci_copy(fd, dest_path);
1949 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1951 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1952 file_data_sc_update_ci_move(fd, dest_path);
1956 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1958 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1959 file_data_sc_update_ci_rename(fd, dest_path);
1963 gboolean file_data_sc_add_ci_delete(FileData *fd)
1965 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1968 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1970 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1971 file_data_sc_update_ci_unspecified(fd, dest_path);
1975 gboolean file_data_add_ci_write_metadata(FileData *fd)
1977 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1980 void file_data_sc_free_ci(FileData *fd)
1984 if (fd->parent) fd = fd->parent;
1986 file_data_free_ci(fd);
1988 work = fd->sidecar_files;
1991 FileData *sfd = work->data;
1993 file_data_free_ci(sfd);
1998 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2001 gboolean ret = TRUE;
2006 FileData *fd = work->data;
2008 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2015 static void file_data_sc_revert_ci_list(GList *fd_list)
2022 FileData *fd = work->data;
2024 file_data_sc_free_ci(fd);
2029 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2036 FileData *fd = work->data;
2038 if (!func(fd, dest))
2040 file_data_sc_revert_ci_list(work->prev);
2049 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2051 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2054 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2056 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2059 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2061 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2064 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2066 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2069 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2072 gboolean ret = TRUE;
2077 FileData *fd = work->data;
2079 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2086 void file_data_free_ci_list(GList *fd_list)
2093 FileData *fd = work->data;
2095 file_data_free_ci(fd);
2100 void file_data_sc_free_ci_list(GList *fd_list)
2107 FileData *fd = work->data;
2109 file_data_sc_free_ci(fd);
2115 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2116 * fails if fd->change does not exist or the change type does not match
2119 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2121 FileDataChangeType type = fd->change->type;
2123 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2127 if (!file_data_planned_change_hash)
2128 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2130 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2132 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2133 g_hash_table_remove(file_data_planned_change_hash, old_path);
2134 file_data_unref(fd);
2137 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
2142 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2143 g_hash_table_remove(file_data_planned_change_hash, new_path);
2144 file_data_unref(ofd);
2147 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2149 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2154 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2156 gchar *old_path = fd->change->dest;
2158 fd->change->dest = g_strdup(dest_path);
2159 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2163 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2165 const gchar *extension = registered_extension_from_path(fd->change->source);
2166 gchar *base = remove_extension_from_path(dest_path);
2167 gchar *old_path = fd->change->dest;
2169 fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2170 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2176 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2179 gchar *dest_path_full = NULL;
2181 if (fd->parent) fd = fd->parent;
2185 dest_path = fd->path;
2187 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2189 gchar *dir = remove_level_from_path(fd->path);
2191 dest_path_full = g_build_filename(dir, dest_path, NULL);
2193 dest_path = dest_path_full;
2195 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2197 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2198 dest_path = dest_path_full;
2201 file_data_update_ci_dest(fd, dest_path);
2203 work = fd->sidecar_files;
2206 FileData *sfd = work->data;
2208 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2212 g_free(dest_path_full);
2215 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2217 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2218 file_data_sc_update_ci(fd, dest_path);
2222 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2224 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2227 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2229 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2232 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2234 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2237 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2239 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2242 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2244 gboolean (*func)(FileData *, const gchar *))
2247 gboolean ret = TRUE;
2252 FileData *fd = work->data;
2254 if (!func(fd, dest)) ret = FALSE;
2261 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2263 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2266 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2268 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2271 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2273 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2278 * verify source and dest paths - dest image exists, etc.
2279 * it should detect all possible problems with the planned operation
2282 gint file_data_verify_ci(FileData *fd, GList *list)
2284 gint ret = CHANGE_OK;
2287 FileData *fd1 = NULL;
2291 DEBUG_1("Change checked: no change info: %s", fd->path);
2295 if (!isname(fd->path))
2297 /* this probably should not happen */
2298 ret |= CHANGE_NO_SRC;
2299 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2303 dir = remove_level_from_path(fd->path);
2305 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2306 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2307 fd->change->type != FILEDATA_CHANGE_RENAME &&
2308 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2311 ret |= CHANGE_WARN_UNSAVED_META;
2312 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2315 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2316 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2317 !access_file(fd->path, R_OK))
2319 ret |= CHANGE_NO_READ_PERM;
2320 DEBUG_1("Change checked: no read permission: %s", fd->path);
2322 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2323 !access_file(dir, W_OK))
2325 ret |= CHANGE_NO_WRITE_PERM_DIR;
2326 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2328 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2329 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2330 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2331 !access_file(fd->path, W_OK))
2333 ret |= CHANGE_WARN_NO_WRITE_PERM;
2334 DEBUG_1("Change checked: no write permission: %s", fd->path);
2336 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2337 - that means that there are no hard errors and warnings can be disabled
2338 - the destination is determined during the check
2340 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2342 /* determine destination file */
2343 gboolean have_dest = FALSE;
2344 gchar *dest_dir = NULL;
2346 if (options->metadata.save_in_image_file)
2348 if (file_data_can_write_directly(fd))
2350 /* we can write the file directly */
2351 if (access_file(fd->path, W_OK))
2357 if (options->metadata.warn_on_write_problems)
2359 ret |= CHANGE_WARN_NO_WRITE_PERM;
2360 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2364 else if (file_data_can_write_sidecar(fd))
2366 /* we can write sidecar */
2367 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2368 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2370 file_data_update_ci_dest(fd, sidecar);
2375 if (options->metadata.warn_on_write_problems)
2377 ret |= CHANGE_WARN_NO_WRITE_PERM;
2378 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2387 /* write private metadata file under ~/.geeqie */
2389 /* If an existing metadata file exists, we will try writing to
2390 * it's location regardless of the user's preference.
2392 gchar *metadata_path = NULL;
2394 /* but ignore XMP if we are not able to write it */
2395 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2397 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2399 if (metadata_path && !access_file(metadata_path, W_OK))
2401 g_free(metadata_path);
2402 metadata_path = NULL;
2409 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2410 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2412 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2414 metadata_path = g_build_filename(dest_dir, filename, NULL);
2418 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2420 file_data_update_ci_dest(fd, metadata_path);
2425 ret |= CHANGE_NO_WRITE_PERM_DEST;
2426 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2428 g_free(metadata_path);
2433 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2438 same = (strcmp(fd->path, fd->change->dest) == 0);
2442 const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2443 if (!dest_ext) dest_ext = "";
2444 if (!options->file_filter.disable_file_extension_checks)
2446 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2448 ret |= CHANGE_WARN_CHANGED_EXT;
2449 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2455 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2457 ret |= CHANGE_WARN_SAME;
2458 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2462 dest_dir = remove_level_from_path(fd->change->dest);
2464 if (!isdir(dest_dir))
2466 ret |= CHANGE_NO_DEST_DIR;
2467 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2469 else if (!access_file(dest_dir, W_OK))
2471 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2472 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2476 if (isfile(fd->change->dest))
2478 if (!access_file(fd->change->dest, W_OK))
2480 ret |= CHANGE_NO_WRITE_PERM_DEST;
2481 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2485 ret |= CHANGE_WARN_DEST_EXISTS;
2486 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2489 else if (isdir(fd->change->dest))
2491 ret |= CHANGE_DEST_EXISTS;
2492 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2499 /* During a rename operation, check if another planned destination file has
2502 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2503 fd->change->type == FILEDATA_CHANGE_COPY ||
2504 fd->change->type == FILEDATA_CHANGE_MOVE)
2511 if (fd1 != NULL && fd != fd1 )
2513 if (!strcmp(fd->change->dest, fd1->change->dest))
2515 ret |= CHANGE_DUPLICATE_DEST;
2521 fd->change->error = ret;
2522 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2529 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2534 ret = file_data_verify_ci(fd, list);
2536 work = fd->sidecar_files;
2539 FileData *sfd = work->data;
2541 ret |= file_data_verify_ci(sfd, list);
2548 gchar *file_data_get_error_string(gint error)
2550 GString *result = g_string_new("");
2552 if (error & CHANGE_NO_SRC)
2554 if (result->len > 0) g_string_append(result, ", ");
2555 g_string_append(result, _("file or directory does not exist"));
2558 if (error & CHANGE_DEST_EXISTS)
2560 if (result->len > 0) g_string_append(result, ", ");
2561 g_string_append(result, _("destination already exists"));
2564 if (error & CHANGE_NO_WRITE_PERM_DEST)
2566 if (result->len > 0) g_string_append(result, ", ");
2567 g_string_append(result, _("destination can't be overwritten"));
2570 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2572 if (result->len > 0) g_string_append(result, ", ");
2573 g_string_append(result, _("destination directory is not writable"));
2576 if (error & CHANGE_NO_DEST_DIR)
2578 if (result->len > 0) g_string_append(result, ", ");
2579 g_string_append(result, _("destination directory does not exist"));
2582 if (error & CHANGE_NO_WRITE_PERM_DIR)
2584 if (result->len > 0) g_string_append(result, ", ");
2585 g_string_append(result, _("source directory is not writable"));
2588 if (error & CHANGE_NO_READ_PERM)
2590 if (result->len > 0) g_string_append(result, ", ");
2591 g_string_append(result, _("no read permission"));
2594 if (error & CHANGE_WARN_NO_WRITE_PERM)
2596 if (result->len > 0) g_string_append(result, ", ");
2597 g_string_append(result, _("file is readonly"));
2600 if (error & CHANGE_WARN_DEST_EXISTS)
2602 if (result->len > 0) g_string_append(result, ", ");
2603 g_string_append(result, _("destination already exists and will be overwritten"));
2606 if (error & CHANGE_WARN_SAME)
2608 if (result->len > 0) g_string_append(result, ", ");
2609 g_string_append(result, _("source and destination are the same"));
2612 if (error & CHANGE_WARN_CHANGED_EXT)
2614 if (result->len > 0) g_string_append(result, ", ");
2615 g_string_append(result, _("source and destination have different extension"));
2618 if (error & CHANGE_WARN_UNSAVED_META)
2620 if (result->len > 0) g_string_append(result, ", ");
2621 g_string_append(result, _("there are unsaved metadata changes for the file"));
2624 if (error & CHANGE_DUPLICATE_DEST)
2626 if (result->len > 0) g_string_append(result, ", ");
2627 g_string_append(result, _("another destination file has the same filename"));
2630 return g_string_free(result, FALSE);
2633 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2636 gint all_errors = 0;
2637 gint common_errors = ~0;
2642 if (!list) return 0;
2644 num = g_list_length(list);
2645 errors = g_new(int, num);
2656 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2657 all_errors |= error;
2658 common_errors &= error;
2665 if (desc && all_errors)
2668 GString *result = g_string_new("");
2672 gchar *str = file_data_get_error_string(common_errors);
2673 g_string_append(result, str);
2674 g_string_append(result, "\n");
2688 error = errors[i] & ~common_errors;
2692 gchar *str = file_data_get_error_string(error);
2693 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2698 *desc = g_string_free(result, FALSE);
2707 * perform the change described by FileFataChangeInfo
2708 * it is used for internal operations,
2709 * this function actually operates with files on the filesystem
2710 * it should implement safe delete
2713 static gboolean file_data_perform_move(FileData *fd)
2715 g_assert(!strcmp(fd->change->source, fd->path));
2716 return move_file(fd->change->source, fd->change->dest);
2719 static gboolean file_data_perform_copy(FileData *fd)
2721 g_assert(!strcmp(fd->change->source, fd->path));
2722 return copy_file(fd->change->source, fd->change->dest);
2725 static gboolean file_data_perform_delete(FileData *fd)
2727 if (isdir(fd->path) && !islink(fd->path))
2728 return rmdir_utf8(fd->path);
2730 if (options->file_ops.safe_delete_enable)
2731 return file_util_safe_unlink(fd->path);
2733 return unlink_file(fd->path);
2736 gboolean file_data_perform_ci(FileData *fd)
2738 FileDataChangeType type = fd->change->type;
2742 case FILEDATA_CHANGE_MOVE:
2743 return file_data_perform_move(fd);
2744 case FILEDATA_CHANGE_COPY:
2745 return file_data_perform_copy(fd);
2746 case FILEDATA_CHANGE_RENAME:
2747 return file_data_perform_move(fd); /* the same as move */
2748 case FILEDATA_CHANGE_DELETE:
2749 return file_data_perform_delete(fd);
2750 case FILEDATA_CHANGE_WRITE_METADATA:
2751 return metadata_write_perform(fd);
2752 case FILEDATA_CHANGE_UNSPECIFIED:
2753 /* nothing to do here */
2761 gboolean file_data_sc_perform_ci(FileData *fd)
2764 gboolean ret = TRUE;
2765 FileDataChangeType type = fd->change->type;
2767 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2769 work = fd->sidecar_files;
2772 FileData *sfd = work->data;
2774 if (!file_data_perform_ci(sfd)) ret = FALSE;
2778 if (!file_data_perform_ci(fd)) ret = FALSE;
2784 * updates FileData structure according to FileDataChangeInfo
2787 gboolean file_data_apply_ci(FileData *fd)
2789 FileDataChangeType type = fd->change->type;
2792 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2794 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2795 file_data_planned_change_remove(fd);
2797 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2799 /* this change overwrites another file which is already known to other modules
2800 renaming fd would create duplicate FileData structure
2801 the best thing we can do is nothing
2802 FIXME: maybe we could copy stuff like marks
2804 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2808 file_data_set_path(fd, fd->change->dest);
2811 file_data_increment_version(fd);
2812 file_data_send_notification(fd, NOTIFY_CHANGE);
2817 gboolean file_data_sc_apply_ci(FileData *fd)
2820 FileDataChangeType type = fd->change->type;
2822 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2824 work = fd->sidecar_files;
2827 FileData *sfd = work->data;
2829 file_data_apply_ci(sfd);
2833 file_data_apply_ci(fd);
2838 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2841 if (fd->parent) fd = fd->parent;
2842 if (!g_list_find(list, fd)) return FALSE;
2844 work = fd->sidecar_files;
2847 if (!g_list_find(list, work->data)) return FALSE;
2853 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2858 /* change partial groups to independent files */
2863 FileData *fd = work->data;
2866 if (!file_data_list_contains_whole_group(list, fd))
2868 file_data_disable_grouping(fd, TRUE);
2871 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2877 /* remove sidecars from the list,
2878 they can be still acessed via main_fd->sidecar_files */
2882 FileData *fd = work->data;
2886 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2888 out = g_list_prepend(out, file_data_ref(fd));
2892 filelist_free(list);
2893 out = g_list_reverse(out);
2903 * notify other modules about the change described by FileDataChangeInfo
2906 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2907 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2908 implementation in view_file_list.c */
2911 typedef struct _NotifyIdleData NotifyIdleData;
2913 struct _NotifyIdleData {
2919 typedef struct _NotifyData NotifyData;
2921 struct _NotifyData {
2922 FileDataNotifyFunc func;
2924 NotifyPriority priority;
2927 static GList *notify_func_list = NULL;
2929 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2931 NotifyData *nda = (NotifyData *)a;
2932 NotifyData *ndb = (NotifyData *)b;
2934 if (nda->priority < ndb->priority) return -1;
2935 if (nda->priority > ndb->priority) return 1;
2939 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2942 GList *work = notify_func_list;
2946 NotifyData *nd = (NotifyData *)work->data;
2948 if (nd->func == func && nd->data == data)
2950 g_warning("Notify func already registered");
2956 nd = g_new(NotifyData, 1);
2959 nd->priority = priority;
2961 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2962 DEBUG_2("Notify func registered: %p", nd);
2967 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2969 GList *work = notify_func_list;
2973 NotifyData *nd = (NotifyData *)work->data;
2975 if (nd->func == func && nd->data == data)
2977 notify_func_list = g_list_delete_link(notify_func_list, work);
2979 DEBUG_2("Notify func unregistered: %p", nd);
2985 g_warning("Notify func not found");
2990 gboolean file_data_send_notification_idle_cb(gpointer data)
2992 NotifyIdleData *nid = (NotifyIdleData *)data;
2993 GList *work = notify_func_list;
2997 NotifyData *nd = (NotifyData *)work->data;
2999 nd->func(nid->fd, nid->type, nd->data);
3002 file_data_unref(nid->fd);
3007 void file_data_send_notification(FileData *fd, NotifyType type)
3009 GList *work = notify_func_list;
3013 NotifyData *nd = (NotifyData *)work->data;
3015 nd->func(fd, type, nd->data);
3019 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3020 nid->fd = file_data_ref(fd);
3022 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3026 static GHashTable *file_data_monitor_pool = NULL;
3027 static guint realtime_monitor_id = 0; /* event source id */
3029 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
3033 file_data_check_changed_files(fd);
3035 DEBUG_1("monitor %s", fd->path);
3038 static gboolean realtime_monitor_cb(gpointer data)
3040 if (!options->update_on_time_change) return TRUE;
3041 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
3045 gboolean file_data_register_real_time_monitor(FileData *fd)
3051 if (!file_data_monitor_pool)
3052 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3054 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3056 DEBUG_1("Register realtime %d %s", count, fd->path);
3059 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3061 if (!realtime_monitor_id)
3063 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
3069 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3073 g_assert(file_data_monitor_pool);
3075 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3077 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3079 g_assert(count > 0);
3084 g_hash_table_remove(file_data_monitor_pool, fd);
3086 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3088 file_data_unref(fd);
3090 if (g_hash_table_size(file_data_monitor_pool) == 0)
3092 g_source_remove(realtime_monitor_id);
3093 realtime_monitor_id = 0;
3099 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */