2 * Copyright (C) 2006 John Ellis
3 * Copyright (C) 2008 - 2016 The Geeqie Team
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 #include "filefilter.h"
27 #include "thumb_standard.h"
28 #include "ui_fileops.h"
31 #include "histogram.h"
38 gint global_file_data_count = 0;
41 static GHashTable *file_data_pool = NULL;
42 static GHashTable *file_data_planned_change_hash = NULL;
44 static gint sidecar_file_priority(const gchar *extension);
45 static void file_data_check_sidecars(const GList *basename_list);
46 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
49 static SortType filelist_sort_method = SORT_NONE;
50 static gboolean filelist_sort_ascend = TRUE;
53 *-----------------------------------------------------------------------------
54 * text conversion utils
55 *-----------------------------------------------------------------------------
58 gchar *text_from_size(gint64 size)
64 /* what I would like to use is printf("%'d", size)
65 * BUT: not supported on every libc :(
69 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
70 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
74 a = g_strdup_printf("%d", (guint)size);
80 b = g_new(gchar, l + n + 1);
105 gchar *text_from_size_abrev(gint64 size)
107 if (size < (gint64)1024)
109 return g_strdup_printf(_("%d bytes"), (gint)size);
111 if (size < (gint64)1048576)
113 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
115 if (size < (gint64)1073741824)
117 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
120 /* to avoid overflowing the gdouble, do division in two steps */
122 return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
125 /* note: returned string is valid until next call to text_from_time() */
126 const gchar *text_from_time(time_t t)
128 static gchar *ret = NULL;
132 GError *error = NULL;
134 btime = localtime(&t);
136 /* the %x warning about 2 digit years is not an error */
137 buflen = strftime(buf, sizeof(buf), "%x %X", btime);
138 if (buflen < 1) return "";
141 ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
144 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
153 *-----------------------------------------------------------------------------
154 * changed files detection and notification
155 *-----------------------------------------------------------------------------
158 void file_data_increment_version(FileData *fd)
164 fd->parent->version++;
165 fd->parent->valid_marks = 0;
169 static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st)
171 if (fd->size != st->st_size ||
172 fd->date != st->st_mtime)
174 fd->size = st->st_size;
175 fd->date = st->st_mtime;
176 fd->cdate = st->st_ctime;
177 fd->mode = st->st_mode;
178 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
179 fd->thumb_pixbuf = NULL;
180 file_data_increment_version(fd);
181 file_data_send_notification(fd, NOTIFY_REREAD);
187 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
189 gboolean ret = FALSE;
192 ret = file_data_check_changed_single_file(fd, st);
194 work = fd->sidecar_files;
197 FileData *sfd = work->data;
201 if (!stat_utf8(sfd->path, &st))
206 file_data_disconnect_sidecar_file(fd, sfd);
208 file_data_increment_version(sfd);
209 file_data_send_notification(sfd, NOTIFY_REREAD);
210 file_data_unref(sfd);
214 ret |= file_data_check_changed_files_recursive(sfd, &st);
220 gboolean file_data_check_changed_files(FileData *fd)
222 gboolean ret = FALSE;
225 if (fd->parent) fd = fd->parent;
227 if (!stat_utf8(fd->path, &st))
231 FileData *sfd = NULL;
233 /* parent is missing, we have to rebuild whole group */
238 /* file_data_disconnect_sidecar_file might delete the file,
239 we have to keep the reference to prevent this */
240 sidecars = filelist_copy(fd->sidecar_files);
248 file_data_disconnect_sidecar_file(fd, sfd);
250 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
251 /* now we can release the sidecars */
252 filelist_free(sidecars);
253 file_data_increment_version(fd);
254 file_data_send_notification(fd, NOTIFY_REREAD);
259 ret |= file_data_check_changed_files_recursive(fd, &st);
266 *-----------------------------------------------------------------------------
267 * file name, extension, sorting, ...
268 *-----------------------------------------------------------------------------
271 static void file_data_set_collate_keys(FileData *fd)
273 gchar *caseless_name;
276 valid_name = g_filename_display_name(fd->name);
277 caseless_name = g_utf8_casefold(valid_name, -1);
279 g_free(fd->collate_key_name);
280 g_free(fd->collate_key_name_nocase);
282 #if GTK_CHECK_VERSION(2, 8, 0)
283 if (options->file_sort.natural)
285 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
286 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
290 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
291 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
294 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
295 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
299 g_free(caseless_name);
302 static void file_data_set_path(FileData *fd, const gchar *path)
304 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
305 g_assert(file_data_pool);
309 if (fd->original_path)
311 g_hash_table_remove(file_data_pool, fd->original_path);
312 g_free(fd->original_path);
315 g_assert(!g_hash_table_lookup(file_data_pool, path));
317 fd->original_path = g_strdup(path);
318 g_hash_table_insert(file_data_pool, fd->original_path, fd);
320 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
322 fd->path = g_strdup(path);
324 fd->extension = fd->name + 1;
325 file_data_set_collate_keys(fd);
329 fd->path = g_strdup(path);
330 fd->name = filename_from_path(fd->path);
332 if (strcmp(fd->name, "..") == 0)
334 gchar *dir = remove_level_from_path(path);
336 fd->path = remove_level_from_path(dir);
339 fd->extension = fd->name + 2;
340 file_data_set_collate_keys(fd);
343 else if (strcmp(fd->name, ".") == 0)
346 fd->path = remove_level_from_path(path);
348 fd->extension = fd->name + 1;
349 file_data_set_collate_keys(fd);
353 fd->extension = registered_extension_from_path(fd->path);
354 if (fd->extension == NULL)
356 fd->extension = fd->name + strlen(fd->name);
359 fd->sidecar_priority = sidecar_file_priority(fd->extension);
360 file_data_set_collate_keys(fd);
364 *-----------------------------------------------------------------------------
365 * create or reuse Filedata
366 *-----------------------------------------------------------------------------
369 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
373 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
375 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
378 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
380 fd = g_hash_table_lookup(file_data_pool, path_utf8);
386 if (!fd && file_data_planned_change_hash)
388 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
391 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
392 if (!isfile(fd->path))
395 file_data_apply_ci(fd);
408 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
411 changed = file_data_check_changed_single_file(fd, st);
413 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
418 fd = g_new0(FileData, 1);
419 #ifdef DEBUG_FILEDATA
420 global_file_data_count++;
421 DEBUG_2("file data count++: %d", global_file_data_count);
424 fd->size = st->st_size;
425 fd->date = st->st_mtime;
426 fd->cdate = st->st_ctime;
427 fd->mode = st->st_mode;
429 fd->magick = FD_MAGICK;
431 if (disable_sidecars) fd->disable_grouping = TRUE;
433 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
438 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
440 gchar *path_utf8 = path_to_utf8(path);
441 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
447 FileData *file_data_new_simple(const gchar *path_utf8)
452 if (!stat_utf8(path_utf8, &st))
458 fd = g_hash_table_lookup(file_data_pool, path_utf8);
459 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
468 void init_exif_time_data(GList *files)
471 DEBUG_1("%s init_exif_time_data: ...", get_exec_time());
483 void read_exif_time_data(FileData *file)
485 if (file->exifdate > 0)
487 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
491 file->exif = exif_read_fd(file);
495 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
496 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), file, file->path);
501 uint year, month, day, hour, min, sec;
503 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
504 time_str.tm_year = year - 1900;
505 time_str.tm_mon = month - 1;
506 time_str.tm_mday = day;
507 time_str.tm_hour = hour;
508 time_str.tm_min = min;
509 time_str.tm_sec = sec;
510 time_str.tm_isdst = 0;
512 file->exifdate = mktime(&time_str);
518 void set_exif_time_data(GList *files)
520 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
524 FileData *file = files->data;
526 read_exif_time_data(file);
531 FileData *file_data_new_no_grouping(const gchar *path_utf8)
535 if (!stat_utf8(path_utf8, &st))
541 return file_data_new(path_utf8, &st, TRUE);
544 FileData *file_data_new_dir(const gchar *path_utf8)
548 if (!stat_utf8(path_utf8, &st))
554 /* dir or non-existing yet */
555 g_assert(S_ISDIR(st.st_mode));
557 return file_data_new(path_utf8, &st, TRUE);
561 *-----------------------------------------------------------------------------
563 *-----------------------------------------------------------------------------
566 #ifdef DEBUG_FILEDATA
567 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
569 FileData *file_data_ref(FileData *fd)
572 if (fd == NULL) return NULL;
573 if (fd->magick != FD_MAGICK)
574 #ifdef DEBUG_FILEDATA
575 DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
577 DEBUG_0("fd magick mismatch fd=%p", fd);
579 g_assert(fd->magick == FD_MAGICK);
582 #ifdef DEBUG_FILEDATA
583 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", fd, fd->ref, fd->path, file, line);
585 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
590 static void file_data_free(FileData *fd)
592 g_assert(fd->magick == FD_MAGICK);
593 g_assert(fd->ref == 0);
594 g_assert(!fd->locked);
596 #ifdef DEBUG_FILEDATA
597 global_file_data_count--;
598 DEBUG_2("file data count--: %d", global_file_data_count);
601 metadata_cache_free(fd);
602 g_hash_table_remove(file_data_pool, fd->original_path);
605 g_free(fd->original_path);
606 g_free(fd->collate_key_name);
607 g_free(fd->collate_key_name_nocase);
608 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
609 histmap_free(fd->histmap);
611 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
613 file_data_change_info_free(NULL, fd);
618 * \brief Checks if the FileData is referenced
620 * Checks the refcount and whether the FileData is locked.
622 static gboolean file_data_check_has_ref(FileData *fd)
624 return fd->ref > 0 || fd->locked;
628 * \brief Consider freeing a FileData.
630 * This function will free a FileData and its children provided that neither its parent nor it has
631 * a positive refcount, and provided that neither is locked.
633 static void file_data_consider_free(FileData *fd)
636 FileData *parent = fd->parent ? fd->parent : fd;
638 g_assert(fd->magick == FD_MAGICK);
639 if (file_data_check_has_ref(fd)) return;
640 if (file_data_check_has_ref(parent)) return;
642 work = parent->sidecar_files;
645 FileData *sfd = work->data;
646 if (file_data_check_has_ref(sfd)) return;
650 /* Neither the parent nor the siblings are referenced, so we can free everything */
651 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
652 fd->path, fd->parent ? parent->path : "-");
654 work = parent->sidecar_files;
657 FileData *sfd = work->data;
662 g_list_free(parent->sidecar_files);
663 parent->sidecar_files = NULL;
665 file_data_free(parent);
668 #ifdef DEBUG_FILEDATA
669 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
671 void file_data_unref(FileData *fd)
674 if (fd == NULL) return;
675 if (fd->magick != FD_MAGICK)
676 #ifdef DEBUG_FILEDATA
677 DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
679 DEBUG_0("fd magick mismatch fd=%p", fd);
681 g_assert(fd->magick == FD_MAGICK);
684 #ifdef DEBUG_FILEDATA
685 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", fd, fd->ref, fd->locked, fd->path,
688 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
691 // Free FileData if it's no longer ref'd
692 file_data_consider_free(fd);
696 * \brief Lock the FileData in memory.
698 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
699 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
700 * even if the code would continue to function properly even if the FileData were freed. Code that
701 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
703 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
704 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
706 void file_data_lock(FileData *fd)
708 if (fd == NULL) return;
709 if (fd->magick != FD_MAGICK) DEBUG_0("fd magick mismatch fd=%p", fd);
711 g_assert(fd->magick == FD_MAGICK);
714 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
718 * \brief Reset the maintain-FileData-in-memory lock
720 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
721 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
722 * keeping it from being freed.
724 void file_data_unlock(FileData *fd)
726 if (fd == NULL) return;
727 if (fd->magick != FD_MAGICK) DEBUG_0("fd magick mismatch fd=%p", fd);
729 g_assert(fd->magick == FD_MAGICK);
732 // Free FileData if it's no longer ref'd
733 file_data_consider_free(fd);
737 * \brief Lock all of the FileDatas in the provided list
739 * \see file_data_lock(FileData)
741 void file_data_lock_list(GList *list)
748 FileData *fd = work->data;
755 * \brief Unlock all of the FileDatas in the provided list
757 * \see file_data_unlock(FileData)
759 void file_data_unlock_list(GList *list)
766 FileData *fd = work->data;
768 file_data_unlock(fd);
773 *-----------------------------------------------------------------------------
774 * sidecar file info struct
775 *-----------------------------------------------------------------------------
778 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
780 const FileData *fda = a;
781 const FileData *fdb = b;
783 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
784 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
786 return strcmp(fdb->extension, fda->extension);
790 static gint sidecar_file_priority(const gchar *extension)
795 if (extension == NULL)
798 work = sidecar_ext_get_list();
801 gchar *ext = work->data;
804 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
810 static void file_data_check_sidecars(const GList *basename_list)
812 /* basename_list contains the new group - first is the parent, then sorted sidecars */
813 /* all files in the list have ref count > 0 */
816 GList *s_work, *new_sidecars;
819 if (!basename_list) return;
822 DEBUG_2("basename start");
823 work = basename_list;
826 FileData *fd = work->data;
828 g_assert(fd->magick == FD_MAGICK);
829 DEBUG_2("basename: %p %s", fd, fd->name);
832 g_assert(fd->parent->magick == FD_MAGICK);
833 DEBUG_2(" parent: %p", fd->parent);
835 s_work = fd->sidecar_files;
838 FileData *sfd = s_work->data;
839 s_work = s_work->next;
840 g_assert(sfd->magick == FD_MAGICK);
841 DEBUG_2(" sidecar: %p %s", sfd, sfd->name);
844 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
847 parent_fd = basename_list->data;
849 /* check if the second and next entries of basename_list are already connected
850 as sidecars of the first entry (parent_fd) */
851 work = basename_list->next;
852 s_work = parent_fd->sidecar_files;
854 while (work && s_work)
856 if (work->data != s_work->data) break;
858 s_work = s_work->next;
861 if (!work && !s_work)
863 DEBUG_2("basename no change");
864 return; /* no change in grouping */
867 /* we have to regroup it */
869 /* first, disconnect everything and send notification*/
871 work = basename_list;
874 FileData *fd = work->data;
876 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
880 FileData *old_parent = fd->parent;
881 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
882 file_data_ref(old_parent);
883 file_data_disconnect_sidecar_file(old_parent, fd);
884 file_data_send_notification(old_parent, NOTIFY_REREAD);
885 file_data_unref(old_parent);
888 while (fd->sidecar_files)
890 FileData *sfd = fd->sidecar_files->data;
891 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
893 file_data_disconnect_sidecar_file(fd, sfd);
894 file_data_send_notification(sfd, NOTIFY_REREAD);
895 file_data_unref(sfd);
897 file_data_send_notification(fd, NOTIFY_GROUPING);
899 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
902 /* now we can form the new group */
903 work = basename_list->next;
907 FileData *sfd = work->data;
908 g_assert(sfd->magick == FD_MAGICK);
909 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
910 sfd->parent = parent_fd;
911 new_sidecars = g_list_prepend(new_sidecars, sfd);
914 g_assert(parent_fd->sidecar_files == NULL);
915 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
916 DEBUG_1("basename group changed for %s", parent_fd->path);
920 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
922 g_assert(target->magick == FD_MAGICK);
923 g_assert(sfd->magick == FD_MAGICK);
924 g_assert(g_list_find(target->sidecar_files, sfd));
926 file_data_ref(target);
929 g_assert(sfd->parent == target);
931 file_data_increment_version(sfd); /* increments both sfd and target */
933 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
936 file_data_unref(target);
937 file_data_unref(sfd);
940 /* disables / enables grouping for particular file, sends UPDATE notification */
941 void file_data_disable_grouping(FileData *fd, gboolean disable)
943 if (!fd->disable_grouping == !disable) return;
945 fd->disable_grouping = !!disable;
951 FileData *parent = file_data_ref(fd->parent);
952 file_data_disconnect_sidecar_file(parent, fd);
953 file_data_send_notification(parent, NOTIFY_GROUPING);
954 file_data_unref(parent);
956 else if (fd->sidecar_files)
958 GList *sidecar_files = filelist_copy(fd->sidecar_files);
959 GList *work = sidecar_files;
962 FileData *sfd = work->data;
964 file_data_disconnect_sidecar_file(fd, sfd);
965 file_data_send_notification(sfd, NOTIFY_GROUPING);
967 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
968 filelist_free(sidecar_files);
972 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
977 file_data_increment_version(fd);
978 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
980 file_data_send_notification(fd, NOTIFY_GROUPING);
983 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
990 FileData *fd = work->data;
992 file_data_disable_grouping(fd, disable);
1000 *-----------------------------------------------------------------------------
1002 *-----------------------------------------------------------------------------
1006 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
1009 if (!filelist_sort_ascend)
1016 switch (filelist_sort_method)
1021 if (fa->size < fb->size) return -1;
1022 if (fa->size > fb->size) return 1;
1023 /* fall back to name */
1026 if (fa->date < fb->date) return -1;
1027 if (fa->date > fb->date) return 1;
1028 /* fall back to name */
1031 if (fa->cdate < fb->cdate) return -1;
1032 if (fa->cdate > fb->cdate) return 1;
1033 /* fall back to name */
1036 if (fa->exifdate < fb->exifdate) return -1;
1037 if (fa->exifdate > fb->exifdate) return 1;
1038 /* fall back to name */
1040 #ifdef HAVE_STRVERSCMP
1042 ret = strverscmp(fa->name, fb->name);
1043 if (ret != 0) return ret;
1050 if (options->file_sort.case_sensitive)
1051 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1053 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1055 if (ret != 0) return ret;
1057 /* do not return 0 unless the files are really the same
1058 file_data_pool ensures that original_path is unique
1060 return strcmp(fa->original_path, fb->original_path);
1063 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1065 filelist_sort_method = method;
1066 filelist_sort_ascend = ascend;
1067 return filelist_sort_compare_filedata(fa, fb);
1070 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1072 return filelist_sort_compare_filedata(a, b);
1075 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1077 filelist_sort_method = method;
1078 filelist_sort_ascend = ascend;
1079 return g_list_sort(list, cb);
1082 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1084 filelist_sort_method = method;
1085 filelist_sort_ascend = ascend;
1086 return g_list_insert_sorted(list, data, cb);
1089 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1091 if (method == SORT_EXIFTIME)
1093 set_exif_time_data(list);
1095 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1098 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
1100 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1104 *-----------------------------------------------------------------------------
1105 * basename hash - grouping of sidecars in filelist
1106 *-----------------------------------------------------------------------------
1110 static GHashTable *file_data_basename_hash_new(void)
1112 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1115 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1118 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1120 list = g_hash_table_lookup(basename_hash, basename);
1122 if (!g_list_find(list, fd))
1124 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1125 g_hash_table_insert(basename_hash, basename, list);
1134 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
1136 filelist_free((GList *)value);
1139 static void file_data_basename_hash_free(GHashTable *basename_hash)
1141 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1142 g_hash_table_destroy(basename_hash);
1146 *-----------------------------------------------------------------------------
1147 * handling sidecars in filelist
1148 *-----------------------------------------------------------------------------
1151 static GList *filelist_filter_out_sidecars(GList *flist)
1153 GList *work = flist;
1154 GList *flist_filtered = NULL;
1158 FileData *fd = work->data;
1161 if (fd->parent) /* remove fd's that are children */
1162 file_data_unref(fd);
1164 flist_filtered = g_list_prepend(flist_filtered, fd);
1168 return flist_filtered;
1171 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
1173 GList *basename_list = (GList *)value;
1174 file_data_check_sidecars(basename_list);
1178 static gboolean is_hidden_file(const gchar *name)
1180 if (name[0] != '.') return FALSE;
1181 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1186 *-----------------------------------------------------------------------------
1187 * the main filelist function
1188 *-----------------------------------------------------------------------------
1191 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1196 GList *dlist = NULL;
1197 GList *flist = NULL;
1198 gint (*stat_func)(const gchar *path, struct stat *buf);
1199 GHashTable *basename_hash = NULL;
1201 g_assert(files || dirs);
1203 if (files) *files = NULL;
1204 if (dirs) *dirs = NULL;
1206 pathl = path_from_utf8(dir_path);
1207 if (!pathl) return FALSE;
1209 dp = opendir(pathl);
1216 if (files) basename_hash = file_data_basename_hash_new();
1218 if (follow_symlinks)
1223 while ((dir = readdir(dp)) != NULL)
1225 struct stat ent_sbuf;
1226 const gchar *name = dir->d_name;
1229 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1232 filepath = g_build_filename(pathl, name, NULL);
1233 if (stat_func(filepath, &ent_sbuf) >= 0)
1235 if (S_ISDIR(ent_sbuf.st_mode))
1237 /* we ignore the .thumbnails dir for cleanliness */
1239 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1240 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1241 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1242 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1244 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1249 if (files && filter_name_exists(name))
1251 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1252 flist = g_list_prepend(flist, fd);
1253 if (fd->sidecar_priority && !fd->disable_grouping)
1255 file_data_basename_hash_insert(basename_hash, fd);
1262 if (errno == EOVERFLOW)
1264 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1274 if (dirs) *dirs = dlist;
1278 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1280 *files = filelist_filter_out_sidecars(flist);
1282 if (basename_hash) file_data_basename_hash_free(basename_hash);
1284 // Call a separate function to initialize the exif datestamps for the found files..
1285 if (files) init_exif_time_data(*files);
1290 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1292 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1295 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1297 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1300 FileData *file_data_new_group(const gchar *path_utf8)
1307 if (!stat_utf8(path_utf8, &st))
1313 if (S_ISDIR(st.st_mode))
1314 return file_data_new(path_utf8, &st, TRUE);
1316 dir = remove_level_from_path(path_utf8);
1318 filelist_read_real(dir, &files, NULL, TRUE);
1320 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1321 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1327 filelist_free(files);
1333 void filelist_free(GList *list)
1340 file_data_unref((FileData *)work->data);
1348 GList *filelist_copy(GList *list)
1350 GList *new_list = NULL;
1361 new_list = g_list_prepend(new_list, file_data_ref(fd));
1364 return g_list_reverse(new_list);
1367 GList *filelist_from_path_list(GList *list)
1369 GList *new_list = NULL;
1380 new_list = g_list_prepend(new_list, file_data_new_group(path));
1383 return g_list_reverse(new_list);
1386 GList *filelist_to_path_list(GList *list)
1388 GList *new_list = NULL;
1399 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1402 return g_list_reverse(new_list);
1405 GList *filelist_filter(GList *list, gboolean is_dir_list)
1409 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1414 FileData *fd = (FileData *)(work->data);
1415 const gchar *name = fd->name;
1417 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1418 (!is_dir_list && !filter_name_exists(name)) ||
1419 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1420 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1424 list = g_list_remove_link(list, link);
1425 file_data_unref(fd);
1436 *-----------------------------------------------------------------------------
1437 * filelist recursive
1438 *-----------------------------------------------------------------------------
1441 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1443 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1446 GList *filelist_sort_path(GList *list)
1448 return g_list_sort(list, filelist_sort_path_cb);
1451 static void filelist_recursive_append(GList **list, GList *dirs)
1458 FileData *fd = (FileData *)(work->data);
1462 if (filelist_read(fd, &f, &d))
1464 f = filelist_filter(f, FALSE);
1465 f = filelist_sort_path(f);
1466 *list = g_list_concat(*list, f);
1468 d = filelist_filter(d, TRUE);
1469 d = filelist_sort_path(d);
1470 filelist_recursive_append(list, d);
1478 GList *filelist_recursive(FileData *dir_fd)
1483 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1484 list = filelist_filter(list, FALSE);
1485 list = filelist_sort_path(list);
1487 d = filelist_filter(d, TRUE);
1488 d = filelist_sort_path(d);
1489 filelist_recursive_append(&list, d);
1496 *-----------------------------------------------------------------------------
1497 * file modification support
1498 *-----------------------------------------------------------------------------
1502 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1504 if (!fdci && fd) fdci = fd->change;
1508 g_free(fdci->source);
1513 if (fd) fd->change = NULL;
1516 static gboolean file_data_can_write_directly(FileData *fd)
1518 return filter_name_is_writable(fd->extension);
1521 static gboolean file_data_can_write_sidecar(FileData *fd)
1523 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1526 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1528 gchar *sidecar_path = NULL;
1531 if (!file_data_can_write_sidecar(fd)) return NULL;
1533 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1536 FileData *sfd = work->data;
1538 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
1540 sidecar_path = g_strdup(sfd->path);
1545 if (!existing_only && !sidecar_path)
1547 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1548 sidecar_path = g_strconcat(base, ".xmp", NULL);
1552 return sidecar_path;
1556 * marks and orientation
1559 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1560 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1561 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1562 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1564 gboolean file_data_get_mark(FileData *fd, gint n)
1566 gboolean valid = (fd->valid_marks & (1 << n));
1568 if (file_data_get_mark_func[n] && !valid)
1570 guint old = fd->marks;
1571 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1573 if (!value != !(fd->marks & (1 << n)))
1575 fd->marks = fd->marks ^ (1 << n);
1578 fd->valid_marks |= (1 << n);
1579 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1581 file_data_unref(fd);
1583 else if (!old && fd->marks)
1589 return !!(fd->marks & (1 << n));
1592 guint file_data_get_marks(FileData *fd)
1595 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1599 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1602 if (!value == !file_data_get_mark(fd, n)) return;
1604 if (file_data_set_mark_func[n])
1606 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1611 fd->marks = fd->marks ^ (1 << n);
1613 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1615 file_data_unref(fd);
1617 else if (!old && fd->marks)
1622 file_data_increment_version(fd);
1623 file_data_send_notification(fd, NOTIFY_MARKS);
1626 gboolean file_data_filter_marks(FileData *fd, guint filter)
1629 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1630 return ((fd->marks & filter) == filter);
1633 GList *file_data_filter_marks_list(GList *list, guint filter)
1640 FileData *fd = work->data;
1644 if (!file_data_filter_marks(fd, filter))
1646 list = g_list_remove_link(list, link);
1647 file_data_unref(fd);
1655 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1657 FileData *fd = value;
1658 file_data_increment_version(fd);
1659 file_data_send_notification(fd, NOTIFY_MARKS);
1662 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1664 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1666 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1668 file_data_get_mark_func[n] = get_mark_func;
1669 file_data_set_mark_func[n] = set_mark_func;
1670 file_data_mark_func_data[n] = data;
1671 file_data_destroy_mark_func[n] = notify;
1675 /* this effectively changes all known files */
1676 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1682 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1684 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1685 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1686 if (data) *data = file_data_mark_func_data[n];
1689 gint file_data_get_user_orientation(FileData *fd)
1691 return fd->user_orientation;
1694 void file_data_set_user_orientation(FileData *fd, gint value)
1696 if (fd->user_orientation == value) return;
1698 fd->user_orientation = value;
1699 file_data_increment_version(fd);
1700 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1705 * file_data - operates on the given fd
1706 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1710 /* return list of sidecar file extensions in a string */
1711 gchar *file_data_sc_list_to_string(FileData *fd)
1714 GString *result = g_string_new("");
1716 work = fd->sidecar_files;
1719 FileData *sfd = work->data;
1721 result = g_string_append(result, "+ ");
1722 result = g_string_append(result, sfd->extension);
1724 if (work) result = g_string_append_c(result, ' ');
1727 return g_string_free(result, FALSE);
1733 * add FileDataChangeInfo (see typedefs.h) for the given operation
1734 * uses file_data_add_change_info
1736 * fails if the fd->change already exists - change operations can't run in parallel
1737 * fd->change_info works as a lock
1739 * dest can be NULL - in this case the current name is used for now, it will
1744 FileDataChangeInfo types:
1746 MOVE - path is changed, name may be changed too
1747 RENAME - path remains unchanged, name is changed
1748 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1749 sidecar names are changed too, extensions are not changed
1751 UPDATE - file size, date or grouping has been changed
1754 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1756 FileDataChangeInfo *fdci;
1758 if (fd->change) return FALSE;
1760 fdci = g_new0(FileDataChangeInfo, 1);
1765 fdci->source = g_strdup(src);
1767 fdci->source = g_strdup(fd->path);
1770 fdci->dest = g_strdup(dest);
1777 static void file_data_planned_change_remove(FileData *fd)
1779 if (file_data_planned_change_hash &&
1780 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1782 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1784 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1785 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1786 file_data_unref(fd);
1787 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1789 g_hash_table_destroy(file_data_planned_change_hash);
1790 file_data_planned_change_hash = NULL;
1791 DEBUG_1("planned change: empty");
1798 void file_data_free_ci(FileData *fd)
1800 FileDataChangeInfo *fdci = fd->change;
1804 file_data_planned_change_remove(fd);
1806 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1808 g_free(fdci->source);
1816 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1818 FileDataChangeInfo *fdci = fd->change;
1820 fdci->regroup_when_finished = enable;
1823 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1827 if (fd->parent) fd = fd->parent;
1829 if (fd->change) return FALSE;
1831 work = fd->sidecar_files;
1834 FileData *sfd = work->data;
1836 if (sfd->change) return FALSE;
1840 file_data_add_ci(fd, type, NULL, NULL);
1842 work = fd->sidecar_files;
1845 FileData *sfd = work->data;
1847 file_data_add_ci(sfd, type, NULL, NULL);
1854 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1858 if (fd->parent) fd = fd->parent;
1860 if (!fd->change || fd->change->type != type) return FALSE;
1862 work = fd->sidecar_files;
1865 FileData *sfd = work->data;
1867 if (!sfd->change || sfd->change->type != type) return FALSE;
1875 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1877 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1878 file_data_sc_update_ci_copy(fd, dest_path);
1882 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1884 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1885 file_data_sc_update_ci_move(fd, dest_path);
1889 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1891 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1892 file_data_sc_update_ci_rename(fd, dest_path);
1896 gboolean file_data_sc_add_ci_delete(FileData *fd)
1898 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1901 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1903 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1904 file_data_sc_update_ci_unspecified(fd, dest_path);
1908 gboolean file_data_add_ci_write_metadata(FileData *fd)
1910 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1913 void file_data_sc_free_ci(FileData *fd)
1917 if (fd->parent) fd = fd->parent;
1919 file_data_free_ci(fd);
1921 work = fd->sidecar_files;
1924 FileData *sfd = work->data;
1926 file_data_free_ci(sfd);
1931 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1934 gboolean ret = TRUE;
1939 FileData *fd = work->data;
1941 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1948 static void file_data_sc_revert_ci_list(GList *fd_list)
1955 FileData *fd = work->data;
1957 file_data_sc_free_ci(fd);
1962 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1969 FileData *fd = work->data;
1971 if (!func(fd, dest))
1973 file_data_sc_revert_ci_list(work->prev);
1982 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1984 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1987 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1989 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1992 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1994 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1997 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1999 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2002 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2005 gboolean ret = TRUE;
2010 FileData *fd = work->data;
2012 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2019 void file_data_free_ci_list(GList *fd_list)
2026 FileData *fd = work->data;
2028 file_data_free_ci(fd);
2033 void file_data_sc_free_ci_list(GList *fd_list)
2040 FileData *fd = work->data;
2042 file_data_sc_free_ci(fd);
2048 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2049 * fails if fd->change does not exist or the change type does not match
2052 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2054 FileDataChangeType type = fd->change->type;
2056 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2060 if (!file_data_planned_change_hash)
2061 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2063 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2065 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2066 g_hash_table_remove(file_data_planned_change_hash, old_path);
2067 file_data_unref(fd);
2070 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
2075 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2076 g_hash_table_remove(file_data_planned_change_hash, new_path);
2077 file_data_unref(ofd);
2080 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2082 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2087 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2089 gchar *old_path = fd->change->dest;
2091 fd->change->dest = g_strdup(dest_path);
2092 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2096 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2098 const gchar *extension = extension_from_path(fd->change->source);
2099 gchar *base = remove_extension_from_path(dest_path);
2100 gchar *old_path = fd->change->dest;
2102 fd->change->dest = g_strconcat(base, extension, NULL);
2103 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2109 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2112 gchar *dest_path_full = NULL;
2114 if (fd->parent) fd = fd->parent;
2118 dest_path = fd->path;
2120 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2122 gchar *dir = remove_level_from_path(fd->path);
2124 dest_path_full = g_build_filename(dir, dest_path, NULL);
2126 dest_path = dest_path_full;
2128 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2130 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2131 dest_path = dest_path_full;
2134 file_data_update_ci_dest(fd, dest_path);
2136 work = fd->sidecar_files;
2139 FileData *sfd = work->data;
2141 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2145 g_free(dest_path_full);
2148 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2150 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2151 file_data_sc_update_ci(fd, dest_path);
2155 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2157 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2160 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2162 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2165 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2167 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2170 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2172 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2175 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2177 gboolean (*func)(FileData *, const gchar *))
2180 gboolean ret = TRUE;
2185 FileData *fd = work->data;
2187 if (!func(fd, dest)) ret = FALSE;
2194 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2196 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2199 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2201 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2204 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2206 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2211 * verify source and dest paths - dest image exists, etc.
2212 * it should detect all possible problems with the planned operation
2215 gint file_data_verify_ci(FileData *fd, GList *list)
2217 gint ret = CHANGE_OK;
2220 FileData *fd1 = NULL;
2224 DEBUG_1("Change checked: no change info: %s", fd->path);
2228 if (!isname(fd->path))
2230 /* this probably should not happen */
2231 ret |= CHANGE_NO_SRC;
2232 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2236 dir = remove_level_from_path(fd->path);
2238 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2239 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2240 fd->change->type != FILEDATA_CHANGE_RENAME &&
2241 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2244 ret |= CHANGE_WARN_UNSAVED_META;
2245 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2248 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2249 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2250 !access_file(fd->path, R_OK))
2252 ret |= CHANGE_NO_READ_PERM;
2253 DEBUG_1("Change checked: no read permission: %s", fd->path);
2255 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2256 !access_file(dir, W_OK))
2258 ret |= CHANGE_NO_WRITE_PERM_DIR;
2259 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2261 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2262 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2263 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2264 !access_file(fd->path, W_OK))
2266 ret |= CHANGE_WARN_NO_WRITE_PERM;
2267 DEBUG_1("Change checked: no write permission: %s", fd->path);
2269 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2270 - that means that there are no hard errors and warnings can be disabled
2271 - the destination is determined during the check
2273 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2275 /* determine destination file */
2276 gboolean have_dest = FALSE;
2277 gchar *dest_dir = NULL;
2279 if (options->metadata.save_in_image_file)
2281 if (file_data_can_write_directly(fd))
2283 /* we can write the file directly */
2284 if (access_file(fd->path, W_OK))
2290 if (options->metadata.warn_on_write_problems)
2292 ret |= CHANGE_WARN_NO_WRITE_PERM;
2293 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2297 else if (file_data_can_write_sidecar(fd))
2299 /* we can write sidecar */
2300 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2301 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2303 file_data_update_ci_dest(fd, sidecar);
2308 if (options->metadata.warn_on_write_problems)
2310 ret |= CHANGE_WARN_NO_WRITE_PERM;
2311 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2320 /* write private metadata file under ~/.geeqie */
2322 /* If an existing metadata file exists, we will try writing to
2323 * it's location regardless of the user's preference.
2325 gchar *metadata_path = NULL;
2327 /* but ignore XMP if we are not able to write it */
2328 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2330 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2332 if (metadata_path && !access_file(metadata_path, W_OK))
2334 g_free(metadata_path);
2335 metadata_path = NULL;
2342 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2343 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2345 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2347 metadata_path = g_build_filename(dest_dir, filename, NULL);
2351 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2353 file_data_update_ci_dest(fd, metadata_path);
2358 ret |= CHANGE_NO_WRITE_PERM_DEST;
2359 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2361 g_free(metadata_path);
2366 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2371 same = (strcmp(fd->path, fd->change->dest) == 0);
2375 const gchar *dest_ext = extension_from_path(fd->change->dest);
2376 if (!dest_ext) dest_ext = "";
2377 if (!options->file_filter.disable_file_extension_checks)
2379 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2381 ret |= CHANGE_WARN_CHANGED_EXT;
2382 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2388 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2390 ret |= CHANGE_WARN_SAME;
2391 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2395 dest_dir = remove_level_from_path(fd->change->dest);
2397 if (!isdir(dest_dir))
2399 ret |= CHANGE_NO_DEST_DIR;
2400 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2402 else if (!access_file(dest_dir, W_OK))
2404 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2405 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2409 if (isfile(fd->change->dest))
2411 if (!access_file(fd->change->dest, W_OK))
2413 ret |= CHANGE_NO_WRITE_PERM_DEST;
2414 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2418 ret |= CHANGE_WARN_DEST_EXISTS;
2419 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2422 else if (isdir(fd->change->dest))
2424 ret |= CHANGE_DEST_EXISTS;
2425 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2432 /* During a rename operation, check if another planned destination file has
2435 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2436 fd->change->type == FILEDATA_CHANGE_COPY ||
2437 fd->change->type == FILEDATA_CHANGE_MOVE)
2444 if (fd1 != NULL && fd != fd1 )
2446 if (!strcmp(fd->change->dest, fd1->change->dest))
2448 ret |= CHANGE_DUPLICATE_DEST;
2454 fd->change->error = ret;
2455 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2462 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2467 ret = file_data_verify_ci(fd, list);
2469 work = fd->sidecar_files;
2472 FileData *sfd = work->data;
2474 ret |= file_data_verify_ci(sfd, list);
2481 gchar *file_data_get_error_string(gint error)
2483 GString *result = g_string_new("");
2485 if (error & CHANGE_NO_SRC)
2487 if (result->len > 0) g_string_append(result, ", ");
2488 g_string_append(result, _("file or directory does not exist"));
2491 if (error & CHANGE_DEST_EXISTS)
2493 if (result->len > 0) g_string_append(result, ", ");
2494 g_string_append(result, _("destination already exists"));
2497 if (error & CHANGE_NO_WRITE_PERM_DEST)
2499 if (result->len > 0) g_string_append(result, ", ");
2500 g_string_append(result, _("destination can't be overwritten"));
2503 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2505 if (result->len > 0) g_string_append(result, ", ");
2506 g_string_append(result, _("destination directory is not writable"));
2509 if (error & CHANGE_NO_DEST_DIR)
2511 if (result->len > 0) g_string_append(result, ", ");
2512 g_string_append(result, _("destination directory does not exist"));
2515 if (error & CHANGE_NO_WRITE_PERM_DIR)
2517 if (result->len > 0) g_string_append(result, ", ");
2518 g_string_append(result, _("source directory is not writable"));
2521 if (error & CHANGE_NO_READ_PERM)
2523 if (result->len > 0) g_string_append(result, ", ");
2524 g_string_append(result, _("no read permission"));
2527 if (error & CHANGE_WARN_NO_WRITE_PERM)
2529 if (result->len > 0) g_string_append(result, ", ");
2530 g_string_append(result, _("file is readonly"));
2533 if (error & CHANGE_WARN_DEST_EXISTS)
2535 if (result->len > 0) g_string_append(result, ", ");
2536 g_string_append(result, _("destination already exists and will be overwritten"));
2539 if (error & CHANGE_WARN_SAME)
2541 if (result->len > 0) g_string_append(result, ", ");
2542 g_string_append(result, _("source and destination are the same"));
2545 if (error & CHANGE_WARN_CHANGED_EXT)
2547 if (result->len > 0) g_string_append(result, ", ");
2548 g_string_append(result, _("source and destination have different extension"));
2551 if (error & CHANGE_WARN_UNSAVED_META)
2553 if (result->len > 0) g_string_append(result, ", ");
2554 g_string_append(result, _("there are unsaved metadata changes for the file"));
2557 if (error & CHANGE_DUPLICATE_DEST)
2559 if (result->len > 0) g_string_append(result, ", ");
2560 g_string_append(result, _("another destination file has the same filename"));
2563 return g_string_free(result, FALSE);
2566 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2569 gint all_errors = 0;
2570 gint common_errors = ~0;
2575 if (!list) return 0;
2577 num = g_list_length(list);
2578 errors = g_new(int, num);
2589 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2590 all_errors |= error;
2591 common_errors &= error;
2598 if (desc && all_errors)
2601 GString *result = g_string_new("");
2605 gchar *str = file_data_get_error_string(common_errors);
2606 g_string_append(result, str);
2607 g_string_append(result, "\n");
2621 error = errors[i] & ~common_errors;
2625 gchar *str = file_data_get_error_string(error);
2626 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2631 *desc = g_string_free(result, FALSE);
2640 * perform the change described by FileFataChangeInfo
2641 * it is used for internal operations,
2642 * this function actually operates with files on the filesystem
2643 * it should implement safe delete
2646 static gboolean file_data_perform_move(FileData *fd)
2648 g_assert(!strcmp(fd->change->source, fd->path));
2649 return move_file(fd->change->source, fd->change->dest);
2652 static gboolean file_data_perform_copy(FileData *fd)
2654 g_assert(!strcmp(fd->change->source, fd->path));
2655 return copy_file(fd->change->source, fd->change->dest);
2658 static gboolean file_data_perform_delete(FileData *fd)
2660 if (isdir(fd->path) && !islink(fd->path))
2661 return rmdir_utf8(fd->path);
2663 if (options->file_ops.safe_delete_enable)
2664 return file_util_safe_unlink(fd->path);
2666 return unlink_file(fd->path);
2669 gboolean file_data_perform_ci(FileData *fd)
2671 FileDataChangeType type = fd->change->type;
2675 case FILEDATA_CHANGE_MOVE:
2676 return file_data_perform_move(fd);
2677 case FILEDATA_CHANGE_COPY:
2678 return file_data_perform_copy(fd);
2679 case FILEDATA_CHANGE_RENAME:
2680 return file_data_perform_move(fd); /* the same as move */
2681 case FILEDATA_CHANGE_DELETE:
2682 return file_data_perform_delete(fd);
2683 case FILEDATA_CHANGE_WRITE_METADATA:
2684 return metadata_write_perform(fd);
2685 case FILEDATA_CHANGE_UNSPECIFIED:
2686 /* nothing to do here */
2694 gboolean file_data_sc_perform_ci(FileData *fd)
2697 gboolean ret = TRUE;
2698 FileDataChangeType type = fd->change->type;
2700 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2702 work = fd->sidecar_files;
2705 FileData *sfd = work->data;
2707 if (!file_data_perform_ci(sfd)) ret = FALSE;
2711 if (!file_data_perform_ci(fd)) ret = FALSE;
2717 * updates FileData structure according to FileDataChangeInfo
2720 gboolean file_data_apply_ci(FileData *fd)
2722 FileDataChangeType type = fd->change->type;
2725 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2727 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2728 file_data_planned_change_remove(fd);
2730 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2732 /* this change overwrites another file which is already known to other modules
2733 renaming fd would create duplicate FileData structure
2734 the best thing we can do is nothing
2735 FIXME: maybe we could copy stuff like marks
2737 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2741 file_data_set_path(fd, fd->change->dest);
2744 file_data_increment_version(fd);
2745 file_data_send_notification(fd, NOTIFY_CHANGE);
2750 gboolean file_data_sc_apply_ci(FileData *fd)
2753 FileDataChangeType type = fd->change->type;
2755 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2757 work = fd->sidecar_files;
2760 FileData *sfd = work->data;
2762 file_data_apply_ci(sfd);
2766 file_data_apply_ci(fd);
2771 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2774 if (fd->parent) fd = fd->parent;
2775 if (!g_list_find(list, fd)) return FALSE;
2777 work = fd->sidecar_files;
2780 if (!g_list_find(list, work->data)) return FALSE;
2786 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2791 /* change partial groups to independent files */
2796 FileData *fd = work->data;
2799 if (!file_data_list_contains_whole_group(list, fd))
2801 file_data_disable_grouping(fd, TRUE);
2804 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2810 /* remove sidecars from the list,
2811 they can be still acessed via main_fd->sidecar_files */
2815 FileData *fd = work->data;
2819 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2821 out = g_list_prepend(out, file_data_ref(fd));
2825 filelist_free(list);
2826 out = g_list_reverse(out);
2836 * notify other modules about the change described by FileDataChangeInfo
2839 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2840 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2841 implementation in view_file_list.c */
2844 typedef struct _NotifyIdleData NotifyIdleData;
2846 struct _NotifyIdleData {
2852 typedef struct _NotifyData NotifyData;
2854 struct _NotifyData {
2855 FileDataNotifyFunc func;
2857 NotifyPriority priority;
2860 static GList *notify_func_list = NULL;
2862 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2864 NotifyData *nda = (NotifyData *)a;
2865 NotifyData *ndb = (NotifyData *)b;
2867 if (nda->priority < ndb->priority) return -1;
2868 if (nda->priority > ndb->priority) return 1;
2872 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2875 GList *work = notify_func_list;
2879 NotifyData *nd = (NotifyData *)work->data;
2881 if (nd->func == func && nd->data == data)
2883 g_warning("Notify func already registered");
2889 nd = g_new(NotifyData, 1);
2892 nd->priority = priority;
2894 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2895 DEBUG_2("Notify func registered: %p", nd);
2900 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2902 GList *work = notify_func_list;
2906 NotifyData *nd = (NotifyData *)work->data;
2908 if (nd->func == func && nd->data == data)
2910 notify_func_list = g_list_delete_link(notify_func_list, work);
2912 DEBUG_2("Notify func unregistered: %p", nd);
2918 g_warning("Notify func not found");
2923 gboolean file_data_send_notification_idle_cb(gpointer data)
2925 NotifyIdleData *nid = (NotifyIdleData *)data;
2926 GList *work = notify_func_list;
2930 NotifyData *nd = (NotifyData *)work->data;
2932 nd->func(nid->fd, nid->type, nd->data);
2935 file_data_unref(nid->fd);
2940 void file_data_send_notification(FileData *fd, NotifyType type)
2942 GList *work = notify_func_list;
2946 NotifyData *nd = (NotifyData *)work->data;
2948 nd->func(fd, type, nd->data);
2952 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
2953 nid->fd = file_data_ref(fd);
2955 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
2959 static GHashTable *file_data_monitor_pool = NULL;
2960 static guint realtime_monitor_id = 0; /* event source id */
2962 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2966 file_data_check_changed_files(fd);
2968 DEBUG_1("monitor %s", fd->path);
2971 static gboolean realtime_monitor_cb(gpointer data)
2973 if (!options->update_on_time_change) return TRUE;
2974 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2978 gboolean file_data_register_real_time_monitor(FileData *fd)
2984 if (!file_data_monitor_pool)
2985 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2987 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2989 DEBUG_1("Register realtime %d %s", count, fd->path);
2992 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2994 if (!realtime_monitor_id)
2996 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
3002 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3006 g_assert(file_data_monitor_pool);
3008 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3010 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3012 g_assert(count > 0);
3017 g_hash_table_remove(file_data_monitor_pool, fd);
3019 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3021 file_data_unref(fd);
3023 if (g_hash_table_size(file_data_monitor_pool) == 0)
3025 g_source_remove(realtime_monitor_id);
3026 realtime_monitor_id = 0;
3032 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */