4 * Copyright (C) 2008 - 2012 The Geeqie Team
8 * This software is released under the GNU General Public License (GNU GPL).
9 * Please read the included file COPYING for more information.
10 * This software comes with no warranty of any kind, use at your own risk!
17 #include "filefilter.h"
19 #include "thumb_standard.h"
20 #include "ui_fileops.h"
23 #include "histogram.h"
30 gint global_file_data_count = 0;
33 static GHashTable *file_data_pool = NULL;
34 static GHashTable *file_data_planned_change_hash = NULL;
36 static gint sidecar_file_priority(const gchar *extension);
37 static void file_data_check_sidecars(const GList *basename_list);
38 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
41 static SortType filelist_sort_method = SORT_NONE;
42 static gboolean filelist_sort_ascend = TRUE;
45 *-----------------------------------------------------------------------------
46 * text conversion utils
47 *-----------------------------------------------------------------------------
50 gchar *text_from_size(gint64 size)
56 /* what I would like to use is printf("%'d", size)
57 * BUT: not supported on every libc :(
61 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
62 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
66 a = g_strdup_printf("%d", (guint)size);
72 b = g_new(gchar, l + n + 1);
97 gchar *text_from_size_abrev(gint64 size)
99 if (size < (gint64)1024)
101 return g_strdup_printf(_("%d bytes"), (gint)size);
103 if (size < (gint64)1048576)
105 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
107 if (size < (gint64)1073741824)
109 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
112 /* to avoid overflowing the gdouble, do division in two steps */
114 return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
117 /* note: returned string is valid until next call to text_from_time() */
118 const gchar *text_from_time(time_t t)
120 static gchar *ret = NULL;
124 GError *error = NULL;
126 btime = localtime(&t);
128 /* the %x warning about 2 digit years is not an error */
129 buflen = strftime(buf, sizeof(buf), "%x %X", btime);
130 if (buflen < 1) return "";
133 ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
136 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
145 *-----------------------------------------------------------------------------
146 * changed files detection and notification
147 *-----------------------------------------------------------------------------
150 void file_data_increment_version(FileData *fd)
156 fd->parent->version++;
157 fd->parent->valid_marks = 0;
161 static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st)
163 if (fd->size != st->st_size ||
164 fd->date != st->st_mtime)
166 fd->size = st->st_size;
167 fd->date = st->st_mtime;
168 fd->mode = st->st_mode;
169 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
170 fd->thumb_pixbuf = NULL;
171 file_data_increment_version(fd);
172 file_data_send_notification(fd, NOTIFY_REREAD);
178 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
180 gboolean ret = FALSE;
183 ret = file_data_check_changed_single_file(fd, st);
185 work = fd->sidecar_files;
188 FileData *sfd = work->data;
192 if (!stat_utf8(sfd->path, &st))
197 file_data_disconnect_sidecar_file(fd, sfd);
199 file_data_increment_version(sfd);
200 file_data_send_notification(sfd, NOTIFY_REREAD);
201 file_data_unref(sfd);
205 ret |= file_data_check_changed_files_recursive(sfd, &st);
211 gboolean file_data_check_changed_files(FileData *fd)
213 gboolean ret = FALSE;
216 if (fd->parent) fd = fd->parent;
218 if (!stat_utf8(fd->path, &st))
222 FileData *sfd = NULL;
224 /* parent is missing, we have to rebuild whole group */
229 /* file_data_disconnect_sidecar_file might delete the file,
230 we have to keep the reference to prevent this */
231 sidecars = filelist_copy(fd->sidecar_files);
239 file_data_disconnect_sidecar_file(fd, sfd);
241 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
242 /* now we can release the sidecars */
243 filelist_free(sidecars);
244 file_data_increment_version(fd);
245 file_data_send_notification(fd, NOTIFY_REREAD);
250 ret |= file_data_check_changed_files_recursive(fd, &st);
257 *-----------------------------------------------------------------------------
258 * file name, extension, sorting, ...
259 *-----------------------------------------------------------------------------
262 static void file_data_set_collate_keys(FileData *fd)
264 gchar *caseless_name;
267 valid_name = g_filename_display_name(fd->name);
268 caseless_name = g_utf8_casefold(valid_name, -1);
270 g_free(fd->collate_key_name);
271 g_free(fd->collate_key_name_nocase);
273 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
274 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
277 g_free(caseless_name);
280 static void file_data_set_path(FileData *fd, const gchar *path)
282 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
283 g_assert(file_data_pool);
287 if (fd->original_path)
289 g_hash_table_remove(file_data_pool, fd->original_path);
290 g_free(fd->original_path);
293 g_assert(!g_hash_table_lookup(file_data_pool, path));
295 fd->original_path = g_strdup(path);
296 g_hash_table_insert(file_data_pool, fd->original_path, fd);
298 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
300 fd->path = g_strdup(path);
302 fd->extension = fd->name + 1;
303 file_data_set_collate_keys(fd);
307 fd->path = g_strdup(path);
308 fd->name = filename_from_path(fd->path);
310 if (strcmp(fd->name, "..") == 0)
312 gchar *dir = remove_level_from_path(path);
314 fd->path = remove_level_from_path(dir);
317 fd->extension = fd->name + 2;
318 file_data_set_collate_keys(fd);
321 else if (strcmp(fd->name, ".") == 0)
324 fd->path = remove_level_from_path(path);
326 fd->extension = fd->name + 1;
327 file_data_set_collate_keys(fd);
331 fd->extension = registered_extension_from_path(fd->path);
332 if (fd->extension == NULL)
334 fd->extension = fd->name + strlen(fd->name);
337 fd->sidecar_priority = sidecar_file_priority(fd->extension);
338 file_data_set_collate_keys(fd);
342 *-----------------------------------------------------------------------------
343 * create or reuse Filedata
344 *-----------------------------------------------------------------------------
347 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
351 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
353 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
356 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
358 fd = g_hash_table_lookup(file_data_pool, path_utf8);
364 if (!fd && file_data_planned_change_hash)
366 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
369 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
371 file_data_apply_ci(fd);
379 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
382 changed = file_data_check_changed_single_file(fd, st);
384 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
389 fd = g_new0(FileData, 1);
390 #ifdef DEBUG_FILEDATA
391 global_file_data_count++;
392 DEBUG_2("file data count++: %d", global_file_data_count);
395 fd->size = st->st_size;
396 fd->date = st->st_mtime;
397 fd->mode = st->st_mode;
399 fd->magick = FD_MAGICK;
401 if (disable_sidecars) fd->disable_grouping = TRUE;
403 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
408 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
410 gchar *path_utf8 = path_to_utf8(path);
411 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
417 void init_exif_time_data(GList *files)
420 DEBUG_1("%s init_exif_time_data: ...", get_exec_time());
432 void read_exif_time_data(FileData *file)
434 if (file->exifdate > 0)
436 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
440 file->exif = exif_read_fd(file);
444 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
445 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), file, file->path);
450 uint year, month, day, hour, min, sec;
452 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
453 time_str.tm_year = year - 1900;
454 time_str.tm_mon = month - 1;
455 time_str.tm_mday = day;
456 time_str.tm_hour = hour;
457 time_str.tm_min = min;
458 time_str.tm_sec = sec;
459 time_str.tm_isdst = 0;
461 file->exifdate = mktime(&time_str);
467 void set_exif_time_data(GList *files)
469 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
473 FileData *file = files->data;
475 read_exif_time_data(file);
480 FileData *file_data_new_no_grouping(const gchar *path_utf8)
484 if (!stat_utf8(path_utf8, &st))
490 return file_data_new(path_utf8, &st, TRUE);
493 FileData *file_data_new_dir(const gchar *path_utf8)
497 if (!stat_utf8(path_utf8, &st))
503 /* dir or non-existing yet */
504 g_assert(S_ISDIR(st.st_mode));
506 return file_data_new(path_utf8, &st, TRUE);
510 *-----------------------------------------------------------------------------
512 *-----------------------------------------------------------------------------
515 #ifdef DEBUG_FILEDATA
516 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
518 FileData *file_data_ref(FileData *fd)
521 if (fd == NULL) return NULL;
522 if (fd->magick != FD_MAGICK)
523 #ifdef DEBUG_FILEDATA
524 DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
526 DEBUG_0("fd magick mismatch fd=%p", fd);
528 g_assert(fd->magick == FD_MAGICK);
531 #ifdef DEBUG_FILEDATA
532 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", fd, fd->ref, fd->path, file, line);
534 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
539 static void file_data_free(FileData *fd)
541 g_assert(fd->magick == FD_MAGICK);
542 g_assert(fd->ref == 0);
543 g_assert(!fd->locked);
545 #ifdef DEBUG_FILEDATA
546 global_file_data_count--;
547 DEBUG_2("file data count--: %d", global_file_data_count);
550 metadata_cache_free(fd);
551 g_hash_table_remove(file_data_pool, fd->original_path);
554 g_free(fd->original_path);
555 g_free(fd->collate_key_name);
556 g_free(fd->collate_key_name_nocase);
557 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
558 histmap_free(fd->histmap);
560 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
562 file_data_change_info_free(NULL, fd);
567 * \brief Checks if the FileData is referenced
569 * Checks the refcount and whether the FileData is locked.
571 static gboolean file_data_check_has_ref(FileData *fd)
573 return fd->ref > 0 || fd->locked;
577 * \brief Consider freeing a FileData.
579 * This function will free a FileData and its children provided that neither its parent nor it has
580 * a positive refcount, and provided that neither is locked.
582 static void file_data_consider_free(FileData *fd)
585 FileData *parent = fd->parent ? fd->parent : fd;
587 g_assert(fd->magick == FD_MAGICK);
588 if (file_data_check_has_ref(fd)) return;
589 if (file_data_check_has_ref(parent)) return;
591 work = parent->sidecar_files;
594 FileData *sfd = work->data;
595 if (file_data_check_has_ref(sfd)) return;
599 /* Neither the parent nor the siblings are referenced, so we can free everything */
600 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
601 fd->path, fd->parent ? parent->path : "-");
603 work = parent->sidecar_files;
606 FileData *sfd = work->data;
611 g_list_free(parent->sidecar_files);
612 parent->sidecar_files = NULL;
614 file_data_free(parent);
617 #ifdef DEBUG_FILEDATA
618 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
620 void file_data_unref(FileData *fd)
623 if (fd == NULL) return;
624 if (fd->magick != FD_MAGICK)
625 #ifdef DEBUG_FILEDATA
626 DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
628 DEBUG_0("fd magick mismatch fd=%p", fd);
630 g_assert(fd->magick == FD_MAGICK);
633 #ifdef DEBUG_FILEDATA
634 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", fd, fd->ref, fd->locked, fd->path,
637 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
640 // Free FileData if it's no longer ref'd
641 file_data_consider_free(fd);
645 * \brief Lock the FileData in memory.
647 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
648 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
649 * even if the code would continue to function properly even if the FileData were freed. Code that
650 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
652 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
653 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
655 void file_data_lock(FileData *fd)
657 if (fd == NULL) return;
658 if (fd->magick != FD_MAGICK) DEBUG_0("fd magick mismatch fd=%p", fd);
660 g_assert(fd->magick == FD_MAGICK);
663 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
667 * \brief Reset the maintain-FileData-in-memory lock
669 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
670 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
671 * keeping it from being freed.
673 void file_data_unlock(FileData *fd)
675 if (fd == NULL) return;
676 if (fd->magick != FD_MAGICK) DEBUG_0("fd magick mismatch fd=%p", fd);
678 g_assert(fd->magick == FD_MAGICK);
681 // Free FileData if it's no longer ref'd
682 file_data_consider_free(fd);
686 * \brief Lock all of the FileDatas in the provided list
688 * \see file_data_lock(FileData)
690 void file_data_lock_list(GList *list)
697 FileData *fd = work->data;
704 * \brief Unlock all of the FileDatas in the provided list
706 * \see file_data_unlock(FileData)
708 void file_data_unlock_list(GList *list)
715 FileData *fd = work->data;
717 file_data_unlock(fd);
722 *-----------------------------------------------------------------------------
723 * sidecar file info struct
724 *-----------------------------------------------------------------------------
727 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
729 const FileData *fda = a;
730 const FileData *fdb = b;
732 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
733 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
735 return strcmp(fdb->extension, fda->extension);
739 static gint sidecar_file_priority(const gchar *extension)
744 if (extension == NULL)
747 work = sidecar_ext_get_list();
750 gchar *ext = work->data;
753 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
759 static void file_data_check_sidecars(const GList *basename_list)
761 /* basename_list contains the new group - first is the parent, then sorted sidecars */
762 /* all files in the list have ref count > 0 */
765 GList *s_work, *new_sidecars;
768 if (!basename_list) return;
771 DEBUG_2("basename start");
772 work = basename_list;
775 FileData *fd = work->data;
777 g_assert(fd->magick == FD_MAGICK);
778 DEBUG_2("basename: %p %s", fd, fd->name);
781 g_assert(fd->parent->magick == FD_MAGICK);
782 DEBUG_2(" parent: %p", fd->parent);
784 s_work = fd->sidecar_files;
787 FileData *sfd = s_work->data;
788 s_work = s_work->next;
789 g_assert(sfd->magick == FD_MAGICK);
790 DEBUG_2(" sidecar: %p %s", sfd, sfd->name);
793 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
796 parent_fd = basename_list->data;
798 /* check if the second and next entries of basename_list are already connected
799 as sidecars of the first entry (parent_fd) */
800 work = basename_list->next;
801 s_work = parent_fd->sidecar_files;
803 while (work && s_work)
805 if (work->data != s_work->data) break;
807 s_work = s_work->next;
810 if (!work && !s_work)
812 DEBUG_2("basename no change");
813 return; /* no change in grouping */
816 /* we have to regroup it */
818 /* first, disconnect everything and send notification*/
820 work = basename_list;
823 FileData *fd = work->data;
825 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
829 FileData *old_parent = fd->parent;
830 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
831 file_data_ref(old_parent);
832 file_data_disconnect_sidecar_file(old_parent, fd);
833 file_data_send_notification(old_parent, NOTIFY_REREAD);
834 file_data_unref(old_parent);
837 while (fd->sidecar_files)
839 FileData *sfd = fd->sidecar_files->data;
840 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
842 file_data_disconnect_sidecar_file(fd, sfd);
843 file_data_send_notification(sfd, NOTIFY_REREAD);
844 file_data_unref(sfd);
846 file_data_send_notification(fd, NOTIFY_GROUPING);
848 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
851 /* now we can form the new group */
852 work = basename_list->next;
856 FileData *sfd = work->data;
857 g_assert(sfd->magick == FD_MAGICK);
858 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
859 sfd->parent = parent_fd;
860 new_sidecars = g_list_prepend(new_sidecars, sfd);
863 g_assert(parent_fd->sidecar_files == NULL);
864 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
865 DEBUG_1("basename group changed for %s", parent_fd->path);
869 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
871 g_assert(target->magick == FD_MAGICK);
872 g_assert(sfd->magick == FD_MAGICK);
873 g_assert(g_list_find(target->sidecar_files, sfd));
875 file_data_ref(target);
878 g_assert(sfd->parent == target);
880 file_data_increment_version(sfd); /* increments both sfd and target */
882 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
885 file_data_unref(target);
886 file_data_unref(sfd);
889 /* disables / enables grouping for particular file, sends UPDATE notification */
890 void file_data_disable_grouping(FileData *fd, gboolean disable)
892 if (!fd->disable_grouping == !disable) return;
894 fd->disable_grouping = !!disable;
900 FileData *parent = file_data_ref(fd->parent);
901 file_data_disconnect_sidecar_file(parent, fd);
902 file_data_send_notification(parent, NOTIFY_GROUPING);
903 file_data_unref(parent);
905 else if (fd->sidecar_files)
907 GList *sidecar_files = filelist_copy(fd->sidecar_files);
908 GList *work = sidecar_files;
911 FileData *sfd = work->data;
913 file_data_disconnect_sidecar_file(fd, sfd);
914 file_data_send_notification(sfd, NOTIFY_GROUPING);
916 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
917 filelist_free(sidecar_files);
921 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
926 file_data_increment_version(fd);
927 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
929 file_data_send_notification(fd, NOTIFY_GROUPING);
932 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
939 FileData *fd = work->data;
941 file_data_disable_grouping(fd, disable);
949 *-----------------------------------------------------------------------------
951 *-----------------------------------------------------------------------------
955 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
958 if (!filelist_sort_ascend)
965 switch (filelist_sort_method)
970 if (fa->size < fb->size) return -1;
971 if (fa->size > fb->size) return 1;
972 /* fall back to name */
975 if (fa->date < fb->date) return -1;
976 if (fa->date > fb->date) return 1;
977 /* fall back to name */
980 if (fa->exifdate < fb->exifdate) return -1;
981 if (fa->exifdate > fb->exifdate) return 1;
982 /* fall back to name */
984 #ifdef HAVE_STRVERSCMP
986 ret = strverscmp(fa->name, fb->name);
987 if (ret != 0) return ret;
994 if (options->file_sort.case_sensitive)
995 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
997 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
999 if (ret != 0) return ret;
1001 /* do not return 0 unless the files are really the same
1002 file_data_pool ensures that original_path is unique
1004 return strcmp(fa->original_path, fb->original_path);
1007 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1009 filelist_sort_method = method;
1010 filelist_sort_ascend = ascend;
1011 return filelist_sort_compare_filedata(fa, fb);
1014 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1016 return filelist_sort_compare_filedata(a, b);
1019 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1021 filelist_sort_method = method;
1022 filelist_sort_ascend = ascend;
1023 return g_list_sort(list, cb);
1026 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1028 filelist_sort_method = method;
1029 filelist_sort_ascend = ascend;
1030 return g_list_insert_sorted(list, data, cb);
1033 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1035 if (method == SORT_EXIFTIME)
1037 set_exif_time_data(list);
1039 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1042 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
1044 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1048 *-----------------------------------------------------------------------------
1049 * basename hash - grouping of sidecars in filelist
1050 *-----------------------------------------------------------------------------
1054 static GHashTable *file_data_basename_hash_new(void)
1056 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1059 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1062 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1064 list = g_hash_table_lookup(basename_hash, basename);
1066 if (!g_list_find(list, fd))
1068 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1069 g_hash_table_insert(basename_hash, basename, list);
1078 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
1080 filelist_free((GList *)value);
1083 static void file_data_basename_hash_free(GHashTable *basename_hash)
1085 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1086 g_hash_table_destroy(basename_hash);
1090 *-----------------------------------------------------------------------------
1091 * handling sidecars in filelist
1092 *-----------------------------------------------------------------------------
1095 static GList *filelist_filter_out_sidecars(GList *flist)
1097 GList *work = flist;
1098 GList *flist_filtered = NULL;
1102 FileData *fd = work->data;
1105 if (fd->parent) /* remove fd's that are children */
1106 file_data_unref(fd);
1108 flist_filtered = g_list_prepend(flist_filtered, fd);
1112 return flist_filtered;
1115 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
1117 GList *basename_list = (GList *)value;
1118 file_data_check_sidecars(basename_list);
1122 static gboolean is_hidden_file(const gchar *name)
1124 if (name[0] != '.') return FALSE;
1125 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1130 *-----------------------------------------------------------------------------
1131 * the main filelist function
1132 *-----------------------------------------------------------------------------
1135 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1140 GList *dlist = NULL;
1141 GList *flist = NULL;
1142 gint (*stat_func)(const gchar *path, struct stat *buf);
1143 GHashTable *basename_hash = NULL;
1145 g_assert(files || dirs);
1147 if (files) *files = NULL;
1148 if (dirs) *dirs = NULL;
1150 pathl = path_from_utf8(dir_path);
1151 if (!pathl) return FALSE;
1153 dp = opendir(pathl);
1160 if (files) basename_hash = file_data_basename_hash_new();
1162 if (follow_symlinks)
1167 while ((dir = readdir(dp)) != NULL)
1169 struct stat ent_sbuf;
1170 const gchar *name = dir->d_name;
1173 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1176 filepath = g_build_filename(pathl, name, NULL);
1177 if (stat_func(filepath, &ent_sbuf) >= 0)
1179 if (S_ISDIR(ent_sbuf.st_mode))
1181 /* we ignore the .thumbnails dir for cleanliness */
1183 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1184 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1185 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1186 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1188 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1193 if (files && filter_name_exists(name))
1195 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1196 flist = g_list_prepend(flist, fd);
1197 if (fd->sidecar_priority && !fd->disable_grouping)
1199 file_data_basename_hash_insert(basename_hash, fd);
1206 if (errno == EOVERFLOW)
1208 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1218 if (dirs) *dirs = dlist;
1222 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1224 *files = filelist_filter_out_sidecars(flist);
1226 if (basename_hash) file_data_basename_hash_free(basename_hash);
1228 // Call a separate function to initialize the exif datestamps for the found files..
1229 if (files) init_exif_time_data(*files);
1234 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1236 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1239 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1241 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1244 FileData *file_data_new_group(const gchar *path_utf8)
1251 if (!stat_utf8(path_utf8, &st))
1257 if (S_ISDIR(st.st_mode))
1258 return file_data_new(path_utf8, &st, TRUE);
1260 dir = remove_level_from_path(path_utf8);
1262 filelist_read_real(dir, &files, NULL, TRUE);
1264 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1265 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1271 filelist_free(files);
1277 void filelist_free(GList *list)
1284 file_data_unref((FileData *)work->data);
1292 GList *filelist_copy(GList *list)
1294 GList *new_list = NULL;
1305 new_list = g_list_prepend(new_list, file_data_ref(fd));
1308 return g_list_reverse(new_list);
1311 GList *filelist_from_path_list(GList *list)
1313 GList *new_list = NULL;
1324 new_list = g_list_prepend(new_list, file_data_new_group(path));
1327 return g_list_reverse(new_list);
1330 GList *filelist_to_path_list(GList *list)
1332 GList *new_list = NULL;
1343 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1346 return g_list_reverse(new_list);
1349 GList *filelist_filter(GList *list, gboolean is_dir_list)
1353 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1358 FileData *fd = (FileData *)(work->data);
1359 const gchar *name = fd->name;
1361 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1362 (!is_dir_list && !filter_name_exists(name)) ||
1363 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1364 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1368 list = g_list_remove_link(list, link);
1369 file_data_unref(fd);
1380 *-----------------------------------------------------------------------------
1381 * filelist recursive
1382 *-----------------------------------------------------------------------------
1385 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1387 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1390 GList *filelist_sort_path(GList *list)
1392 return g_list_sort(list, filelist_sort_path_cb);
1395 static void filelist_recursive_append(GList **list, GList *dirs)
1402 FileData *fd = (FileData *)(work->data);
1406 if (filelist_read(fd, &f, &d))
1408 f = filelist_filter(f, FALSE);
1409 f = filelist_sort_path(f);
1410 *list = g_list_concat(*list, f);
1412 d = filelist_filter(d, TRUE);
1413 d = filelist_sort_path(d);
1414 filelist_recursive_append(list, d);
1422 GList *filelist_recursive(FileData *dir_fd)
1427 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1428 list = filelist_filter(list, FALSE);
1429 list = filelist_sort_path(list);
1431 d = filelist_filter(d, TRUE);
1432 d = filelist_sort_path(d);
1433 filelist_recursive_append(&list, d);
1440 *-----------------------------------------------------------------------------
1441 * file modification support
1442 *-----------------------------------------------------------------------------
1446 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1448 if (!fdci && fd) fdci = fd->change;
1452 g_free(fdci->source);
1457 if (fd) fd->change = NULL;
1460 static gboolean file_data_can_write_directly(FileData *fd)
1462 return filter_name_is_writable(fd->extension);
1465 static gboolean file_data_can_write_sidecar(FileData *fd)
1467 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1470 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1472 gchar *sidecar_path = NULL;
1475 if (!file_data_can_write_sidecar(fd)) return NULL;
1477 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1480 FileData *sfd = work->data;
1482 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
1484 sidecar_path = g_strdup(sfd->path);
1489 if (!existing_only && !sidecar_path)
1491 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1492 sidecar_path = g_strconcat(base, ".xmp", NULL);
1496 return sidecar_path;
1500 * marks and orientation
1503 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1504 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1505 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1506 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1508 gboolean file_data_get_mark(FileData *fd, gint n)
1510 gboolean valid = (fd->valid_marks & (1 << n));
1512 if (file_data_get_mark_func[n] && !valid)
1514 guint old = fd->marks;
1515 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1517 if (!value != !(fd->marks & (1 << n)))
1519 fd->marks = fd->marks ^ (1 << n);
1522 fd->valid_marks |= (1 << n);
1523 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1525 file_data_unref(fd);
1527 else if (!old && fd->marks)
1533 return !!(fd->marks & (1 << n));
1536 guint file_data_get_marks(FileData *fd)
1539 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1543 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1546 if (!value == !file_data_get_mark(fd, n)) return;
1548 if (file_data_set_mark_func[n])
1550 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1555 fd->marks = fd->marks ^ (1 << n);
1557 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1559 file_data_unref(fd);
1561 else if (!old && fd->marks)
1566 file_data_increment_version(fd);
1567 file_data_send_notification(fd, NOTIFY_MARKS);
1570 gboolean file_data_filter_marks(FileData *fd, guint filter)
1573 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1574 return ((fd->marks & filter) == filter);
1577 GList *file_data_filter_marks_list(GList *list, guint filter)
1584 FileData *fd = work->data;
1588 if (!file_data_filter_marks(fd, filter))
1590 list = g_list_remove_link(list, link);
1591 file_data_unref(fd);
1599 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1601 FileData *fd = value;
1602 file_data_increment_version(fd);
1603 file_data_send_notification(fd, NOTIFY_MARKS);
1606 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1608 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1610 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1612 file_data_get_mark_func[n] = get_mark_func;
1613 file_data_set_mark_func[n] = set_mark_func;
1614 file_data_mark_func_data[n] = data;
1615 file_data_destroy_mark_func[n] = notify;
1619 /* this effectively changes all known files */
1620 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1626 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1628 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1629 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1630 if (data) *data = file_data_mark_func_data[n];
1633 gint file_data_get_user_orientation(FileData *fd)
1635 return fd->user_orientation;
1638 void file_data_set_user_orientation(FileData *fd, gint value)
1640 if (fd->user_orientation == value) return;
1642 fd->user_orientation = value;
1643 file_data_increment_version(fd);
1644 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1649 * file_data - operates on the given fd
1650 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1654 /* return list of sidecar file extensions in a string */
1655 gchar *file_data_sc_list_to_string(FileData *fd)
1658 GString *result = g_string_new("");
1660 work = fd->sidecar_files;
1663 FileData *sfd = work->data;
1665 result = g_string_append(result, "+ ");
1666 result = g_string_append(result, sfd->extension);
1668 if (work) result = g_string_append_c(result, ' ');
1671 return g_string_free(result, FALSE);
1677 * add FileDataChangeInfo (see typedefs.h) for the given operation
1678 * uses file_data_add_change_info
1680 * fails if the fd->change already exists - change operations can't run in parallel
1681 * fd->change_info works as a lock
1683 * dest can be NULL - in this case the current name is used for now, it will
1688 FileDataChangeInfo types:
1690 MOVE - path is changed, name may be changed too
1691 RENAME - path remains unchanged, name is changed
1692 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1693 sidecar names are changed too, extensions are not changed
1695 UPDATE - file size, date or grouping has been changed
1698 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1700 FileDataChangeInfo *fdci;
1702 if (fd->change) return FALSE;
1704 fdci = g_new0(FileDataChangeInfo, 1);
1709 fdci->source = g_strdup(src);
1711 fdci->source = g_strdup(fd->path);
1714 fdci->dest = g_strdup(dest);
1721 static void file_data_planned_change_remove(FileData *fd)
1723 if (file_data_planned_change_hash &&
1724 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1726 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1728 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1729 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1730 file_data_unref(fd);
1731 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1733 g_hash_table_destroy(file_data_planned_change_hash);
1734 file_data_planned_change_hash = NULL;
1735 DEBUG_1("planned change: empty");
1742 void file_data_free_ci(FileData *fd)
1744 FileDataChangeInfo *fdci = fd->change;
1748 file_data_planned_change_remove(fd);
1750 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1752 g_free(fdci->source);
1760 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1762 FileDataChangeInfo *fdci = fd->change;
1764 fdci->regroup_when_finished = enable;
1767 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1771 if (fd->parent) fd = fd->parent;
1773 if (fd->change) return FALSE;
1775 work = fd->sidecar_files;
1778 FileData *sfd = work->data;
1780 if (sfd->change) return FALSE;
1784 file_data_add_ci(fd, type, NULL, NULL);
1786 work = fd->sidecar_files;
1789 FileData *sfd = work->data;
1791 file_data_add_ci(sfd, type, NULL, NULL);
1798 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1802 if (fd->parent) fd = fd->parent;
1804 if (!fd->change || fd->change->type != type) return FALSE;
1806 work = fd->sidecar_files;
1809 FileData *sfd = work->data;
1811 if (!sfd->change || sfd->change->type != type) return FALSE;
1819 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1821 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1822 file_data_sc_update_ci_copy(fd, dest_path);
1826 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1828 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1829 file_data_sc_update_ci_move(fd, dest_path);
1833 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1835 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1836 file_data_sc_update_ci_rename(fd, dest_path);
1840 gboolean file_data_sc_add_ci_delete(FileData *fd)
1842 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1845 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1847 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1848 file_data_sc_update_ci_unspecified(fd, dest_path);
1852 gboolean file_data_add_ci_write_metadata(FileData *fd)
1854 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1857 void file_data_sc_free_ci(FileData *fd)
1861 if (fd->parent) fd = fd->parent;
1863 file_data_free_ci(fd);
1865 work = fd->sidecar_files;
1868 FileData *sfd = work->data;
1870 file_data_free_ci(sfd);
1875 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1878 gboolean ret = TRUE;
1883 FileData *fd = work->data;
1885 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1892 static void file_data_sc_revert_ci_list(GList *fd_list)
1899 FileData *fd = work->data;
1901 file_data_sc_free_ci(fd);
1906 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1913 FileData *fd = work->data;
1915 if (!func(fd, dest))
1917 file_data_sc_revert_ci_list(work->prev);
1926 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1928 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1931 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1933 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1936 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1938 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1941 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1943 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1946 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1949 gboolean ret = TRUE;
1954 FileData *fd = work->data;
1956 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1963 void file_data_free_ci_list(GList *fd_list)
1970 FileData *fd = work->data;
1972 file_data_free_ci(fd);
1977 void file_data_sc_free_ci_list(GList *fd_list)
1984 FileData *fd = work->data;
1986 file_data_sc_free_ci(fd);
1992 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1993 * fails if fd->change does not exist or the change type does not match
1996 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1998 FileDataChangeType type = fd->change->type;
2000 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2004 if (!file_data_planned_change_hash)
2005 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2007 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2009 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2010 g_hash_table_remove(file_data_planned_change_hash, old_path);
2011 file_data_unref(fd);
2014 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
2019 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2020 g_hash_table_remove(file_data_planned_change_hash, new_path);
2021 file_data_unref(ofd);
2024 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2026 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2031 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2033 gchar *old_path = fd->change->dest;
2035 fd->change->dest = g_strdup(dest_path);
2036 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2040 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2042 const gchar *extension = extension_from_path(fd->change->source);
2043 gchar *base = remove_extension_from_path(dest_path);
2044 gchar *old_path = fd->change->dest;
2046 fd->change->dest = g_strconcat(base, extension, NULL);
2047 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2053 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2056 gchar *dest_path_full = NULL;
2058 if (fd->parent) fd = fd->parent;
2062 dest_path = fd->path;
2064 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2066 gchar *dir = remove_level_from_path(fd->path);
2068 dest_path_full = g_build_filename(dir, dest_path, NULL);
2070 dest_path = dest_path_full;
2072 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2074 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2075 dest_path = dest_path_full;
2078 file_data_update_ci_dest(fd, dest_path);
2080 work = fd->sidecar_files;
2083 FileData *sfd = work->data;
2085 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2089 g_free(dest_path_full);
2092 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2094 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2095 file_data_sc_update_ci(fd, dest_path);
2099 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2101 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2104 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2106 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2109 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2111 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2114 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2116 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2119 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2121 gboolean (*func)(FileData *, const gchar *))
2124 gboolean ret = TRUE;
2129 FileData *fd = work->data;
2131 if (!func(fd, dest)) ret = FALSE;
2138 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2140 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2143 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2145 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2148 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2150 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2155 * verify source and dest paths - dest image exists, etc.
2156 * it should detect all possible problems with the planned operation
2159 gint file_data_verify_ci(FileData *fd)
2161 gint ret = CHANGE_OK;
2166 DEBUG_1("Change checked: no change info: %s", fd->path);
2170 if (!isname(fd->path))
2172 /* this probably should not happen */
2173 ret |= CHANGE_NO_SRC;
2174 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2178 dir = remove_level_from_path(fd->path);
2180 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2181 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2182 fd->change->type != FILEDATA_CHANGE_RENAME &&
2183 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2186 ret |= CHANGE_WARN_UNSAVED_META;
2187 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2190 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2191 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2192 !access_file(fd->path, R_OK))
2194 ret |= CHANGE_NO_READ_PERM;
2195 DEBUG_1("Change checked: no read permission: %s", fd->path);
2197 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2198 !access_file(dir, W_OK))
2200 ret |= CHANGE_NO_WRITE_PERM_DIR;
2201 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2203 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2204 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2205 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2206 !access_file(fd->path, W_OK))
2208 ret |= CHANGE_WARN_NO_WRITE_PERM;
2209 DEBUG_1("Change checked: no write permission: %s", fd->path);
2211 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2212 - that means that there are no hard errors and warnings can be disabled
2213 - the destination is determined during the check
2215 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2217 /* determine destination file */
2218 gboolean have_dest = FALSE;
2219 gchar *dest_dir = NULL;
2221 if (options->metadata.save_in_image_file)
2223 if (file_data_can_write_directly(fd))
2225 /* we can write the file directly */
2226 if (access_file(fd->path, W_OK))
2232 if (options->metadata.warn_on_write_problems)
2234 ret |= CHANGE_WARN_NO_WRITE_PERM;
2235 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2239 else if (file_data_can_write_sidecar(fd))
2241 /* we can write sidecar */
2242 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2243 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2245 file_data_update_ci_dest(fd, sidecar);
2250 if (options->metadata.warn_on_write_problems)
2252 ret |= CHANGE_WARN_NO_WRITE_PERM;
2253 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2262 /* write private metadata file under ~/.geeqie */
2264 /* If an existing metadata file exists, we will try writing to
2265 * it's location regardless of the user's preference.
2267 gchar *metadata_path = NULL;
2269 /* but ignore XMP if we are not able to write it */
2270 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2272 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2274 if (metadata_path && !access_file(metadata_path, W_OK))
2276 g_free(metadata_path);
2277 metadata_path = NULL;
2284 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2285 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2287 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2289 metadata_path = g_build_filename(dest_dir, filename, NULL);
2293 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2295 file_data_update_ci_dest(fd, metadata_path);
2300 ret |= CHANGE_NO_WRITE_PERM_DEST;
2301 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2303 g_free(metadata_path);
2308 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2313 same = (strcmp(fd->path, fd->change->dest) == 0);
2317 const gchar *dest_ext = extension_from_path(fd->change->dest);
2318 if (!dest_ext) dest_ext = "";
2320 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2322 ret |= CHANGE_WARN_CHANGED_EXT;
2323 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2328 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2330 ret |= CHANGE_WARN_SAME;
2331 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2335 dest_dir = remove_level_from_path(fd->change->dest);
2337 if (!isdir(dest_dir))
2339 ret |= CHANGE_NO_DEST_DIR;
2340 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2342 else if (!access_file(dest_dir, W_OK))
2344 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2345 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2349 if (isfile(fd->change->dest))
2351 if (!access_file(fd->change->dest, W_OK))
2353 ret |= CHANGE_NO_WRITE_PERM_DEST;
2354 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2358 ret |= CHANGE_WARN_DEST_EXISTS;
2359 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2362 else if (isdir(fd->change->dest))
2364 ret |= CHANGE_DEST_EXISTS;
2365 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2372 fd->change->error = ret;
2373 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2380 gint file_data_sc_verify_ci(FileData *fd)
2385 ret = file_data_verify_ci(fd);
2387 work = fd->sidecar_files;
2390 FileData *sfd = work->data;
2392 ret |= file_data_verify_ci(sfd);
2399 gchar *file_data_get_error_string(gint error)
2401 GString *result = g_string_new("");
2403 if (error & CHANGE_NO_SRC)
2405 if (result->len > 0) g_string_append(result, ", ");
2406 g_string_append(result, _("file or directory does not exist"));
2409 if (error & CHANGE_DEST_EXISTS)
2411 if (result->len > 0) g_string_append(result, ", ");
2412 g_string_append(result, _("destination already exists"));
2415 if (error & CHANGE_NO_WRITE_PERM_DEST)
2417 if (result->len > 0) g_string_append(result, ", ");
2418 g_string_append(result, _("destination can't be overwritten"));
2421 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2423 if (result->len > 0) g_string_append(result, ", ");
2424 g_string_append(result, _("destination directory is not writable"));
2427 if (error & CHANGE_NO_DEST_DIR)
2429 if (result->len > 0) g_string_append(result, ", ");
2430 g_string_append(result, _("destination directory does not exist"));
2433 if (error & CHANGE_NO_WRITE_PERM_DIR)
2435 if (result->len > 0) g_string_append(result, ", ");
2436 g_string_append(result, _("source directory is not writable"));
2439 if (error & CHANGE_NO_READ_PERM)
2441 if (result->len > 0) g_string_append(result, ", ");
2442 g_string_append(result, _("no read permission"));
2445 if (error & CHANGE_WARN_NO_WRITE_PERM)
2447 if (result->len > 0) g_string_append(result, ", ");
2448 g_string_append(result, _("file is readonly"));
2451 if (error & CHANGE_WARN_DEST_EXISTS)
2453 if (result->len > 0) g_string_append(result, ", ");
2454 g_string_append(result, _("destination already exists and will be overwritten"));
2457 if (error & CHANGE_WARN_SAME)
2459 if (result->len > 0) g_string_append(result, ", ");
2460 g_string_append(result, _("source and destination are the same"));
2463 if (error & CHANGE_WARN_CHANGED_EXT)
2465 if (result->len > 0) g_string_append(result, ", ");
2466 g_string_append(result, _("source and destination have different extension"));
2469 if (error & CHANGE_WARN_UNSAVED_META)
2471 if (result->len > 0) g_string_append(result, ", ");
2472 g_string_append(result, _("there are unsaved metadata changes for the file"));
2475 return g_string_free(result, FALSE);
2478 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2481 gint all_errors = 0;
2482 gint common_errors = ~0;
2487 if (!list) return 0;
2489 num = g_list_length(list);
2490 errors = g_new(int, num);
2501 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2502 all_errors |= error;
2503 common_errors &= error;
2510 if (desc && all_errors)
2513 GString *result = g_string_new("");
2517 gchar *str = file_data_get_error_string(common_errors);
2518 g_string_append(result, str);
2519 g_string_append(result, "\n");
2533 error = errors[i] & ~common_errors;
2537 gchar *str = file_data_get_error_string(error);
2538 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2543 *desc = g_string_free(result, FALSE);
2552 * perform the change described by FileFataChangeInfo
2553 * it is used for internal operations,
2554 * this function actually operates with files on the filesystem
2555 * it should implement safe delete
2558 static gboolean file_data_perform_move(FileData *fd)
2560 g_assert(!strcmp(fd->change->source, fd->path));
2561 return move_file(fd->change->source, fd->change->dest);
2564 static gboolean file_data_perform_copy(FileData *fd)
2566 g_assert(!strcmp(fd->change->source, fd->path));
2567 return copy_file(fd->change->source, fd->change->dest);
2570 static gboolean file_data_perform_delete(FileData *fd)
2572 if (isdir(fd->path) && !islink(fd->path))
2573 return rmdir_utf8(fd->path);
2575 if (options->file_ops.safe_delete_enable)
2576 return file_util_safe_unlink(fd->path);
2578 return unlink_file(fd->path);
2581 gboolean file_data_perform_ci(FileData *fd)
2583 FileDataChangeType type = fd->change->type;
2587 case FILEDATA_CHANGE_MOVE:
2588 return file_data_perform_move(fd);
2589 case FILEDATA_CHANGE_COPY:
2590 return file_data_perform_copy(fd);
2591 case FILEDATA_CHANGE_RENAME:
2592 return file_data_perform_move(fd); /* the same as move */
2593 case FILEDATA_CHANGE_DELETE:
2594 return file_data_perform_delete(fd);
2595 case FILEDATA_CHANGE_WRITE_METADATA:
2596 return metadata_write_perform(fd);
2597 case FILEDATA_CHANGE_UNSPECIFIED:
2598 /* nothing to do here */
2606 gboolean file_data_sc_perform_ci(FileData *fd)
2609 gboolean ret = TRUE;
2610 FileDataChangeType type = fd->change->type;
2612 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2614 work = fd->sidecar_files;
2617 FileData *sfd = work->data;
2619 if (!file_data_perform_ci(sfd)) ret = FALSE;
2623 if (!file_data_perform_ci(fd)) ret = FALSE;
2629 * updates FileData structure according to FileDataChangeInfo
2632 gboolean file_data_apply_ci(FileData *fd)
2634 FileDataChangeType type = fd->change->type;
2637 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2639 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2640 file_data_planned_change_remove(fd);
2642 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2644 /* this change overwrites another file which is already known to other modules
2645 renaming fd would create duplicate FileData structure
2646 the best thing we can do is nothing
2647 FIXME: maybe we could copy stuff like marks
2649 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2653 file_data_set_path(fd, fd->change->dest);
2656 file_data_increment_version(fd);
2657 file_data_send_notification(fd, NOTIFY_CHANGE);
2662 gboolean file_data_sc_apply_ci(FileData *fd)
2665 FileDataChangeType type = fd->change->type;
2667 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2669 work = fd->sidecar_files;
2672 FileData *sfd = work->data;
2674 file_data_apply_ci(sfd);
2678 file_data_apply_ci(fd);
2683 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2686 if (fd->parent) fd = fd->parent;
2687 if (!g_list_find(list, fd)) return FALSE;
2689 work = fd->sidecar_files;
2692 if (!g_list_find(list, work->data)) return FALSE;
2698 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2703 /* change partial groups to independent files */
2708 FileData *fd = work->data;
2711 if (!file_data_list_contains_whole_group(list, fd))
2713 file_data_disable_grouping(fd, TRUE);
2716 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2722 /* remove sidecars from the list,
2723 they can be still acessed via main_fd->sidecar_files */
2727 FileData *fd = work->data;
2731 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2733 out = g_list_prepend(out, file_data_ref(fd));
2737 filelist_free(list);
2738 out = g_list_reverse(out);
2748 * notify other modules about the change described by FileDataChangeInfo
2751 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2752 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2753 implementation in view_file_list.c */
2756 typedef struct _NotifyIdleData NotifyIdleData;
2758 struct _NotifyIdleData {
2764 typedef struct _NotifyData NotifyData;
2766 struct _NotifyData {
2767 FileDataNotifyFunc func;
2769 NotifyPriority priority;
2772 static GList *notify_func_list = NULL;
2774 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2776 NotifyData *nda = (NotifyData *)a;
2777 NotifyData *ndb = (NotifyData *)b;
2779 if (nda->priority < ndb->priority) return -1;
2780 if (nda->priority > ndb->priority) return 1;
2784 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2787 GList *work = notify_func_list;
2791 NotifyData *nd = (NotifyData *)work->data;
2793 if (nd->func == func && nd->data == data)
2795 g_warning("Notify func already registered");
2801 nd = g_new(NotifyData, 1);
2804 nd->priority = priority;
2806 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2807 DEBUG_2("Notify func registered: %p", nd);
2812 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2814 GList *work = notify_func_list;
2818 NotifyData *nd = (NotifyData *)work->data;
2820 if (nd->func == func && nd->data == data)
2822 notify_func_list = g_list_delete_link(notify_func_list, work);
2824 DEBUG_2("Notify func unregistered: %p", nd);
2830 g_warning("Notify func not found");
2835 gboolean file_data_send_notification_idle_cb(gpointer data)
2837 NotifyIdleData *nid = (NotifyIdleData *)data;
2838 GList *work = notify_func_list;
2842 NotifyData *nd = (NotifyData *)work->data;
2844 nd->func(nid->fd, nid->type, nd->data);
2847 file_data_unref(nid->fd);
2852 void file_data_send_notification(FileData *fd, NotifyType type)
2854 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
2855 nid->fd = file_data_ref(fd);
2857 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
2860 static GHashTable *file_data_monitor_pool = NULL;
2861 static guint realtime_monitor_id = 0; /* event source id */
2863 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2867 file_data_check_changed_files(fd);
2869 DEBUG_1("monitor %s", fd->path);
2872 static gboolean realtime_monitor_cb(gpointer data)
2874 if (!options->update_on_time_change) return TRUE;
2875 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2879 gboolean file_data_register_real_time_monitor(FileData *fd)
2885 if (!file_data_monitor_pool)
2886 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2888 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2890 DEBUG_1("Register realtime %d %s", count, fd->path);
2893 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2895 if (!realtime_monitor_id)
2897 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2903 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2907 g_assert(file_data_monitor_pool);
2909 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2911 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2913 g_assert(count > 0);
2918 g_hash_table_remove(file_data_monitor_pool, fd);
2920 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2922 file_data_unref(fd);
2924 if (g_hash_table_size(file_data_monitor_pool) == 0)
2926 g_source_remove(realtime_monitor_id);
2927 realtime_monitor_id = 0;
2933 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */