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 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
283 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
286 g_free(caseless_name);
289 static void file_data_set_path(FileData *fd, const gchar *path)
291 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
292 g_assert(file_data_pool);
296 if (fd->original_path)
298 g_hash_table_remove(file_data_pool, fd->original_path);
299 g_free(fd->original_path);
302 g_assert(!g_hash_table_lookup(file_data_pool, path));
304 fd->original_path = g_strdup(path);
305 g_hash_table_insert(file_data_pool, fd->original_path, fd);
307 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
309 fd->path = g_strdup(path);
311 fd->extension = fd->name + 1;
312 file_data_set_collate_keys(fd);
316 fd->path = g_strdup(path);
317 fd->name = filename_from_path(fd->path);
319 if (strcmp(fd->name, "..") == 0)
321 gchar *dir = remove_level_from_path(path);
323 fd->path = remove_level_from_path(dir);
326 fd->extension = fd->name + 2;
327 file_data_set_collate_keys(fd);
330 else if (strcmp(fd->name, ".") == 0)
333 fd->path = remove_level_from_path(path);
335 fd->extension = fd->name + 1;
336 file_data_set_collate_keys(fd);
340 fd->extension = registered_extension_from_path(fd->path);
341 if (fd->extension == NULL)
343 fd->extension = fd->name + strlen(fd->name);
346 fd->sidecar_priority = sidecar_file_priority(fd->extension);
347 file_data_set_collate_keys(fd);
351 *-----------------------------------------------------------------------------
352 * create or reuse Filedata
353 *-----------------------------------------------------------------------------
356 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
360 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
362 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
365 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
367 fd = g_hash_table_lookup(file_data_pool, path_utf8);
373 if (!fd && file_data_planned_change_hash)
375 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
378 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
380 file_data_apply_ci(fd);
388 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
391 changed = file_data_check_changed_single_file(fd, st);
393 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
398 fd = g_new0(FileData, 1);
399 #ifdef DEBUG_FILEDATA
400 global_file_data_count++;
401 DEBUG_2("file data count++: %d", global_file_data_count);
404 fd->size = st->st_size;
405 fd->date = st->st_mtime;
406 fd->cdate = st->st_ctime;
407 fd->mode = st->st_mode;
409 fd->magick = FD_MAGICK;
411 if (disable_sidecars) fd->disable_grouping = TRUE;
413 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
418 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
420 gchar *path_utf8 = path_to_utf8(path);
421 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
427 FileData *file_data_new_simple(const gchar *path_utf8)
432 if (!stat_utf8(path_utf8, &st))
438 fd = g_hash_table_lookup(file_data_pool, path_utf8);
439 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
448 void init_exif_time_data(GList *files)
451 DEBUG_1("%s init_exif_time_data: ...", get_exec_time());
463 void read_exif_time_data(FileData *file)
465 if (file->exifdate > 0)
467 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
471 file->exif = exif_read_fd(file);
475 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
476 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), file, file->path);
481 uint year, month, day, hour, min, sec;
483 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
484 time_str.tm_year = year - 1900;
485 time_str.tm_mon = month - 1;
486 time_str.tm_mday = day;
487 time_str.tm_hour = hour;
488 time_str.tm_min = min;
489 time_str.tm_sec = sec;
490 time_str.tm_isdst = 0;
492 file->exifdate = mktime(&time_str);
498 void set_exif_time_data(GList *files)
500 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
504 FileData *file = files->data;
506 read_exif_time_data(file);
511 FileData *file_data_new_no_grouping(const gchar *path_utf8)
515 if (!stat_utf8(path_utf8, &st))
521 return file_data_new(path_utf8, &st, TRUE);
524 FileData *file_data_new_dir(const gchar *path_utf8)
528 if (!stat_utf8(path_utf8, &st))
534 /* dir or non-existing yet */
535 g_assert(S_ISDIR(st.st_mode));
537 return file_data_new(path_utf8, &st, TRUE);
541 *-----------------------------------------------------------------------------
543 *-----------------------------------------------------------------------------
546 #ifdef DEBUG_FILEDATA
547 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
549 FileData *file_data_ref(FileData *fd)
552 if (fd == NULL) return NULL;
553 if (fd->magick != FD_MAGICK)
554 #ifdef DEBUG_FILEDATA
555 DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
557 DEBUG_0("fd magick mismatch fd=%p", fd);
559 g_assert(fd->magick == FD_MAGICK);
562 #ifdef DEBUG_FILEDATA
563 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", fd, fd->ref, fd->path, file, line);
565 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
570 static void file_data_free(FileData *fd)
572 g_assert(fd->magick == FD_MAGICK);
573 g_assert(fd->ref == 0);
574 g_assert(!fd->locked);
576 #ifdef DEBUG_FILEDATA
577 global_file_data_count--;
578 DEBUG_2("file data count--: %d", global_file_data_count);
581 metadata_cache_free(fd);
582 g_hash_table_remove(file_data_pool, fd->original_path);
585 g_free(fd->original_path);
586 g_free(fd->collate_key_name);
587 g_free(fd->collate_key_name_nocase);
588 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
589 histmap_free(fd->histmap);
591 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
593 file_data_change_info_free(NULL, fd);
598 * \brief Checks if the FileData is referenced
600 * Checks the refcount and whether the FileData is locked.
602 static gboolean file_data_check_has_ref(FileData *fd)
604 return fd->ref > 0 || fd->locked;
608 * \brief Consider freeing a FileData.
610 * This function will free a FileData and its children provided that neither its parent nor it has
611 * a positive refcount, and provided that neither is locked.
613 static void file_data_consider_free(FileData *fd)
616 FileData *parent = fd->parent ? fd->parent : fd;
618 g_assert(fd->magick == FD_MAGICK);
619 if (file_data_check_has_ref(fd)) return;
620 if (file_data_check_has_ref(parent)) return;
622 work = parent->sidecar_files;
625 FileData *sfd = work->data;
626 if (file_data_check_has_ref(sfd)) return;
630 /* Neither the parent nor the siblings are referenced, so we can free everything */
631 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
632 fd->path, fd->parent ? parent->path : "-");
634 work = parent->sidecar_files;
637 FileData *sfd = work->data;
642 g_list_free(parent->sidecar_files);
643 parent->sidecar_files = NULL;
645 file_data_free(parent);
648 #ifdef DEBUG_FILEDATA
649 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
651 void file_data_unref(FileData *fd)
654 if (fd == NULL) return;
655 if (fd->magick != FD_MAGICK)
656 #ifdef DEBUG_FILEDATA
657 DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
659 DEBUG_0("fd magick mismatch fd=%p", fd);
661 g_assert(fd->magick == FD_MAGICK);
664 #ifdef DEBUG_FILEDATA
665 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", fd, fd->ref, fd->locked, fd->path,
668 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
671 // Free FileData if it's no longer ref'd
672 file_data_consider_free(fd);
676 * \brief Lock the FileData in memory.
678 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
679 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
680 * even if the code would continue to function properly even if the FileData were freed. Code that
681 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
683 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
684 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
686 void file_data_lock(FileData *fd)
688 if (fd == NULL) return;
689 if (fd->magick != FD_MAGICK) DEBUG_0("fd magick mismatch fd=%p", fd);
691 g_assert(fd->magick == FD_MAGICK);
694 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
698 * \brief Reset the maintain-FileData-in-memory lock
700 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
701 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
702 * keeping it from being freed.
704 void file_data_unlock(FileData *fd)
706 if (fd == NULL) return;
707 if (fd->magick != FD_MAGICK) DEBUG_0("fd magick mismatch fd=%p", fd);
709 g_assert(fd->magick == FD_MAGICK);
712 // Free FileData if it's no longer ref'd
713 file_data_consider_free(fd);
717 * \brief Lock all of the FileDatas in the provided list
719 * \see file_data_lock(FileData)
721 void file_data_lock_list(GList *list)
728 FileData *fd = work->data;
735 * \brief Unlock all of the FileDatas in the provided list
737 * \see file_data_unlock(FileData)
739 void file_data_unlock_list(GList *list)
746 FileData *fd = work->data;
748 file_data_unlock(fd);
753 *-----------------------------------------------------------------------------
754 * sidecar file info struct
755 *-----------------------------------------------------------------------------
758 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
760 const FileData *fda = a;
761 const FileData *fdb = b;
763 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
764 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
766 return strcmp(fdb->extension, fda->extension);
770 static gint sidecar_file_priority(const gchar *extension)
775 if (extension == NULL)
778 work = sidecar_ext_get_list();
781 gchar *ext = work->data;
784 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
790 static void file_data_check_sidecars(const GList *basename_list)
792 /* basename_list contains the new group - first is the parent, then sorted sidecars */
793 /* all files in the list have ref count > 0 */
796 GList *s_work, *new_sidecars;
799 if (!basename_list) return;
802 DEBUG_2("basename start");
803 work = basename_list;
806 FileData *fd = work->data;
808 g_assert(fd->magick == FD_MAGICK);
809 DEBUG_2("basename: %p %s", fd, fd->name);
812 g_assert(fd->parent->magick == FD_MAGICK);
813 DEBUG_2(" parent: %p", fd->parent);
815 s_work = fd->sidecar_files;
818 FileData *sfd = s_work->data;
819 s_work = s_work->next;
820 g_assert(sfd->magick == FD_MAGICK);
821 DEBUG_2(" sidecar: %p %s", sfd, sfd->name);
824 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
827 parent_fd = basename_list->data;
829 /* check if the second and next entries of basename_list are already connected
830 as sidecars of the first entry (parent_fd) */
831 work = basename_list->next;
832 s_work = parent_fd->sidecar_files;
834 while (work && s_work)
836 if (work->data != s_work->data) break;
838 s_work = s_work->next;
841 if (!work && !s_work)
843 DEBUG_2("basename no change");
844 return; /* no change in grouping */
847 /* we have to regroup it */
849 /* first, disconnect everything and send notification*/
851 work = basename_list;
854 FileData *fd = work->data;
856 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
860 FileData *old_parent = fd->parent;
861 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
862 file_data_ref(old_parent);
863 file_data_disconnect_sidecar_file(old_parent, fd);
864 file_data_send_notification(old_parent, NOTIFY_REREAD);
865 file_data_unref(old_parent);
868 while (fd->sidecar_files)
870 FileData *sfd = fd->sidecar_files->data;
871 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
873 file_data_disconnect_sidecar_file(fd, sfd);
874 file_data_send_notification(sfd, NOTIFY_REREAD);
875 file_data_unref(sfd);
877 file_data_send_notification(fd, NOTIFY_GROUPING);
879 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
882 /* now we can form the new group */
883 work = basename_list->next;
887 FileData *sfd = work->data;
888 g_assert(sfd->magick == FD_MAGICK);
889 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
890 sfd->parent = parent_fd;
891 new_sidecars = g_list_prepend(new_sidecars, sfd);
894 g_assert(parent_fd->sidecar_files == NULL);
895 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
896 DEBUG_1("basename group changed for %s", parent_fd->path);
900 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
902 g_assert(target->magick == FD_MAGICK);
903 g_assert(sfd->magick == FD_MAGICK);
904 g_assert(g_list_find(target->sidecar_files, sfd));
906 file_data_ref(target);
909 g_assert(sfd->parent == target);
911 file_data_increment_version(sfd); /* increments both sfd and target */
913 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
916 file_data_unref(target);
917 file_data_unref(sfd);
920 /* disables / enables grouping for particular file, sends UPDATE notification */
921 void file_data_disable_grouping(FileData *fd, gboolean disable)
923 if (!fd->disable_grouping == !disable) return;
925 fd->disable_grouping = !!disable;
931 FileData *parent = file_data_ref(fd->parent);
932 file_data_disconnect_sidecar_file(parent, fd);
933 file_data_send_notification(parent, NOTIFY_GROUPING);
934 file_data_unref(parent);
936 else if (fd->sidecar_files)
938 GList *sidecar_files = filelist_copy(fd->sidecar_files);
939 GList *work = sidecar_files;
942 FileData *sfd = work->data;
944 file_data_disconnect_sidecar_file(fd, sfd);
945 file_data_send_notification(sfd, NOTIFY_GROUPING);
947 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
948 filelist_free(sidecar_files);
952 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
957 file_data_increment_version(fd);
958 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
960 file_data_send_notification(fd, NOTIFY_GROUPING);
963 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
970 FileData *fd = work->data;
972 file_data_disable_grouping(fd, disable);
980 *-----------------------------------------------------------------------------
982 *-----------------------------------------------------------------------------
986 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
989 if (!filelist_sort_ascend)
996 switch (filelist_sort_method)
1001 if (fa->size < fb->size) return -1;
1002 if (fa->size > fb->size) return 1;
1003 /* fall back to name */
1006 if (fa->date < fb->date) return -1;
1007 if (fa->date > fb->date) return 1;
1008 /* fall back to name */
1011 if (fa->cdate < fb->cdate) return -1;
1012 if (fa->cdate > fb->cdate) return 1;
1013 /* fall back to name */
1016 if (fa->exifdate < fb->exifdate) return -1;
1017 if (fa->exifdate > fb->exifdate) return 1;
1018 /* fall back to name */
1020 #ifdef HAVE_STRVERSCMP
1022 ret = strverscmp(fa->name, fb->name);
1023 if (ret != 0) return ret;
1030 if (options->file_sort.case_sensitive)
1031 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1033 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1035 if (ret != 0) return ret;
1037 /* do not return 0 unless the files are really the same
1038 file_data_pool ensures that original_path is unique
1040 return strcmp(fa->original_path, fb->original_path);
1043 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1045 filelist_sort_method = method;
1046 filelist_sort_ascend = ascend;
1047 return filelist_sort_compare_filedata(fa, fb);
1050 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1052 return filelist_sort_compare_filedata(a, b);
1055 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1057 filelist_sort_method = method;
1058 filelist_sort_ascend = ascend;
1059 return g_list_sort(list, cb);
1062 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1064 filelist_sort_method = method;
1065 filelist_sort_ascend = ascend;
1066 return g_list_insert_sorted(list, data, cb);
1069 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1071 if (method == SORT_EXIFTIME)
1073 set_exif_time_data(list);
1075 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1078 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
1080 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1084 *-----------------------------------------------------------------------------
1085 * basename hash - grouping of sidecars in filelist
1086 *-----------------------------------------------------------------------------
1090 static GHashTable *file_data_basename_hash_new(void)
1092 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1095 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1098 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1100 list = g_hash_table_lookup(basename_hash, basename);
1102 if (!g_list_find(list, fd))
1104 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1105 g_hash_table_insert(basename_hash, basename, list);
1114 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
1116 filelist_free((GList *)value);
1119 static void file_data_basename_hash_free(GHashTable *basename_hash)
1121 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1122 g_hash_table_destroy(basename_hash);
1126 *-----------------------------------------------------------------------------
1127 * handling sidecars in filelist
1128 *-----------------------------------------------------------------------------
1131 static GList *filelist_filter_out_sidecars(GList *flist)
1133 GList *work = flist;
1134 GList *flist_filtered = NULL;
1138 FileData *fd = work->data;
1141 if (fd->parent) /* remove fd's that are children */
1142 file_data_unref(fd);
1144 flist_filtered = g_list_prepend(flist_filtered, fd);
1148 return flist_filtered;
1151 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
1153 GList *basename_list = (GList *)value;
1154 file_data_check_sidecars(basename_list);
1158 static gboolean is_hidden_file(const gchar *name)
1160 if (name[0] != '.') return FALSE;
1161 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1166 *-----------------------------------------------------------------------------
1167 * the main filelist function
1168 *-----------------------------------------------------------------------------
1171 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1176 GList *dlist = NULL;
1177 GList *flist = NULL;
1178 gint (*stat_func)(const gchar *path, struct stat *buf);
1179 GHashTable *basename_hash = NULL;
1181 g_assert(files || dirs);
1183 if (files) *files = NULL;
1184 if (dirs) *dirs = NULL;
1186 pathl = path_from_utf8(dir_path);
1187 if (!pathl) return FALSE;
1189 dp = opendir(pathl);
1196 if (files) basename_hash = file_data_basename_hash_new();
1198 if (follow_symlinks)
1203 while ((dir = readdir(dp)) != NULL)
1205 struct stat ent_sbuf;
1206 const gchar *name = dir->d_name;
1209 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1212 filepath = g_build_filename(pathl, name, NULL);
1213 if (stat_func(filepath, &ent_sbuf) >= 0)
1215 if (S_ISDIR(ent_sbuf.st_mode))
1217 /* we ignore the .thumbnails dir for cleanliness */
1219 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1220 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1221 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1222 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1224 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1229 if (files && filter_name_exists(name))
1231 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1232 flist = g_list_prepend(flist, fd);
1233 if (fd->sidecar_priority && !fd->disable_grouping)
1235 file_data_basename_hash_insert(basename_hash, fd);
1242 if (errno == EOVERFLOW)
1244 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1254 if (dirs) *dirs = dlist;
1258 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1260 *files = filelist_filter_out_sidecars(flist);
1262 if (basename_hash) file_data_basename_hash_free(basename_hash);
1264 // Call a separate function to initialize the exif datestamps for the found files..
1265 if (files) init_exif_time_data(*files);
1270 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1272 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1275 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1277 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1280 FileData *file_data_new_group(const gchar *path_utf8)
1287 if (!stat_utf8(path_utf8, &st))
1293 if (S_ISDIR(st.st_mode))
1294 return file_data_new(path_utf8, &st, TRUE);
1296 dir = remove_level_from_path(path_utf8);
1298 filelist_read_real(dir, &files, NULL, TRUE);
1300 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1301 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1307 filelist_free(files);
1313 void filelist_free(GList *list)
1320 file_data_unref((FileData *)work->data);
1328 GList *filelist_copy(GList *list)
1330 GList *new_list = NULL;
1341 new_list = g_list_prepend(new_list, file_data_ref(fd));
1344 return g_list_reverse(new_list);
1347 GList *filelist_from_path_list(GList *list)
1349 GList *new_list = NULL;
1360 new_list = g_list_prepend(new_list, file_data_new_group(path));
1363 return g_list_reverse(new_list);
1366 GList *filelist_to_path_list(GList *list)
1368 GList *new_list = NULL;
1379 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1382 return g_list_reverse(new_list);
1385 GList *filelist_filter(GList *list, gboolean is_dir_list)
1389 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1394 FileData *fd = (FileData *)(work->data);
1395 const gchar *name = fd->name;
1397 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1398 (!is_dir_list && !filter_name_exists(name)) ||
1399 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1400 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1404 list = g_list_remove_link(list, link);
1405 file_data_unref(fd);
1416 *-----------------------------------------------------------------------------
1417 * filelist recursive
1418 *-----------------------------------------------------------------------------
1421 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1423 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1426 GList *filelist_sort_path(GList *list)
1428 return g_list_sort(list, filelist_sort_path_cb);
1431 static void filelist_recursive_append(GList **list, GList *dirs)
1438 FileData *fd = (FileData *)(work->data);
1442 if (filelist_read(fd, &f, &d))
1444 f = filelist_filter(f, FALSE);
1445 f = filelist_sort_path(f);
1446 *list = g_list_concat(*list, f);
1448 d = filelist_filter(d, TRUE);
1449 d = filelist_sort_path(d);
1450 filelist_recursive_append(list, d);
1458 GList *filelist_recursive(FileData *dir_fd)
1463 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1464 list = filelist_filter(list, FALSE);
1465 list = filelist_sort_path(list);
1467 d = filelist_filter(d, TRUE);
1468 d = filelist_sort_path(d);
1469 filelist_recursive_append(&list, d);
1476 *-----------------------------------------------------------------------------
1477 * file modification support
1478 *-----------------------------------------------------------------------------
1482 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1484 if (!fdci && fd) fdci = fd->change;
1488 g_free(fdci->source);
1493 if (fd) fd->change = NULL;
1496 static gboolean file_data_can_write_directly(FileData *fd)
1498 return filter_name_is_writable(fd->extension);
1501 static gboolean file_data_can_write_sidecar(FileData *fd)
1503 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1506 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1508 gchar *sidecar_path = NULL;
1511 if (!file_data_can_write_sidecar(fd)) return NULL;
1513 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1516 FileData *sfd = work->data;
1518 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
1520 sidecar_path = g_strdup(sfd->path);
1525 if (!existing_only && !sidecar_path)
1527 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1528 sidecar_path = g_strconcat(base, ".xmp", NULL);
1532 return sidecar_path;
1536 * marks and orientation
1539 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1540 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1541 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1542 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1544 gboolean file_data_get_mark(FileData *fd, gint n)
1546 gboolean valid = (fd->valid_marks & (1 << n));
1548 if (file_data_get_mark_func[n] && !valid)
1550 guint old = fd->marks;
1551 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1553 if (!value != !(fd->marks & (1 << n)))
1555 fd->marks = fd->marks ^ (1 << n);
1558 fd->valid_marks |= (1 << n);
1559 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1561 file_data_unref(fd);
1563 else if (!old && fd->marks)
1569 return !!(fd->marks & (1 << n));
1572 guint file_data_get_marks(FileData *fd)
1575 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1579 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1582 if (!value == !file_data_get_mark(fd, n)) return;
1584 if (file_data_set_mark_func[n])
1586 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1591 fd->marks = fd->marks ^ (1 << n);
1593 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1595 file_data_unref(fd);
1597 else if (!old && fd->marks)
1602 file_data_increment_version(fd);
1603 file_data_send_notification(fd, NOTIFY_MARKS);
1606 gboolean file_data_filter_marks(FileData *fd, guint filter)
1609 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1610 return ((fd->marks & filter) == filter);
1613 GList *file_data_filter_marks_list(GList *list, guint filter)
1620 FileData *fd = work->data;
1624 if (!file_data_filter_marks(fd, filter))
1626 list = g_list_remove_link(list, link);
1627 file_data_unref(fd);
1635 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1637 FileData *fd = value;
1638 file_data_increment_version(fd);
1639 file_data_send_notification(fd, NOTIFY_MARKS);
1642 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1644 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1646 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1648 file_data_get_mark_func[n] = get_mark_func;
1649 file_data_set_mark_func[n] = set_mark_func;
1650 file_data_mark_func_data[n] = data;
1651 file_data_destroy_mark_func[n] = notify;
1655 /* this effectively changes all known files */
1656 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1662 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1664 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1665 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1666 if (data) *data = file_data_mark_func_data[n];
1669 gint file_data_get_user_orientation(FileData *fd)
1671 return fd->user_orientation;
1674 void file_data_set_user_orientation(FileData *fd, gint value)
1676 if (fd->user_orientation == value) return;
1678 fd->user_orientation = value;
1679 file_data_increment_version(fd);
1680 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1685 * file_data - operates on the given fd
1686 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1690 /* return list of sidecar file extensions in a string */
1691 gchar *file_data_sc_list_to_string(FileData *fd)
1694 GString *result = g_string_new("");
1696 work = fd->sidecar_files;
1699 FileData *sfd = work->data;
1701 result = g_string_append(result, "+ ");
1702 result = g_string_append(result, sfd->extension);
1704 if (work) result = g_string_append_c(result, ' ');
1707 return g_string_free(result, FALSE);
1713 * add FileDataChangeInfo (see typedefs.h) for the given operation
1714 * uses file_data_add_change_info
1716 * fails if the fd->change already exists - change operations can't run in parallel
1717 * fd->change_info works as a lock
1719 * dest can be NULL - in this case the current name is used for now, it will
1724 FileDataChangeInfo types:
1726 MOVE - path is changed, name may be changed too
1727 RENAME - path remains unchanged, name is changed
1728 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1729 sidecar names are changed too, extensions are not changed
1731 UPDATE - file size, date or grouping has been changed
1734 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1736 FileDataChangeInfo *fdci;
1738 if (fd->change) return FALSE;
1740 fdci = g_new0(FileDataChangeInfo, 1);
1745 fdci->source = g_strdup(src);
1747 fdci->source = g_strdup(fd->path);
1750 fdci->dest = g_strdup(dest);
1757 static void file_data_planned_change_remove(FileData *fd)
1759 if (file_data_planned_change_hash &&
1760 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1762 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1764 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1765 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1766 file_data_unref(fd);
1767 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1769 g_hash_table_destroy(file_data_planned_change_hash);
1770 file_data_planned_change_hash = NULL;
1771 DEBUG_1("planned change: empty");
1778 void file_data_free_ci(FileData *fd)
1780 FileDataChangeInfo *fdci = fd->change;
1784 file_data_planned_change_remove(fd);
1786 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1788 g_free(fdci->source);
1796 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1798 FileDataChangeInfo *fdci = fd->change;
1800 fdci->regroup_when_finished = enable;
1803 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1807 if (fd->parent) fd = fd->parent;
1809 if (fd->change) return FALSE;
1811 work = fd->sidecar_files;
1814 FileData *sfd = work->data;
1816 if (sfd->change) return FALSE;
1820 file_data_add_ci(fd, type, NULL, NULL);
1822 work = fd->sidecar_files;
1825 FileData *sfd = work->data;
1827 file_data_add_ci(sfd, type, NULL, NULL);
1834 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1838 if (fd->parent) fd = fd->parent;
1840 if (!fd->change || fd->change->type != type) return FALSE;
1842 work = fd->sidecar_files;
1845 FileData *sfd = work->data;
1847 if (!sfd->change || sfd->change->type != type) return FALSE;
1855 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1857 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1858 file_data_sc_update_ci_copy(fd, dest_path);
1862 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1864 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1865 file_data_sc_update_ci_move(fd, dest_path);
1869 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1871 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1872 file_data_sc_update_ci_rename(fd, dest_path);
1876 gboolean file_data_sc_add_ci_delete(FileData *fd)
1878 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1881 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1883 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1884 file_data_sc_update_ci_unspecified(fd, dest_path);
1888 gboolean file_data_add_ci_write_metadata(FileData *fd)
1890 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1893 void file_data_sc_free_ci(FileData *fd)
1897 if (fd->parent) fd = fd->parent;
1899 file_data_free_ci(fd);
1901 work = fd->sidecar_files;
1904 FileData *sfd = work->data;
1906 file_data_free_ci(sfd);
1911 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1914 gboolean ret = TRUE;
1919 FileData *fd = work->data;
1921 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1928 static void file_data_sc_revert_ci_list(GList *fd_list)
1935 FileData *fd = work->data;
1937 file_data_sc_free_ci(fd);
1942 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1949 FileData *fd = work->data;
1951 if (!func(fd, dest))
1953 file_data_sc_revert_ci_list(work->prev);
1962 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1964 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1967 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1969 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1972 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1974 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1977 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1979 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1982 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1985 gboolean ret = TRUE;
1990 FileData *fd = work->data;
1992 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1999 void file_data_free_ci_list(GList *fd_list)
2006 FileData *fd = work->data;
2008 file_data_free_ci(fd);
2013 void file_data_sc_free_ci_list(GList *fd_list)
2020 FileData *fd = work->data;
2022 file_data_sc_free_ci(fd);
2028 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2029 * fails if fd->change does not exist or the change type does not match
2032 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2034 FileDataChangeType type = fd->change->type;
2036 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2040 if (!file_data_planned_change_hash)
2041 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2043 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2045 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2046 g_hash_table_remove(file_data_planned_change_hash, old_path);
2047 file_data_unref(fd);
2050 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
2055 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2056 g_hash_table_remove(file_data_planned_change_hash, new_path);
2057 file_data_unref(ofd);
2060 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2062 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2067 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2069 gchar *old_path = fd->change->dest;
2071 fd->change->dest = g_strdup(dest_path);
2072 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2076 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2078 const gchar *extension = extension_from_path(fd->change->source);
2079 gchar *base = remove_extension_from_path(dest_path);
2080 gchar *old_path = fd->change->dest;
2082 fd->change->dest = g_strconcat(base, extension, NULL);
2083 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2089 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2092 gchar *dest_path_full = NULL;
2094 if (fd->parent) fd = fd->parent;
2098 dest_path = fd->path;
2100 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2102 gchar *dir = remove_level_from_path(fd->path);
2104 dest_path_full = g_build_filename(dir, dest_path, NULL);
2106 dest_path = dest_path_full;
2108 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2110 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2111 dest_path = dest_path_full;
2114 file_data_update_ci_dest(fd, dest_path);
2116 work = fd->sidecar_files;
2119 FileData *sfd = work->data;
2121 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2125 g_free(dest_path_full);
2128 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2130 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2131 file_data_sc_update_ci(fd, dest_path);
2135 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2137 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2140 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2142 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2145 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2147 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2150 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2152 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2155 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2157 gboolean (*func)(FileData *, const gchar *))
2160 gboolean ret = TRUE;
2165 FileData *fd = work->data;
2167 if (!func(fd, dest)) ret = FALSE;
2174 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2176 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2179 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2181 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2184 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2186 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2191 * verify source and dest paths - dest image exists, etc.
2192 * it should detect all possible problems with the planned operation
2195 gint file_data_verify_ci(FileData *fd, GList *list)
2197 gint ret = CHANGE_OK;
2200 FileData *fd1 = NULL;
2204 DEBUG_1("Change checked: no change info: %s", fd->path);
2208 if (!isname(fd->path))
2210 /* this probably should not happen */
2211 ret |= CHANGE_NO_SRC;
2212 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2216 dir = remove_level_from_path(fd->path);
2218 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2219 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2220 fd->change->type != FILEDATA_CHANGE_RENAME &&
2221 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2224 ret |= CHANGE_WARN_UNSAVED_META;
2225 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2228 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2229 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2230 !access_file(fd->path, R_OK))
2232 ret |= CHANGE_NO_READ_PERM;
2233 DEBUG_1("Change checked: no read permission: %s", fd->path);
2235 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2236 !access_file(dir, W_OK))
2238 ret |= CHANGE_NO_WRITE_PERM_DIR;
2239 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2241 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2242 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2243 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2244 !access_file(fd->path, W_OK))
2246 ret |= CHANGE_WARN_NO_WRITE_PERM;
2247 DEBUG_1("Change checked: no write permission: %s", fd->path);
2249 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2250 - that means that there are no hard errors and warnings can be disabled
2251 - the destination is determined during the check
2253 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2255 /* determine destination file */
2256 gboolean have_dest = FALSE;
2257 gchar *dest_dir = NULL;
2259 if (options->metadata.save_in_image_file)
2261 if (file_data_can_write_directly(fd))
2263 /* we can write the file directly */
2264 if (access_file(fd->path, W_OK))
2270 if (options->metadata.warn_on_write_problems)
2272 ret |= CHANGE_WARN_NO_WRITE_PERM;
2273 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2277 else if (file_data_can_write_sidecar(fd))
2279 /* we can write sidecar */
2280 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2281 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2283 file_data_update_ci_dest(fd, sidecar);
2288 if (options->metadata.warn_on_write_problems)
2290 ret |= CHANGE_WARN_NO_WRITE_PERM;
2291 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2300 /* write private metadata file under ~/.geeqie */
2302 /* If an existing metadata file exists, we will try writing to
2303 * it's location regardless of the user's preference.
2305 gchar *metadata_path = NULL;
2307 /* but ignore XMP if we are not able to write it */
2308 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2310 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2312 if (metadata_path && !access_file(metadata_path, W_OK))
2314 g_free(metadata_path);
2315 metadata_path = NULL;
2322 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2323 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2325 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2327 metadata_path = g_build_filename(dest_dir, filename, NULL);
2331 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2333 file_data_update_ci_dest(fd, metadata_path);
2338 ret |= CHANGE_NO_WRITE_PERM_DEST;
2339 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2341 g_free(metadata_path);
2346 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2351 same = (strcmp(fd->path, fd->change->dest) == 0);
2355 const gchar *dest_ext = extension_from_path(fd->change->dest);
2356 if (!dest_ext) dest_ext = "";
2357 if (!options->file_filter.disable_file_extension_checks)
2359 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2361 ret |= CHANGE_WARN_CHANGED_EXT;
2362 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2368 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2370 ret |= CHANGE_WARN_SAME;
2371 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2375 dest_dir = remove_level_from_path(fd->change->dest);
2377 if (!isdir(dest_dir))
2379 ret |= CHANGE_NO_DEST_DIR;
2380 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2382 else if (!access_file(dest_dir, W_OK))
2384 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2385 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2389 if (isfile(fd->change->dest))
2391 if (!access_file(fd->change->dest, W_OK))
2393 ret |= CHANGE_NO_WRITE_PERM_DEST;
2394 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2398 ret |= CHANGE_WARN_DEST_EXISTS;
2399 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2402 else if (isdir(fd->change->dest))
2404 ret |= CHANGE_DEST_EXISTS;
2405 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2412 /* During a rename operation, check if another planned destination file has
2415 if(fd->change->type == FILEDATA_CHANGE_RENAME)
2422 if (fd1 != NULL && fd != fd1 )
2424 if (!strcmp(fd->change->dest, fd1->change->dest))
2426 ret |= CHANGE_DUPLICATE_DEST;
2432 fd->change->error = ret;
2433 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2440 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2445 ret = file_data_verify_ci(fd, list);
2447 work = fd->sidecar_files;
2450 FileData *sfd = work->data;
2452 ret |= file_data_verify_ci(sfd, list);
2459 gchar *file_data_get_error_string(gint error)
2461 GString *result = g_string_new("");
2463 if (error & CHANGE_NO_SRC)
2465 if (result->len > 0) g_string_append(result, ", ");
2466 g_string_append(result, _("file or directory does not exist"));
2469 if (error & CHANGE_DEST_EXISTS)
2471 if (result->len > 0) g_string_append(result, ", ");
2472 g_string_append(result, _("destination already exists"));
2475 if (error & CHANGE_NO_WRITE_PERM_DEST)
2477 if (result->len > 0) g_string_append(result, ", ");
2478 g_string_append(result, _("destination can't be overwritten"));
2481 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2483 if (result->len > 0) g_string_append(result, ", ");
2484 g_string_append(result, _("destination directory is not writable"));
2487 if (error & CHANGE_NO_DEST_DIR)
2489 if (result->len > 0) g_string_append(result, ", ");
2490 g_string_append(result, _("destination directory does not exist"));
2493 if (error & CHANGE_NO_WRITE_PERM_DIR)
2495 if (result->len > 0) g_string_append(result, ", ");
2496 g_string_append(result, _("source directory is not writable"));
2499 if (error & CHANGE_NO_READ_PERM)
2501 if (result->len > 0) g_string_append(result, ", ");
2502 g_string_append(result, _("no read permission"));
2505 if (error & CHANGE_WARN_NO_WRITE_PERM)
2507 if (result->len > 0) g_string_append(result, ", ");
2508 g_string_append(result, _("file is readonly"));
2511 if (error & CHANGE_WARN_DEST_EXISTS)
2513 if (result->len > 0) g_string_append(result, ", ");
2514 g_string_append(result, _("destination already exists and will be overwritten"));
2517 if (error & CHANGE_WARN_SAME)
2519 if (result->len > 0) g_string_append(result, ", ");
2520 g_string_append(result, _("source and destination are the same"));
2523 if (error & CHANGE_WARN_CHANGED_EXT)
2525 if (result->len > 0) g_string_append(result, ", ");
2526 g_string_append(result, _("source and destination have different extension"));
2529 if (error & CHANGE_WARN_UNSAVED_META)
2531 if (result->len > 0) g_string_append(result, ", ");
2532 g_string_append(result, _("there are unsaved metadata changes for the file"));
2535 if (error & CHANGE_DUPLICATE_DEST)
2537 if (result->len > 0) g_string_append(result, ", ");
2538 g_string_append(result, _("another destination file has the same filename"));
2541 return g_string_free(result, FALSE);
2544 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2547 gint all_errors = 0;
2548 gint common_errors = ~0;
2553 if (!list) return 0;
2555 num = g_list_length(list);
2556 errors = g_new(int, num);
2567 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2568 all_errors |= error;
2569 common_errors &= error;
2576 if (desc && all_errors)
2579 GString *result = g_string_new("");
2583 gchar *str = file_data_get_error_string(common_errors);
2584 g_string_append(result, str);
2585 g_string_append(result, "\n");
2599 error = errors[i] & ~common_errors;
2603 gchar *str = file_data_get_error_string(error);
2604 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2609 *desc = g_string_free(result, FALSE);
2618 * perform the change described by FileFataChangeInfo
2619 * it is used for internal operations,
2620 * this function actually operates with files on the filesystem
2621 * it should implement safe delete
2624 static gboolean file_data_perform_move(FileData *fd)
2626 g_assert(!strcmp(fd->change->source, fd->path));
2627 return move_file(fd->change->source, fd->change->dest);
2630 static gboolean file_data_perform_copy(FileData *fd)
2632 g_assert(!strcmp(fd->change->source, fd->path));
2633 return copy_file(fd->change->source, fd->change->dest);
2636 static gboolean file_data_perform_delete(FileData *fd)
2638 if (isdir(fd->path) && !islink(fd->path))
2639 return rmdir_utf8(fd->path);
2641 if (options->file_ops.safe_delete_enable)
2642 return file_util_safe_unlink(fd->path);
2644 return unlink_file(fd->path);
2647 gboolean file_data_perform_ci(FileData *fd)
2649 FileDataChangeType type = fd->change->type;
2653 case FILEDATA_CHANGE_MOVE:
2654 return file_data_perform_move(fd);
2655 case FILEDATA_CHANGE_COPY:
2656 return file_data_perform_copy(fd);
2657 case FILEDATA_CHANGE_RENAME:
2658 return file_data_perform_move(fd); /* the same as move */
2659 case FILEDATA_CHANGE_DELETE:
2660 return file_data_perform_delete(fd);
2661 case FILEDATA_CHANGE_WRITE_METADATA:
2662 return metadata_write_perform(fd);
2663 case FILEDATA_CHANGE_UNSPECIFIED:
2664 /* nothing to do here */
2672 gboolean file_data_sc_perform_ci(FileData *fd)
2675 gboolean ret = TRUE;
2676 FileDataChangeType type = fd->change->type;
2678 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2680 work = fd->sidecar_files;
2683 FileData *sfd = work->data;
2685 if (!file_data_perform_ci(sfd)) ret = FALSE;
2689 if (!file_data_perform_ci(fd)) ret = FALSE;
2695 * updates FileData structure according to FileDataChangeInfo
2698 gboolean file_data_apply_ci(FileData *fd)
2700 FileDataChangeType type = fd->change->type;
2703 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2705 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2706 file_data_planned_change_remove(fd);
2708 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2710 /* this change overwrites another file which is already known to other modules
2711 renaming fd would create duplicate FileData structure
2712 the best thing we can do is nothing
2713 FIXME: maybe we could copy stuff like marks
2715 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2719 file_data_set_path(fd, fd->change->dest);
2722 file_data_increment_version(fd);
2723 file_data_send_notification(fd, NOTIFY_CHANGE);
2728 gboolean file_data_sc_apply_ci(FileData *fd)
2731 FileDataChangeType type = fd->change->type;
2733 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2735 work = fd->sidecar_files;
2738 FileData *sfd = work->data;
2740 file_data_apply_ci(sfd);
2744 file_data_apply_ci(fd);
2749 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2752 if (fd->parent) fd = fd->parent;
2753 if (!g_list_find(list, fd)) return FALSE;
2755 work = fd->sidecar_files;
2758 if (!g_list_find(list, work->data)) return FALSE;
2764 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2769 /* change partial groups to independent files */
2774 FileData *fd = work->data;
2777 if (!file_data_list_contains_whole_group(list, fd))
2779 file_data_disable_grouping(fd, TRUE);
2782 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2788 /* remove sidecars from the list,
2789 they can be still acessed via main_fd->sidecar_files */
2793 FileData *fd = work->data;
2797 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2799 out = g_list_prepend(out, file_data_ref(fd));
2803 filelist_free(list);
2804 out = g_list_reverse(out);
2814 * notify other modules about the change described by FileDataChangeInfo
2817 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2818 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2819 implementation in view_file_list.c */
2822 typedef struct _NotifyIdleData NotifyIdleData;
2824 struct _NotifyIdleData {
2830 typedef struct _NotifyData NotifyData;
2832 struct _NotifyData {
2833 FileDataNotifyFunc func;
2835 NotifyPriority priority;
2838 static GList *notify_func_list = NULL;
2840 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2842 NotifyData *nda = (NotifyData *)a;
2843 NotifyData *ndb = (NotifyData *)b;
2845 if (nda->priority < ndb->priority) return -1;
2846 if (nda->priority > ndb->priority) return 1;
2850 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2853 GList *work = notify_func_list;
2857 NotifyData *nd = (NotifyData *)work->data;
2859 if (nd->func == func && nd->data == data)
2861 g_warning("Notify func already registered");
2867 nd = g_new(NotifyData, 1);
2870 nd->priority = priority;
2872 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2873 DEBUG_2("Notify func registered: %p", nd);
2878 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2880 GList *work = notify_func_list;
2884 NotifyData *nd = (NotifyData *)work->data;
2886 if (nd->func == func && nd->data == data)
2888 notify_func_list = g_list_delete_link(notify_func_list, work);
2890 DEBUG_2("Notify func unregistered: %p", nd);
2896 g_warning("Notify func not found");
2901 gboolean file_data_send_notification_idle_cb(gpointer data)
2903 NotifyIdleData *nid = (NotifyIdleData *)data;
2904 GList *work = notify_func_list;
2908 NotifyData *nd = (NotifyData *)work->data;
2910 nd->func(nid->fd, nid->type, nd->data);
2913 file_data_unref(nid->fd);
2918 void file_data_send_notification(FileData *fd, NotifyType type)
2920 GList *work = notify_func_list;
2924 NotifyData *nd = (NotifyData *)work->data;
2926 nd->func(fd, type, nd->data);
2930 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
2931 nid->fd = file_data_ref(fd);
2933 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
2937 static GHashTable *file_data_monitor_pool = NULL;
2938 static guint realtime_monitor_id = 0; /* event source id */
2940 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2944 file_data_check_changed_files(fd);
2946 DEBUG_1("monitor %s", fd->path);
2949 static gboolean realtime_monitor_cb(gpointer data)
2951 if (!options->update_on_time_change) return TRUE;
2952 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2956 gboolean file_data_register_real_time_monitor(FileData *fd)
2962 if (!file_data_monitor_pool)
2963 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2965 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2967 DEBUG_1("Register realtime %d %s", count, fd->path);
2970 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2972 if (!realtime_monitor_id)
2974 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2980 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2984 g_assert(file_data_monitor_pool);
2986 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2988 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2990 g_assert(count > 0);
2995 g_hash_table_remove(file_data_monitor_pool, fd);
2997 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2999 file_data_unref(fd);
3001 if (g_hash_table_size(file_data_monitor_pool) == 0)
3003 g_source_remove(realtime_monitor_id);
3004 realtime_monitor_id = 0;
3010 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */