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);
1268 filelist_free(files);
1274 void filelist_free(GList *list)
1281 file_data_unref((FileData *)work->data);
1289 GList *filelist_copy(GList *list)
1291 GList *new_list = NULL;
1302 new_list = g_list_prepend(new_list, file_data_ref(fd));
1305 return g_list_reverse(new_list);
1308 GList *filelist_from_path_list(GList *list)
1310 GList *new_list = NULL;
1321 new_list = g_list_prepend(new_list, file_data_new_group(path));
1324 return g_list_reverse(new_list);
1327 GList *filelist_to_path_list(GList *list)
1329 GList *new_list = NULL;
1340 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1343 return g_list_reverse(new_list);
1346 GList *filelist_filter(GList *list, gboolean is_dir_list)
1350 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1355 FileData *fd = (FileData *)(work->data);
1356 const gchar *name = fd->name;
1358 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1359 (!is_dir_list && !filter_name_exists(name)) ||
1360 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1361 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1365 list = g_list_remove_link(list, link);
1366 file_data_unref(fd);
1377 *-----------------------------------------------------------------------------
1378 * filelist recursive
1379 *-----------------------------------------------------------------------------
1382 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1384 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1387 GList *filelist_sort_path(GList *list)
1389 return g_list_sort(list, filelist_sort_path_cb);
1392 static void filelist_recursive_append(GList **list, GList *dirs)
1399 FileData *fd = (FileData *)(work->data);
1403 if (filelist_read(fd, &f, &d))
1405 f = filelist_filter(f, FALSE);
1406 f = filelist_sort_path(f);
1407 *list = g_list_concat(*list, f);
1409 d = filelist_filter(d, TRUE);
1410 d = filelist_sort_path(d);
1411 filelist_recursive_append(list, d);
1419 GList *filelist_recursive(FileData *dir_fd)
1424 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1425 list = filelist_filter(list, FALSE);
1426 list = filelist_sort_path(list);
1428 d = filelist_filter(d, TRUE);
1429 d = filelist_sort_path(d);
1430 filelist_recursive_append(&list, d);
1437 *-----------------------------------------------------------------------------
1438 * file modification support
1439 *-----------------------------------------------------------------------------
1443 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1445 if (!fdci && fd) fdci = fd->change;
1449 g_free(fdci->source);
1454 if (fd) fd->change = NULL;
1457 static gboolean file_data_can_write_directly(FileData *fd)
1459 return filter_name_is_writable(fd->extension);
1462 static gboolean file_data_can_write_sidecar(FileData *fd)
1464 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1467 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1469 gchar *sidecar_path = NULL;
1472 if (!file_data_can_write_sidecar(fd)) return NULL;
1474 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1477 FileData *sfd = work->data;
1479 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
1481 sidecar_path = g_strdup(sfd->path);
1486 if (!existing_only && !sidecar_path)
1488 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1489 sidecar_path = g_strconcat(base, ".xmp", NULL);
1493 return sidecar_path;
1497 * marks and orientation
1500 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1501 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1502 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1503 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1505 gboolean file_data_get_mark(FileData *fd, gint n)
1507 gboolean valid = (fd->valid_marks & (1 << n));
1509 if (file_data_get_mark_func[n] && !valid)
1511 guint old = fd->marks;
1512 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1514 if (!value != !(fd->marks & (1 << n)))
1516 fd->marks = fd->marks ^ (1 << n);
1519 fd->valid_marks |= (1 << n);
1520 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1522 file_data_unref(fd);
1524 else if (!old && fd->marks)
1530 return !!(fd->marks & (1 << n));
1533 guint file_data_get_marks(FileData *fd)
1536 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1540 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1543 if (!value == !file_data_get_mark(fd, n)) return;
1545 if (file_data_set_mark_func[n])
1547 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1552 fd->marks = fd->marks ^ (1 << n);
1554 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1556 file_data_unref(fd);
1558 else if (!old && fd->marks)
1563 file_data_increment_version(fd);
1564 file_data_send_notification(fd, NOTIFY_MARKS);
1567 gboolean file_data_filter_marks(FileData *fd, guint filter)
1570 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1571 return ((fd->marks & filter) == filter);
1574 GList *file_data_filter_marks_list(GList *list, guint filter)
1581 FileData *fd = work->data;
1585 if (!file_data_filter_marks(fd, filter))
1587 list = g_list_remove_link(list, link);
1588 file_data_unref(fd);
1596 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1598 FileData *fd = value;
1599 file_data_increment_version(fd);
1600 file_data_send_notification(fd, NOTIFY_MARKS);
1603 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1605 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1607 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1609 file_data_get_mark_func[n] = get_mark_func;
1610 file_data_set_mark_func[n] = set_mark_func;
1611 file_data_mark_func_data[n] = data;
1612 file_data_destroy_mark_func[n] = notify;
1616 /* this effectively changes all known files */
1617 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1623 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1625 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1626 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1627 if (data) *data = file_data_mark_func_data[n];
1630 gint file_data_get_user_orientation(FileData *fd)
1632 return fd->user_orientation;
1635 void file_data_set_user_orientation(FileData *fd, gint value)
1637 if (fd->user_orientation == value) return;
1639 fd->user_orientation = value;
1640 file_data_increment_version(fd);
1641 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1646 * file_data - operates on the given fd
1647 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1651 /* return list of sidecar file extensions in a string */
1652 gchar *file_data_sc_list_to_string(FileData *fd)
1655 GString *result = g_string_new("");
1657 work = fd->sidecar_files;
1660 FileData *sfd = work->data;
1662 result = g_string_append(result, "+ ");
1663 result = g_string_append(result, sfd->extension);
1665 if (work) result = g_string_append_c(result, ' ');
1668 return g_string_free(result, FALSE);
1674 * add FileDataChangeInfo (see typedefs.h) for the given operation
1675 * uses file_data_add_change_info
1677 * fails if the fd->change already exists - change operations can't run in parallel
1678 * fd->change_info works as a lock
1680 * dest can be NULL - in this case the current name is used for now, it will
1685 FileDataChangeInfo types:
1687 MOVE - path is changed, name may be changed too
1688 RENAME - path remains unchanged, name is changed
1689 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1690 sidecar names are changed too, extensions are not changed
1692 UPDATE - file size, date or grouping has been changed
1695 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1697 FileDataChangeInfo *fdci;
1699 if (fd->change) return FALSE;
1701 fdci = g_new0(FileDataChangeInfo, 1);
1706 fdci->source = g_strdup(src);
1708 fdci->source = g_strdup(fd->path);
1711 fdci->dest = g_strdup(dest);
1718 static void file_data_planned_change_remove(FileData *fd)
1720 if (file_data_planned_change_hash &&
1721 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1723 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1725 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1726 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1727 file_data_unref(fd);
1728 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1730 g_hash_table_destroy(file_data_planned_change_hash);
1731 file_data_planned_change_hash = NULL;
1732 DEBUG_1("planned change: empty");
1739 void file_data_free_ci(FileData *fd)
1741 FileDataChangeInfo *fdci = fd->change;
1745 file_data_planned_change_remove(fd);
1747 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1749 g_free(fdci->source);
1757 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1759 FileDataChangeInfo *fdci = fd->change;
1761 fdci->regroup_when_finished = enable;
1764 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1768 if (fd->parent) fd = fd->parent;
1770 if (fd->change) return FALSE;
1772 work = fd->sidecar_files;
1775 FileData *sfd = work->data;
1777 if (sfd->change) return FALSE;
1781 file_data_add_ci(fd, type, NULL, NULL);
1783 work = fd->sidecar_files;
1786 FileData *sfd = work->data;
1788 file_data_add_ci(sfd, type, NULL, NULL);
1795 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1799 if (fd->parent) fd = fd->parent;
1801 if (!fd->change || fd->change->type != type) return FALSE;
1803 work = fd->sidecar_files;
1806 FileData *sfd = work->data;
1808 if (!sfd->change || sfd->change->type != type) return FALSE;
1816 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1818 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1819 file_data_sc_update_ci_copy(fd, dest_path);
1823 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1825 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1826 file_data_sc_update_ci_move(fd, dest_path);
1830 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1832 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1833 file_data_sc_update_ci_rename(fd, dest_path);
1837 gboolean file_data_sc_add_ci_delete(FileData *fd)
1839 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1842 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1844 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1845 file_data_sc_update_ci_unspecified(fd, dest_path);
1849 gboolean file_data_add_ci_write_metadata(FileData *fd)
1851 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1854 void file_data_sc_free_ci(FileData *fd)
1858 if (fd->parent) fd = fd->parent;
1860 file_data_free_ci(fd);
1862 work = fd->sidecar_files;
1865 FileData *sfd = work->data;
1867 file_data_free_ci(sfd);
1872 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1875 gboolean ret = TRUE;
1880 FileData *fd = work->data;
1882 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1889 static void file_data_sc_revert_ci_list(GList *fd_list)
1896 FileData *fd = work->data;
1898 file_data_sc_free_ci(fd);
1903 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1910 FileData *fd = work->data;
1912 if (!func(fd, dest))
1914 file_data_sc_revert_ci_list(work->prev);
1923 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1925 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1928 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1930 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1933 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1935 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1938 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1940 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1943 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1946 gboolean ret = TRUE;
1951 FileData *fd = work->data;
1953 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1960 void file_data_free_ci_list(GList *fd_list)
1967 FileData *fd = work->data;
1969 file_data_free_ci(fd);
1974 void file_data_sc_free_ci_list(GList *fd_list)
1981 FileData *fd = work->data;
1983 file_data_sc_free_ci(fd);
1989 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1990 * fails if fd->change does not exist or the change type does not match
1993 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1995 FileDataChangeType type = fd->change->type;
1997 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2001 if (!file_data_planned_change_hash)
2002 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2004 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2006 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2007 g_hash_table_remove(file_data_planned_change_hash, old_path);
2008 file_data_unref(fd);
2011 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
2016 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2017 g_hash_table_remove(file_data_planned_change_hash, new_path);
2018 file_data_unref(ofd);
2021 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2023 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2028 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2030 gchar *old_path = fd->change->dest;
2032 fd->change->dest = g_strdup(dest_path);
2033 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2037 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2039 const gchar *extension = extension_from_path(fd->change->source);
2040 gchar *base = remove_extension_from_path(dest_path);
2041 gchar *old_path = fd->change->dest;
2043 fd->change->dest = g_strconcat(base, extension, NULL);
2044 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2050 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2053 gchar *dest_path_full = NULL;
2055 if (fd->parent) fd = fd->parent;
2059 dest_path = fd->path;
2061 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2063 gchar *dir = remove_level_from_path(fd->path);
2065 dest_path_full = g_build_filename(dir, dest_path, NULL);
2067 dest_path = dest_path_full;
2069 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2071 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2072 dest_path = dest_path_full;
2075 file_data_update_ci_dest(fd, dest_path);
2077 work = fd->sidecar_files;
2080 FileData *sfd = work->data;
2082 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2086 g_free(dest_path_full);
2089 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2091 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2092 file_data_sc_update_ci(fd, dest_path);
2096 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2098 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2101 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2103 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2106 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2108 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2111 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2113 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2116 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2118 gboolean (*func)(FileData *, const gchar *))
2121 gboolean ret = TRUE;
2126 FileData *fd = work->data;
2128 if (!func(fd, dest)) ret = FALSE;
2135 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2137 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2140 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2142 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2145 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2147 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2152 * verify source and dest paths - dest image exists, etc.
2153 * it should detect all possible problems with the planned operation
2156 gint file_data_verify_ci(FileData *fd)
2158 gint ret = CHANGE_OK;
2163 DEBUG_1("Change checked: no change info: %s", fd->path);
2167 if (!isname(fd->path))
2169 /* this probably should not happen */
2170 ret |= CHANGE_NO_SRC;
2171 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2175 dir = remove_level_from_path(fd->path);
2177 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2178 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2179 fd->change->type != FILEDATA_CHANGE_RENAME &&
2180 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2183 ret |= CHANGE_WARN_UNSAVED_META;
2184 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2187 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2188 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2189 !access_file(fd->path, R_OK))
2191 ret |= CHANGE_NO_READ_PERM;
2192 DEBUG_1("Change checked: no read permission: %s", fd->path);
2194 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2195 !access_file(dir, W_OK))
2197 ret |= CHANGE_NO_WRITE_PERM_DIR;
2198 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2200 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2201 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2202 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2203 !access_file(fd->path, W_OK))
2205 ret |= CHANGE_WARN_NO_WRITE_PERM;
2206 DEBUG_1("Change checked: no write permission: %s", fd->path);
2208 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2209 - that means that there are no hard errors and warnings can be disabled
2210 - the destination is determined during the check
2212 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2214 /* determine destination file */
2215 gboolean have_dest = FALSE;
2216 gchar *dest_dir = NULL;
2218 if (options->metadata.save_in_image_file)
2220 if (file_data_can_write_directly(fd))
2222 /* we can write the file directly */
2223 if (access_file(fd->path, W_OK))
2229 if (options->metadata.warn_on_write_problems)
2231 ret |= CHANGE_WARN_NO_WRITE_PERM;
2232 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2236 else if (file_data_can_write_sidecar(fd))
2238 /* we can write sidecar */
2239 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2240 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2242 file_data_update_ci_dest(fd, sidecar);
2247 if (options->metadata.warn_on_write_problems)
2249 ret |= CHANGE_WARN_NO_WRITE_PERM;
2250 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2259 /* write private metadata file under ~/.geeqie */
2261 /* If an existing metadata file exists, we will try writing to
2262 * it's location regardless of the user's preference.
2264 gchar *metadata_path = NULL;
2266 /* but ignore XMP if we are not able to write it */
2267 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2269 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2271 if (metadata_path && !access_file(metadata_path, W_OK))
2273 g_free(metadata_path);
2274 metadata_path = NULL;
2281 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2282 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2284 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2286 metadata_path = g_build_filename(dest_dir, filename, NULL);
2290 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2292 file_data_update_ci_dest(fd, metadata_path);
2297 ret |= CHANGE_NO_WRITE_PERM_DEST;
2298 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2300 g_free(metadata_path);
2305 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2310 same = (strcmp(fd->path, fd->change->dest) == 0);
2314 const gchar *dest_ext = extension_from_path(fd->change->dest);
2315 if (!dest_ext) dest_ext = "";
2317 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2319 ret |= CHANGE_WARN_CHANGED_EXT;
2320 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2325 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2327 ret |= CHANGE_WARN_SAME;
2328 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2332 dest_dir = remove_level_from_path(fd->change->dest);
2334 if (!isdir(dest_dir))
2336 ret |= CHANGE_NO_DEST_DIR;
2337 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2339 else if (!access_file(dest_dir, W_OK))
2341 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2342 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2346 if (isfile(fd->change->dest))
2348 if (!access_file(fd->change->dest, W_OK))
2350 ret |= CHANGE_NO_WRITE_PERM_DEST;
2351 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2355 ret |= CHANGE_WARN_DEST_EXISTS;
2356 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2359 else if (isdir(fd->change->dest))
2361 ret |= CHANGE_DEST_EXISTS;
2362 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2369 fd->change->error = ret;
2370 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2377 gint file_data_sc_verify_ci(FileData *fd)
2382 ret = file_data_verify_ci(fd);
2384 work = fd->sidecar_files;
2387 FileData *sfd = work->data;
2389 ret |= file_data_verify_ci(sfd);
2396 gchar *file_data_get_error_string(gint error)
2398 GString *result = g_string_new("");
2400 if (error & CHANGE_NO_SRC)
2402 if (result->len > 0) g_string_append(result, ", ");
2403 g_string_append(result, _("file or directory does not exist"));
2406 if (error & CHANGE_DEST_EXISTS)
2408 if (result->len > 0) g_string_append(result, ", ");
2409 g_string_append(result, _("destination already exists"));
2412 if (error & CHANGE_NO_WRITE_PERM_DEST)
2414 if (result->len > 0) g_string_append(result, ", ");
2415 g_string_append(result, _("destination can't be overwritten"));
2418 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2420 if (result->len > 0) g_string_append(result, ", ");
2421 g_string_append(result, _("destination directory is not writable"));
2424 if (error & CHANGE_NO_DEST_DIR)
2426 if (result->len > 0) g_string_append(result, ", ");
2427 g_string_append(result, _("destination directory does not exist"));
2430 if (error & CHANGE_NO_WRITE_PERM_DIR)
2432 if (result->len > 0) g_string_append(result, ", ");
2433 g_string_append(result, _("source directory is not writable"));
2436 if (error & CHANGE_NO_READ_PERM)
2438 if (result->len > 0) g_string_append(result, ", ");
2439 g_string_append(result, _("no read permission"));
2442 if (error & CHANGE_WARN_NO_WRITE_PERM)
2444 if (result->len > 0) g_string_append(result, ", ");
2445 g_string_append(result, _("file is readonly"));
2448 if (error & CHANGE_WARN_DEST_EXISTS)
2450 if (result->len > 0) g_string_append(result, ", ");
2451 g_string_append(result, _("destination already exists and will be overwritten"));
2454 if (error & CHANGE_WARN_SAME)
2456 if (result->len > 0) g_string_append(result, ", ");
2457 g_string_append(result, _("source and destination are the same"));
2460 if (error & CHANGE_WARN_CHANGED_EXT)
2462 if (result->len > 0) g_string_append(result, ", ");
2463 g_string_append(result, _("source and destination have different extension"));
2466 if (error & CHANGE_WARN_UNSAVED_META)
2468 if (result->len > 0) g_string_append(result, ", ");
2469 g_string_append(result, _("there are unsaved metadata changes for the file"));
2472 return g_string_free(result, FALSE);
2475 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2478 gint all_errors = 0;
2479 gint common_errors = ~0;
2484 if (!list) return 0;
2486 num = g_list_length(list);
2487 errors = g_new(int, num);
2498 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2499 all_errors |= error;
2500 common_errors &= error;
2507 if (desc && all_errors)
2510 GString *result = g_string_new("");
2514 gchar *str = file_data_get_error_string(common_errors);
2515 g_string_append(result, str);
2516 g_string_append(result, "\n");
2530 error = errors[i] & ~common_errors;
2534 gchar *str = file_data_get_error_string(error);
2535 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2540 *desc = g_string_free(result, FALSE);
2549 * perform the change described by FileFataChangeInfo
2550 * it is used for internal operations,
2551 * this function actually operates with files on the filesystem
2552 * it should implement safe delete
2555 static gboolean file_data_perform_move(FileData *fd)
2557 g_assert(!strcmp(fd->change->source, fd->path));
2558 return move_file(fd->change->source, fd->change->dest);
2561 static gboolean file_data_perform_copy(FileData *fd)
2563 g_assert(!strcmp(fd->change->source, fd->path));
2564 return copy_file(fd->change->source, fd->change->dest);
2567 static gboolean file_data_perform_delete(FileData *fd)
2569 if (isdir(fd->path) && !islink(fd->path))
2570 return rmdir_utf8(fd->path);
2572 if (options->file_ops.safe_delete_enable)
2573 return file_util_safe_unlink(fd->path);
2575 return unlink_file(fd->path);
2578 gboolean file_data_perform_ci(FileData *fd)
2580 FileDataChangeType type = fd->change->type;
2584 case FILEDATA_CHANGE_MOVE:
2585 return file_data_perform_move(fd);
2586 case FILEDATA_CHANGE_COPY:
2587 return file_data_perform_copy(fd);
2588 case FILEDATA_CHANGE_RENAME:
2589 return file_data_perform_move(fd); /* the same as move */
2590 case FILEDATA_CHANGE_DELETE:
2591 return file_data_perform_delete(fd);
2592 case FILEDATA_CHANGE_WRITE_METADATA:
2593 return metadata_write_perform(fd);
2594 case FILEDATA_CHANGE_UNSPECIFIED:
2595 /* nothing to do here */
2603 gboolean file_data_sc_perform_ci(FileData *fd)
2606 gboolean ret = TRUE;
2607 FileDataChangeType type = fd->change->type;
2609 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2611 work = fd->sidecar_files;
2614 FileData *sfd = work->data;
2616 if (!file_data_perform_ci(sfd)) ret = FALSE;
2620 if (!file_data_perform_ci(fd)) ret = FALSE;
2626 * updates FileData structure according to FileDataChangeInfo
2629 gboolean file_data_apply_ci(FileData *fd)
2631 FileDataChangeType type = fd->change->type;
2634 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2636 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2637 file_data_planned_change_remove(fd);
2639 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2641 /* this change overwrites another file which is already known to other modules
2642 renaming fd would create duplicate FileData structure
2643 the best thing we can do is nothing
2644 FIXME: maybe we could copy stuff like marks
2646 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2650 file_data_set_path(fd, fd->change->dest);
2653 file_data_increment_version(fd);
2654 file_data_send_notification(fd, NOTIFY_CHANGE);
2659 gboolean file_data_sc_apply_ci(FileData *fd)
2662 FileDataChangeType type = fd->change->type;
2664 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2666 work = fd->sidecar_files;
2669 FileData *sfd = work->data;
2671 file_data_apply_ci(sfd);
2675 file_data_apply_ci(fd);
2680 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2683 if (fd->parent) fd = fd->parent;
2684 if (!g_list_find(list, fd)) return FALSE;
2686 work = fd->sidecar_files;
2689 if (!g_list_find(list, work->data)) return FALSE;
2695 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2700 /* change partial groups to independent files */
2705 FileData *fd = work->data;
2708 if (!file_data_list_contains_whole_group(list, fd))
2710 file_data_disable_grouping(fd, TRUE);
2713 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2719 /* remove sidecars from the list,
2720 they can be still acessed via main_fd->sidecar_files */
2724 FileData *fd = work->data;
2728 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2730 out = g_list_prepend(out, file_data_ref(fd));
2734 filelist_free(list);
2735 out = g_list_reverse(out);
2745 * notify other modules about the change described by FileDataChangeInfo
2748 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2749 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2750 implementation in view_file_list.c */
2753 typedef struct _NotifyIdleData NotifyIdleData;
2755 struct _NotifyIdleData {
2761 typedef struct _NotifyData NotifyData;
2763 struct _NotifyData {
2764 FileDataNotifyFunc func;
2766 NotifyPriority priority;
2769 static GList *notify_func_list = NULL;
2771 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2773 NotifyData *nda = (NotifyData *)a;
2774 NotifyData *ndb = (NotifyData *)b;
2776 if (nda->priority < ndb->priority) return -1;
2777 if (nda->priority > ndb->priority) return 1;
2781 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2784 GList *work = notify_func_list;
2788 NotifyData *nd = (NotifyData *)work->data;
2790 if (nd->func == func && nd->data == data)
2792 g_warning("Notify func already registered");
2798 nd = g_new(NotifyData, 1);
2801 nd->priority = priority;
2803 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2804 DEBUG_2("Notify func registered: %p", nd);
2809 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2811 GList *work = notify_func_list;
2815 NotifyData *nd = (NotifyData *)work->data;
2817 if (nd->func == func && nd->data == data)
2819 notify_func_list = g_list_delete_link(notify_func_list, work);
2821 DEBUG_2("Notify func unregistered: %p", nd);
2827 g_warning("Notify func not found");
2832 gboolean file_data_send_notification_idle_cb(gpointer data)
2834 NotifyIdleData *nid = (NotifyIdleData *)data;
2835 GList *work = notify_func_list;
2839 NotifyData *nd = (NotifyData *)work->data;
2841 nd->func(nid->fd, nid->type, nd->data);
2844 file_data_unref(nid->fd);
2849 void file_data_send_notification(FileData *fd, NotifyType type)
2851 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
2852 nid->fd = file_data_ref(fd);
2854 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
2857 static GHashTable *file_data_monitor_pool = NULL;
2858 static guint realtime_monitor_id = 0; /* event source id */
2860 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2864 file_data_check_changed_files(fd);
2866 DEBUG_1("monitor %s", fd->path);
2869 static gboolean realtime_monitor_cb(gpointer data)
2871 if (!options->update_on_time_change) return TRUE;
2872 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2876 gboolean file_data_register_real_time_monitor(FileData *fd)
2882 if (!file_data_monitor_pool)
2883 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2885 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2887 DEBUG_1("Register realtime %d %s", count, fd->path);
2890 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2892 if (!realtime_monitor_id)
2894 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2900 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2904 g_assert(file_data_monitor_pool);
2906 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2908 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2910 g_assert(count > 0);
2915 g_hash_table_remove(file_data_monitor_pool, fd);
2917 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2919 file_data_unref(fd);
2921 if (g_hash_table_size(file_data_monitor_pool) == 0)
2923 g_source_remove(realtime_monitor_id);
2924 realtime_monitor_id = 0;
2930 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */