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"
29 static GHashTable *file_data_pool = NULL;
30 static GHashTable *file_data_planned_change_hash = NULL;
32 static gint sidecar_file_priority(const gchar *extension);
33 static void file_data_check_sidecars(const GList *basename_list);
34 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
37 static SortType filelist_sort_method = SORT_NONE;
38 static gboolean filelist_sort_ascend = TRUE;
41 *-----------------------------------------------------------------------------
42 * text conversion utils
43 *-----------------------------------------------------------------------------
46 gchar *text_from_size(gint64 size)
52 /* what I would like to use is printf("%'d", size)
53 * BUT: not supported on every libc :(
57 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
58 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
62 a = g_strdup_printf("%d", (guint)size);
68 b = g_new(gchar, l + n + 1);
93 gchar *text_from_size_abrev(gint64 size)
95 if (size < (gint64)1024)
97 return g_strdup_printf(_("%d bytes"), (gint)size);
99 if (size < (gint64)1048576)
101 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
103 if (size < (gint64)1073741824)
105 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
108 /* to avoid overflowing the gdouble, do division in two steps */
110 return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
113 /* note: returned string is valid until next call to text_from_time() */
114 const gchar *text_from_time(time_t t)
116 static gchar *ret = NULL;
120 GError *error = NULL;
122 btime = localtime(&t);
124 /* the %x warning about 2 digit years is not an error */
125 buflen = strftime(buf, sizeof(buf), "%x %X", btime);
126 if (buflen < 1) return "";
129 ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
132 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
141 *-----------------------------------------------------------------------------
142 * changed files detection and notification
143 *-----------------------------------------------------------------------------
146 void file_data_increment_version(FileData *fd)
152 fd->parent->version++;
153 fd->parent->valid_marks = 0;
157 static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st)
159 if (fd->size != st->st_size ||
160 fd->date != st->st_mtime)
162 fd->size = st->st_size;
163 fd->date = st->st_mtime;
164 fd->mode = st->st_mode;
165 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
166 fd->thumb_pixbuf = NULL;
167 file_data_increment_version(fd);
168 file_data_send_notification(fd, NOTIFY_REREAD);
174 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
176 gboolean ret = FALSE;
179 ret = file_data_check_changed_single_file(fd, st);
181 work = fd->sidecar_files;
184 FileData *sfd = work->data;
188 if (!stat_utf8(sfd->path, &st))
193 file_data_disconnect_sidecar_file(fd, sfd);
195 file_data_increment_version(sfd);
196 file_data_send_notification(sfd, NOTIFY_REREAD);
197 file_data_unref(sfd);
201 ret |= file_data_check_changed_files_recursive(sfd, &st);
207 gboolean file_data_check_changed_files(FileData *fd)
209 gboolean ret = FALSE;
212 if (fd->parent) fd = fd->parent;
214 if (!stat_utf8(fd->path, &st))
218 FileData *sfd = NULL;
220 /* parent is missing, we have to rebuild whole group */
225 /* file_data_disconnect_sidecar_file might delete the file,
226 we have to keep the reference to prevent this */
227 sidecars = filelist_copy(fd->sidecar_files);
235 file_data_disconnect_sidecar_file(fd, sfd);
237 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
238 /* now we can release the sidecars */
239 filelist_free(sidecars);
240 file_data_increment_version(fd);
241 file_data_send_notification(fd, NOTIFY_REREAD);
246 ret |= file_data_check_changed_files_recursive(fd, &st);
253 *-----------------------------------------------------------------------------
254 * file name, extension, sorting, ...
255 *-----------------------------------------------------------------------------
258 static void file_data_set_collate_keys(FileData *fd)
260 gchar *caseless_name;
263 valid_name = g_filename_display_name(fd->name);
264 caseless_name = g_utf8_casefold(valid_name, -1);
266 g_free(fd->collate_key_name);
267 g_free(fd->collate_key_name_nocase);
269 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
270 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
273 g_free(caseless_name);
276 static void file_data_set_path(FileData *fd, const gchar *path)
278 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
279 g_assert(file_data_pool);
283 if (fd->original_path)
285 g_hash_table_remove(file_data_pool, fd->original_path);
286 g_free(fd->original_path);
289 g_assert(!g_hash_table_lookup(file_data_pool, path));
291 fd->original_path = g_strdup(path);
292 g_hash_table_insert(file_data_pool, fd->original_path, fd);
294 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
296 fd->path = g_strdup(path);
298 fd->extension = fd->name + 1;
299 file_data_set_collate_keys(fd);
303 fd->path = g_strdup(path);
304 fd->name = filename_from_path(fd->path);
306 if (strcmp(fd->name, "..") == 0)
308 gchar *dir = remove_level_from_path(path);
310 fd->path = remove_level_from_path(dir);
313 fd->extension = fd->name + 2;
314 file_data_set_collate_keys(fd);
317 else if (strcmp(fd->name, ".") == 0)
320 fd->path = remove_level_from_path(path);
322 fd->extension = fd->name + 1;
323 file_data_set_collate_keys(fd);
327 fd->extension = registered_extension_from_path(fd->path);
328 if (fd->extension == NULL)
330 fd->extension = fd->name + strlen(fd->name);
333 fd->sidecar_priority = sidecar_file_priority(fd->extension);
334 file_data_set_collate_keys(fd);
338 *-----------------------------------------------------------------------------
339 * create or reuse Filedata
340 *-----------------------------------------------------------------------------
343 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
347 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
349 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
352 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
354 fd = g_hash_table_lookup(file_data_pool, path_utf8);
360 if (!fd && file_data_planned_change_hash)
362 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
365 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
367 file_data_apply_ci(fd);
375 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
378 changed = file_data_check_changed_single_file(fd, st);
380 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
385 fd = g_new0(FileData, 1);
387 fd->size = st->st_size;
388 fd->date = st->st_mtime;
389 fd->mode = st->st_mode;
391 fd->magick = FD_MAGICK;
393 if (disable_sidecars) fd->disable_grouping = TRUE;
395 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
400 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
402 gchar *path_utf8 = path_to_utf8(path);
403 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
409 void init_exif_time_data(GList *files)
412 DEBUG_1("%s init_exif_time_data: ...", get_exec_time());
424 void read_exif_time_data(FileData *file)
426 if (file->exifdate > 0)
428 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
432 file->exif = exif_read_fd(file);
436 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
437 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), file, file->path);
442 uint year, month, day, hour, min, sec;
444 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
445 time_str.tm_year = year - 1900;
446 time_str.tm_mon = month - 1;
447 time_str.tm_mday = day;
448 time_str.tm_hour = hour;
449 time_str.tm_min = min;
450 time_str.tm_sec = sec;
451 time_str.tm_isdst = 0;
453 file->exifdate = mktime(&time_str);
459 void set_exif_time_data(GList *files)
461 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
465 FileData *file = files->data;
467 read_exif_time_data(file);
472 FileData *file_data_new_no_grouping(const gchar *path_utf8)
476 if (!stat_utf8(path_utf8, &st))
482 return file_data_new(path_utf8, &st, TRUE);
485 FileData *file_data_new_dir(const gchar *path_utf8)
489 if (!stat_utf8(path_utf8, &st))
495 /* dir or non-existing yet */
496 g_assert(S_ISDIR(st.st_mode));
498 return file_data_new(path_utf8, &st, TRUE);
502 *-----------------------------------------------------------------------------
504 *-----------------------------------------------------------------------------
507 #ifdef DEBUG_FILEDATA
508 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
510 FileData *file_data_ref(FileData *fd)
513 if (fd == NULL) return NULL;
514 if (fd->magick != FD_MAGICK)
515 #ifdef DEBUG_FILEDATA
516 DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
518 DEBUG_0("fd magick mismatch fd=%p", fd);
520 g_assert(fd->magick == FD_MAGICK);
523 #ifdef DEBUG_FILEDATA
524 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", fd, fd->ref, fd->path, file, line);
526 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
531 static void file_data_free(FileData *fd)
533 g_assert(fd->magick == FD_MAGICK);
534 g_assert(fd->ref == 0);
536 metadata_cache_free(fd);
537 g_hash_table_remove(file_data_pool, fd->original_path);
540 g_free(fd->original_path);
541 g_free(fd->collate_key_name);
542 g_free(fd->collate_key_name_nocase);
543 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
544 histmap_free(fd->histmap);
546 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
548 file_data_change_info_free(NULL, fd);
553 * \brief Checks if the FileData is referenced
555 * Checks the refcount and whether the FileData is locked.
557 static gboolean file_data_check_has_ref(FileData *fd)
559 return fd->ref > 0 || fd->locked;
563 * \brief Consider freeing a FileData.
565 * This function will free a FileData and its children provided that neither its parent nor it has
566 * a positive refcount, and provided that neither is locked.
568 static void file_data_consider_free(FileData *fd)
571 FileData *parent = fd->parent ? fd->parent : fd;
573 g_assert(fd->magick == FD_MAGICK);
574 if (file_data_check_has_ref(fd)) return;
575 if (file_data_check_has_ref(parent)) return;
577 work = parent->sidecar_files;
580 FileData *sfd = work->data;
581 if (file_data_check_has_ref(sfd)) return;
585 /* Neither the parent nor the siblings are referenced, so we can free everything */
586 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
587 fd->path, fd->parent ? parent->path : "-");
589 work = parent->sidecar_files;
592 FileData *sfd = work->data;
597 g_list_free(parent->sidecar_files);
598 parent->sidecar_files = NULL;
600 file_data_free(parent);
603 #ifdef DEBUG_FILEDATA
604 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
606 void file_data_unref(FileData *fd)
609 if (fd == NULL) return;
610 if (fd->magick != FD_MAGICK)
611 #ifdef DEBUG_FILEDATA
612 DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
614 DEBUG_0("fd magick mismatch fd=%p", fd);
616 g_assert(fd->magick == FD_MAGICK);
619 #ifdef DEBUG_FILEDATA
620 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", fd, fd->ref, fd->locked, fd->path,
623 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
626 // Free FileData if it's no longer ref'd
627 file_data_consider_free(fd);
631 * \brief Lock the FileData in memory.
633 * This allows the caller to prevent a FileData from being freed, even
634 * after its refcount is zero.
636 * This differs from file_data_ref in that the behavior is reentrant -- after N calls to
637 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
639 void file_data_lock(FileData *fd)
641 if (fd == NULL) return;
642 if (fd->magick != FD_MAGICK) DEBUG_0("fd magick mismatch fd=%p", fd);
644 g_assert(fd->magick == FD_MAGICK);
647 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
651 * \brief Reset the maintain-FileData-in-memory lock
653 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
654 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
655 * keeping it from being freed.
657 void file_data_unlock(FileData *fd)
659 if (fd == NULL) return;
660 if (fd->magick != FD_MAGICK) DEBUG_0("fd magick mismatch fd=%p", fd);
662 g_assert(fd->magick == FD_MAGICK);
665 // Free FileData if it's no longer ref'd
666 file_data_consider_free(fd);
670 *-----------------------------------------------------------------------------
671 * sidecar file info struct
672 *-----------------------------------------------------------------------------
675 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
677 const FileData *fda = a;
678 const FileData *fdb = b;
680 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
681 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
683 return strcmp(fdb->extension, fda->extension);
687 static gint sidecar_file_priority(const gchar *extension)
692 if (extension == NULL)
695 work = sidecar_ext_get_list();
698 gchar *ext = work->data;
701 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
707 static void file_data_check_sidecars(const GList *basename_list)
709 /* basename_list contains the new group - first is the parent, then sorted sidecars */
710 /* all files in the list have ref count > 0 */
713 GList *s_work, *new_sidecars;
716 if (!basename_list) return;
719 DEBUG_2("basename start");
720 work = basename_list;
723 FileData *fd = work->data;
725 g_assert(fd->magick == FD_MAGICK);
726 DEBUG_2("basename: %p %s", fd, fd->name);
729 g_assert(fd->parent->magick == FD_MAGICK);
730 DEBUG_2(" parent: %p", fd->parent);
732 s_work = fd->sidecar_files;
735 FileData *sfd = s_work->data;
736 s_work = s_work->next;
737 g_assert(sfd->magick == FD_MAGICK);
738 DEBUG_2(" sidecar: %p %s", sfd, sfd->name);
741 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
744 parent_fd = basename_list->data;
746 /* check if the second and next entries of basename_list are already connected
747 as sidecars of the first entry (parent_fd) */
748 work = basename_list->next;
749 s_work = parent_fd->sidecar_files;
751 while (work && s_work)
753 if (work->data != s_work->data) break;
755 s_work = s_work->next;
758 if (!work && !s_work)
760 DEBUG_2("basename no change");
761 return; /* no change in grouping */
764 /* we have to regroup it */
766 /* first, disconnect everything and send notification*/
768 work = basename_list;
771 FileData *fd = work->data;
773 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
777 FileData *old_parent = fd->parent;
778 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
779 file_data_ref(old_parent);
780 file_data_disconnect_sidecar_file(old_parent, fd);
781 file_data_send_notification(old_parent, NOTIFY_REREAD);
782 file_data_unref(old_parent);
785 while (fd->sidecar_files)
787 FileData *sfd = fd->sidecar_files->data;
788 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
790 file_data_disconnect_sidecar_file(fd, sfd);
791 file_data_send_notification(sfd, NOTIFY_REREAD);
792 file_data_unref(sfd);
794 file_data_send_notification(fd, NOTIFY_GROUPING);
796 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
799 /* now we can form the new group */
800 work = basename_list->next;
804 FileData *sfd = work->data;
805 g_assert(sfd->magick == FD_MAGICK);
806 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
807 sfd->parent = parent_fd;
808 new_sidecars = g_list_prepend(new_sidecars, sfd);
811 g_assert(parent_fd->sidecar_files == NULL);
812 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
813 DEBUG_1("basename group changed for %s", parent_fd->path);
817 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
819 g_assert(target->magick == FD_MAGICK);
820 g_assert(sfd->magick == FD_MAGICK);
821 g_assert(g_list_find(target->sidecar_files, sfd));
823 file_data_ref(target);
826 g_assert(sfd->parent == target);
828 file_data_increment_version(sfd); /* increments both sfd and target */
830 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
833 file_data_unref(target);
834 file_data_unref(sfd);
837 /* disables / enables grouping for particular file, sends UPDATE notification */
838 void file_data_disable_grouping(FileData *fd, gboolean disable)
840 if (!fd->disable_grouping == !disable) return;
842 fd->disable_grouping = !!disable;
848 FileData *parent = file_data_ref(fd->parent);
849 file_data_disconnect_sidecar_file(parent, fd);
850 file_data_send_notification(parent, NOTIFY_GROUPING);
851 file_data_unref(parent);
853 else if (fd->sidecar_files)
855 GList *sidecar_files = filelist_copy(fd->sidecar_files);
856 GList *work = sidecar_files;
859 FileData *sfd = work->data;
861 file_data_disconnect_sidecar_file(fd, sfd);
862 file_data_send_notification(sfd, NOTIFY_GROUPING);
864 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
865 filelist_free(sidecar_files);
869 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
874 file_data_increment_version(fd);
875 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
877 file_data_send_notification(fd, NOTIFY_GROUPING);
880 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
887 FileData *fd = work->data;
889 file_data_disable_grouping(fd, disable);
897 *-----------------------------------------------------------------------------
899 *-----------------------------------------------------------------------------
903 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
906 if (!filelist_sort_ascend)
913 switch (filelist_sort_method)
918 if (fa->size < fb->size) return -1;
919 if (fa->size > fb->size) return 1;
920 /* fall back to name */
923 if (fa->date < fb->date) return -1;
924 if (fa->date > fb->date) return 1;
925 /* fall back to name */
928 if (fa->exifdate < fb->exifdate) return -1;
929 if (fa->exifdate > fb->exifdate) return 1;
930 /* fall back to name */
932 #ifdef HAVE_STRVERSCMP
934 ret = strverscmp(fa->name, fb->name);
935 if (ret != 0) return ret;
942 if (options->file_sort.case_sensitive)
943 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
945 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
947 if (ret != 0) return ret;
949 /* do not return 0 unless the files are really the same
950 file_data_pool ensures that original_path is unique
952 return strcmp(fa->original_path, fb->original_path);
955 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
957 filelist_sort_method = method;
958 filelist_sort_ascend = ascend;
959 return filelist_sort_compare_filedata(fa, fb);
962 static gint filelist_sort_file_cb(gpointer a, gpointer b)
964 return filelist_sort_compare_filedata(a, b);
967 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
969 filelist_sort_method = method;
970 filelist_sort_ascend = ascend;
971 return g_list_sort(list, cb);
974 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
976 filelist_sort_method = method;
977 filelist_sort_ascend = ascend;
978 return g_list_insert_sorted(list, data, cb);
981 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
983 if (method == SORT_EXIFTIME)
985 set_exif_time_data(list);
987 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
990 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
992 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
996 *-----------------------------------------------------------------------------
997 * basename hash - grouping of sidecars in filelist
998 *-----------------------------------------------------------------------------
1002 static GHashTable *file_data_basename_hash_new(void)
1004 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1007 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1010 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1012 list = g_hash_table_lookup(basename_hash, basename);
1014 if (!g_list_find(list, fd))
1016 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1017 g_hash_table_insert(basename_hash, basename, list);
1026 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
1028 filelist_free((GList *)value);
1031 static void file_data_basename_hash_free(GHashTable *basename_hash)
1033 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1034 g_hash_table_destroy(basename_hash);
1038 *-----------------------------------------------------------------------------
1039 * handling sidecars in filelist
1040 *-----------------------------------------------------------------------------
1043 static GList *filelist_filter_out_sidecars(GList *flist)
1045 GList *work = flist;
1046 GList *flist_filtered = NULL;
1050 FileData *fd = work->data;
1053 if (fd->parent) /* remove fd's that are children */
1054 file_data_unref(fd);
1056 flist_filtered = g_list_prepend(flist_filtered, fd);
1060 return flist_filtered;
1063 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
1065 GList *basename_list = (GList *)value;
1066 file_data_check_sidecars(basename_list);
1070 static gboolean is_hidden_file(const gchar *name)
1072 if (name[0] != '.') return FALSE;
1073 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1078 *-----------------------------------------------------------------------------
1079 * the main filelist function
1080 *-----------------------------------------------------------------------------
1083 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1088 GList *dlist = NULL;
1089 GList *flist = NULL;
1090 gint (*stat_func)(const gchar *path, struct stat *buf);
1091 GHashTable *basename_hash = NULL;
1093 g_assert(files || dirs);
1095 if (files) *files = NULL;
1096 if (dirs) *dirs = NULL;
1098 pathl = path_from_utf8(dir_path);
1099 if (!pathl) return FALSE;
1101 dp = opendir(pathl);
1108 if (files) basename_hash = file_data_basename_hash_new();
1110 if (follow_symlinks)
1115 while ((dir = readdir(dp)) != NULL)
1117 struct stat ent_sbuf;
1118 const gchar *name = dir->d_name;
1121 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1124 filepath = g_build_filename(pathl, name, NULL);
1125 if (stat_func(filepath, &ent_sbuf) >= 0)
1127 if (S_ISDIR(ent_sbuf.st_mode))
1129 /* we ignore the .thumbnails dir for cleanliness */
1131 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1132 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1133 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1134 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1136 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1141 if (files && filter_name_exists(name))
1143 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1144 flist = g_list_prepend(flist, fd);
1145 if (fd->sidecar_priority && !fd->disable_grouping)
1147 file_data_basename_hash_insert(basename_hash, fd);
1154 if (errno == EOVERFLOW)
1156 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1166 if (dirs) *dirs = dlist;
1170 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1172 *files = filelist_filter_out_sidecars(flist);
1174 if (basename_hash) file_data_basename_hash_free(basename_hash);
1176 // Call a separate function to initialize the exif datestamps for the found files..
1177 if (files) init_exif_time_data(*files);
1182 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1184 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1187 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1189 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1192 FileData *file_data_new_group(const gchar *path_utf8)
1199 if (!stat_utf8(path_utf8, &st))
1205 if (S_ISDIR(st.st_mode))
1206 return file_data_new(path_utf8, &st, TRUE);
1208 dir = remove_level_from_path(path_utf8);
1210 filelist_read_real(dir, &files, NULL, TRUE);
1212 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1216 filelist_free(files);
1222 void filelist_free(GList *list)
1229 file_data_unref((FileData *)work->data);
1237 GList *filelist_copy(GList *list)
1239 GList *new_list = NULL;
1250 new_list = g_list_prepend(new_list, file_data_ref(fd));
1253 return g_list_reverse(new_list);
1256 GList *filelist_from_path_list(GList *list)
1258 GList *new_list = NULL;
1269 new_list = g_list_prepend(new_list, file_data_new_group(path));
1272 return g_list_reverse(new_list);
1275 GList *filelist_to_path_list(GList *list)
1277 GList *new_list = NULL;
1288 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1291 return g_list_reverse(new_list);
1294 GList *filelist_filter(GList *list, gboolean is_dir_list)
1298 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1303 FileData *fd = (FileData *)(work->data);
1304 const gchar *name = fd->name;
1306 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1307 (!is_dir_list && !filter_name_exists(name)) ||
1308 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1309 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1313 list = g_list_remove_link(list, link);
1314 file_data_unref(fd);
1325 *-----------------------------------------------------------------------------
1326 * filelist recursive
1327 *-----------------------------------------------------------------------------
1330 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1332 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1335 GList *filelist_sort_path(GList *list)
1337 return g_list_sort(list, filelist_sort_path_cb);
1340 static void filelist_recursive_append(GList **list, GList *dirs)
1347 FileData *fd = (FileData *)(work->data);
1351 if (filelist_read(fd, &f, &d))
1353 f = filelist_filter(f, FALSE);
1354 f = filelist_sort_path(f);
1355 *list = g_list_concat(*list, f);
1357 d = filelist_filter(d, TRUE);
1358 d = filelist_sort_path(d);
1359 filelist_recursive_append(list, d);
1367 GList *filelist_recursive(FileData *dir_fd)
1372 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1373 list = filelist_filter(list, FALSE);
1374 list = filelist_sort_path(list);
1376 d = filelist_filter(d, TRUE);
1377 d = filelist_sort_path(d);
1378 filelist_recursive_append(&list, d);
1385 *-----------------------------------------------------------------------------
1386 * file modification support
1387 *-----------------------------------------------------------------------------
1391 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1393 if (!fdci && fd) fdci = fd->change;
1397 g_free(fdci->source);
1402 if (fd) fd->change = NULL;
1405 static gboolean file_data_can_write_directly(FileData *fd)
1407 return filter_name_is_writable(fd->extension);
1410 static gboolean file_data_can_write_sidecar(FileData *fd)
1412 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1415 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1417 gchar *sidecar_path = NULL;
1420 if (!file_data_can_write_sidecar(fd)) return NULL;
1422 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1425 FileData *sfd = work->data;
1427 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
1429 sidecar_path = g_strdup(sfd->path);
1434 if (!existing_only && !sidecar_path)
1436 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1437 sidecar_path = g_strconcat(base, ".xmp", NULL);
1441 return sidecar_path;
1445 * marks and orientation
1448 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1449 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1450 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1451 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1453 gboolean file_data_get_mark(FileData *fd, gint n)
1455 gboolean valid = (fd->valid_marks & (1 << n));
1457 if (file_data_get_mark_func[n] && !valid)
1459 guint old = fd->marks;
1460 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1462 if (!value != !(fd->marks & (1 << n)))
1464 fd->marks = fd->marks ^ (1 << n);
1467 fd->valid_marks |= (1 << n);
1468 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1470 file_data_unref(fd);
1472 else if (!old && fd->marks)
1478 return !!(fd->marks & (1 << n));
1481 guint file_data_get_marks(FileData *fd)
1484 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1488 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1491 if (!value == !file_data_get_mark(fd, n)) return;
1493 if (file_data_set_mark_func[n])
1495 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1500 fd->marks = fd->marks ^ (1 << n);
1502 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1504 file_data_unref(fd);
1506 else if (!old && fd->marks)
1511 file_data_increment_version(fd);
1512 file_data_send_notification(fd, NOTIFY_MARKS);
1515 gboolean file_data_filter_marks(FileData *fd, guint filter)
1518 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1519 return ((fd->marks & filter) == filter);
1522 GList *file_data_filter_marks_list(GList *list, guint filter)
1529 FileData *fd = work->data;
1533 if (!file_data_filter_marks(fd, filter))
1535 list = g_list_remove_link(list, link);
1536 file_data_unref(fd);
1544 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1546 FileData *fd = value;
1547 file_data_increment_version(fd);
1548 file_data_send_notification(fd, NOTIFY_MARKS);
1551 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1553 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1555 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1557 file_data_get_mark_func[n] = get_mark_func;
1558 file_data_set_mark_func[n] = set_mark_func;
1559 file_data_mark_func_data[n] = data;
1560 file_data_destroy_mark_func[n] = notify;
1564 /* this effectively changes all known files */
1565 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1571 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1573 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1574 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1575 if (data) *data = file_data_mark_func_data[n];
1578 gint file_data_get_user_orientation(FileData *fd)
1580 return fd->user_orientation;
1583 void file_data_set_user_orientation(FileData *fd, gint value)
1585 if (fd->user_orientation == value) return;
1587 fd->user_orientation = value;
1588 file_data_increment_version(fd);
1589 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1594 * file_data - operates on the given fd
1595 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1599 /* return list of sidecar file extensions in a string */
1600 gchar *file_data_sc_list_to_string(FileData *fd)
1603 GString *result = g_string_new("");
1605 work = fd->sidecar_files;
1608 FileData *sfd = work->data;
1610 result = g_string_append(result, "+ ");
1611 result = g_string_append(result, sfd->extension);
1613 if (work) result = g_string_append_c(result, ' ');
1616 return g_string_free(result, FALSE);
1622 * add FileDataChangeInfo (see typedefs.h) for the given operation
1623 * uses file_data_add_change_info
1625 * fails if the fd->change already exists - change operations can't run in parallel
1626 * fd->change_info works as a lock
1628 * dest can be NULL - in this case the current name is used for now, it will
1633 FileDataChangeInfo types:
1635 MOVE - path is changed, name may be changed too
1636 RENAME - path remains unchanged, name is changed
1637 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1638 sidecar names are changed too, extensions are not changed
1640 UPDATE - file size, date or grouping has been changed
1643 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1645 FileDataChangeInfo *fdci;
1647 if (fd->change) return FALSE;
1649 fdci = g_new0(FileDataChangeInfo, 1);
1654 fdci->source = g_strdup(src);
1656 fdci->source = g_strdup(fd->path);
1659 fdci->dest = g_strdup(dest);
1666 static void file_data_planned_change_remove(FileData *fd)
1668 if (file_data_planned_change_hash &&
1669 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1671 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1673 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1674 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1675 file_data_unref(fd);
1676 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1678 g_hash_table_destroy(file_data_planned_change_hash);
1679 file_data_planned_change_hash = NULL;
1680 DEBUG_1("planned change: empty");
1687 void file_data_free_ci(FileData *fd)
1689 FileDataChangeInfo *fdci = fd->change;
1693 file_data_planned_change_remove(fd);
1695 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1697 g_free(fdci->source);
1705 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1707 FileDataChangeInfo *fdci = fd->change;
1709 fdci->regroup_when_finished = enable;
1712 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1716 if (fd->parent) fd = fd->parent;
1718 if (fd->change) return FALSE;
1720 work = fd->sidecar_files;
1723 FileData *sfd = work->data;
1725 if (sfd->change) return FALSE;
1729 file_data_add_ci(fd, type, NULL, NULL);
1731 work = fd->sidecar_files;
1734 FileData *sfd = work->data;
1736 file_data_add_ci(sfd, type, NULL, NULL);
1743 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1747 if (fd->parent) fd = fd->parent;
1749 if (!fd->change || fd->change->type != type) return FALSE;
1751 work = fd->sidecar_files;
1754 FileData *sfd = work->data;
1756 if (!sfd->change || sfd->change->type != type) return FALSE;
1764 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1766 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1767 file_data_sc_update_ci_copy(fd, dest_path);
1771 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1773 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1774 file_data_sc_update_ci_move(fd, dest_path);
1778 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1780 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1781 file_data_sc_update_ci_rename(fd, dest_path);
1785 gboolean file_data_sc_add_ci_delete(FileData *fd)
1787 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1790 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1792 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1793 file_data_sc_update_ci_unspecified(fd, dest_path);
1797 gboolean file_data_add_ci_write_metadata(FileData *fd)
1799 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1802 void file_data_sc_free_ci(FileData *fd)
1806 if (fd->parent) fd = fd->parent;
1808 file_data_free_ci(fd);
1810 work = fd->sidecar_files;
1813 FileData *sfd = work->data;
1815 file_data_free_ci(sfd);
1820 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1823 gboolean ret = TRUE;
1828 FileData *fd = work->data;
1830 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1837 static void file_data_sc_revert_ci_list(GList *fd_list)
1844 FileData *fd = work->data;
1846 file_data_sc_free_ci(fd);
1851 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1858 FileData *fd = work->data;
1860 if (!func(fd, dest))
1862 file_data_sc_revert_ci_list(work->prev);
1871 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1873 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1876 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1878 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1881 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1883 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1886 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1888 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1891 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1894 gboolean ret = TRUE;
1899 FileData *fd = work->data;
1901 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1908 void file_data_free_ci_list(GList *fd_list)
1915 FileData *fd = work->data;
1917 file_data_free_ci(fd);
1922 void file_data_sc_free_ci_list(GList *fd_list)
1929 FileData *fd = work->data;
1931 file_data_sc_free_ci(fd);
1937 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1938 * fails if fd->change does not exist or the change type does not match
1941 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1943 FileDataChangeType type = fd->change->type;
1945 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1949 if (!file_data_planned_change_hash)
1950 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1952 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1954 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1955 g_hash_table_remove(file_data_planned_change_hash, old_path);
1956 file_data_unref(fd);
1959 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1964 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1965 g_hash_table_remove(file_data_planned_change_hash, new_path);
1966 file_data_unref(ofd);
1969 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1971 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1976 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1978 gchar *old_path = fd->change->dest;
1980 fd->change->dest = g_strdup(dest_path);
1981 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1985 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1987 const gchar *extension = extension_from_path(fd->change->source);
1988 gchar *base = remove_extension_from_path(dest_path);
1989 gchar *old_path = fd->change->dest;
1991 fd->change->dest = g_strconcat(base, extension, NULL);
1992 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1998 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2001 gchar *dest_path_full = NULL;
2003 if (fd->parent) fd = fd->parent;
2007 dest_path = fd->path;
2009 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2011 gchar *dir = remove_level_from_path(fd->path);
2013 dest_path_full = g_build_filename(dir, dest_path, NULL);
2015 dest_path = dest_path_full;
2017 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2019 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2020 dest_path = dest_path_full;
2023 file_data_update_ci_dest(fd, dest_path);
2025 work = fd->sidecar_files;
2028 FileData *sfd = work->data;
2030 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2034 g_free(dest_path_full);
2037 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2039 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2040 file_data_sc_update_ci(fd, dest_path);
2044 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2046 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2049 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2051 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2054 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2056 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2059 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2061 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2064 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2066 gboolean (*func)(FileData *, const gchar *))
2069 gboolean ret = TRUE;
2074 FileData *fd = work->data;
2076 if (!func(fd, dest)) ret = FALSE;
2083 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2085 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2088 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2090 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2093 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2095 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2100 * verify source and dest paths - dest image exists, etc.
2101 * it should detect all possible problems with the planned operation
2104 gint file_data_verify_ci(FileData *fd)
2106 gint ret = CHANGE_OK;
2111 DEBUG_1("Change checked: no change info: %s", fd->path);
2115 if (!isname(fd->path))
2117 /* this probably should not happen */
2118 ret |= CHANGE_NO_SRC;
2119 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2123 dir = remove_level_from_path(fd->path);
2125 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2126 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2127 fd->change->type != FILEDATA_CHANGE_RENAME &&
2128 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2131 ret |= CHANGE_WARN_UNSAVED_META;
2132 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2135 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2136 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2137 !access_file(fd->path, R_OK))
2139 ret |= CHANGE_NO_READ_PERM;
2140 DEBUG_1("Change checked: no read permission: %s", fd->path);
2142 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2143 !access_file(dir, W_OK))
2145 ret |= CHANGE_NO_WRITE_PERM_DIR;
2146 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2148 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2149 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2150 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2151 !access_file(fd->path, W_OK))
2153 ret |= CHANGE_WARN_NO_WRITE_PERM;
2154 DEBUG_1("Change checked: no write permission: %s", fd->path);
2156 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2157 - that means that there are no hard errors and warnings can be disabled
2158 - the destination is determined during the check
2160 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2162 /* determine destination file */
2163 gboolean have_dest = FALSE;
2164 gchar *dest_dir = NULL;
2166 if (options->metadata.save_in_image_file)
2168 if (file_data_can_write_directly(fd))
2170 /* we can write the file directly */
2171 if (access_file(fd->path, W_OK))
2177 if (options->metadata.warn_on_write_problems)
2179 ret |= CHANGE_WARN_NO_WRITE_PERM;
2180 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2184 else if (file_data_can_write_sidecar(fd))
2186 /* we can write sidecar */
2187 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2188 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2190 file_data_update_ci_dest(fd, sidecar);
2195 if (options->metadata.warn_on_write_problems)
2197 ret |= CHANGE_WARN_NO_WRITE_PERM;
2198 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2207 /* write private metadata file under ~/.geeqie */
2209 /* If an existing metadata file exists, we will try writing to
2210 * it's location regardless of the user's preference.
2212 gchar *metadata_path = NULL;
2214 /* but ignore XMP if we are not able to write it */
2215 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2217 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2219 if (metadata_path && !access_file(metadata_path, W_OK))
2221 g_free(metadata_path);
2222 metadata_path = NULL;
2229 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2230 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2232 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2234 metadata_path = g_build_filename(dest_dir, filename, NULL);
2238 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2240 file_data_update_ci_dest(fd, metadata_path);
2245 ret |= CHANGE_NO_WRITE_PERM_DEST;
2246 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2248 g_free(metadata_path);
2253 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2258 same = (strcmp(fd->path, fd->change->dest) == 0);
2262 const gchar *dest_ext = extension_from_path(fd->change->dest);
2263 if (!dest_ext) dest_ext = "";
2265 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2267 ret |= CHANGE_WARN_CHANGED_EXT;
2268 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2273 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2275 ret |= CHANGE_WARN_SAME;
2276 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2280 dest_dir = remove_level_from_path(fd->change->dest);
2282 if (!isdir(dest_dir))
2284 ret |= CHANGE_NO_DEST_DIR;
2285 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2287 else if (!access_file(dest_dir, W_OK))
2289 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2290 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2294 if (isfile(fd->change->dest))
2296 if (!access_file(fd->change->dest, W_OK))
2298 ret |= CHANGE_NO_WRITE_PERM_DEST;
2299 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2303 ret |= CHANGE_WARN_DEST_EXISTS;
2304 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2307 else if (isdir(fd->change->dest))
2309 ret |= CHANGE_DEST_EXISTS;
2310 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2317 fd->change->error = ret;
2318 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2325 gint file_data_sc_verify_ci(FileData *fd)
2330 ret = file_data_verify_ci(fd);
2332 work = fd->sidecar_files;
2335 FileData *sfd = work->data;
2337 ret |= file_data_verify_ci(sfd);
2344 gchar *file_data_get_error_string(gint error)
2346 GString *result = g_string_new("");
2348 if (error & CHANGE_NO_SRC)
2350 if (result->len > 0) g_string_append(result, ", ");
2351 g_string_append(result, _("file or directory does not exist"));
2354 if (error & CHANGE_DEST_EXISTS)
2356 if (result->len > 0) g_string_append(result, ", ");
2357 g_string_append(result, _("destination already exists"));
2360 if (error & CHANGE_NO_WRITE_PERM_DEST)
2362 if (result->len > 0) g_string_append(result, ", ");
2363 g_string_append(result, _("destination can't be overwritten"));
2366 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2368 if (result->len > 0) g_string_append(result, ", ");
2369 g_string_append(result, _("destination directory is not writable"));
2372 if (error & CHANGE_NO_DEST_DIR)
2374 if (result->len > 0) g_string_append(result, ", ");
2375 g_string_append(result, _("destination directory does not exist"));
2378 if (error & CHANGE_NO_WRITE_PERM_DIR)
2380 if (result->len > 0) g_string_append(result, ", ");
2381 g_string_append(result, _("source directory is not writable"));
2384 if (error & CHANGE_NO_READ_PERM)
2386 if (result->len > 0) g_string_append(result, ", ");
2387 g_string_append(result, _("no read permission"));
2390 if (error & CHANGE_WARN_NO_WRITE_PERM)
2392 if (result->len > 0) g_string_append(result, ", ");
2393 g_string_append(result, _("file is readonly"));
2396 if (error & CHANGE_WARN_DEST_EXISTS)
2398 if (result->len > 0) g_string_append(result, ", ");
2399 g_string_append(result, _("destination already exists and will be overwritten"));
2402 if (error & CHANGE_WARN_SAME)
2404 if (result->len > 0) g_string_append(result, ", ");
2405 g_string_append(result, _("source and destination are the same"));
2408 if (error & CHANGE_WARN_CHANGED_EXT)
2410 if (result->len > 0) g_string_append(result, ", ");
2411 g_string_append(result, _("source and destination have different extension"));
2414 if (error & CHANGE_WARN_UNSAVED_META)
2416 if (result->len > 0) g_string_append(result, ", ");
2417 g_string_append(result, _("there are unsaved metadata changes for the file"));
2420 return g_string_free(result, FALSE);
2423 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2426 gint all_errors = 0;
2427 gint common_errors = ~0;
2432 if (!list) return 0;
2434 num = g_list_length(list);
2435 errors = g_new(int, num);
2446 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2447 all_errors |= error;
2448 common_errors &= error;
2455 if (desc && all_errors)
2458 GString *result = g_string_new("");
2462 gchar *str = file_data_get_error_string(common_errors);
2463 g_string_append(result, str);
2464 g_string_append(result, "\n");
2478 error = errors[i] & ~common_errors;
2482 gchar *str = file_data_get_error_string(error);
2483 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2488 *desc = g_string_free(result, FALSE);
2497 * perform the change described by FileFataChangeInfo
2498 * it is used for internal operations,
2499 * this function actually operates with files on the filesystem
2500 * it should implement safe delete
2503 static gboolean file_data_perform_move(FileData *fd)
2505 g_assert(!strcmp(fd->change->source, fd->path));
2506 return move_file(fd->change->source, fd->change->dest);
2509 static gboolean file_data_perform_copy(FileData *fd)
2511 g_assert(!strcmp(fd->change->source, fd->path));
2512 return copy_file(fd->change->source, fd->change->dest);
2515 static gboolean file_data_perform_delete(FileData *fd)
2517 if (isdir(fd->path) && !islink(fd->path))
2518 return rmdir_utf8(fd->path);
2520 if (options->file_ops.safe_delete_enable)
2521 return file_util_safe_unlink(fd->path);
2523 return unlink_file(fd->path);
2526 gboolean file_data_perform_ci(FileData *fd)
2528 FileDataChangeType type = fd->change->type;
2532 case FILEDATA_CHANGE_MOVE:
2533 return file_data_perform_move(fd);
2534 case FILEDATA_CHANGE_COPY:
2535 return file_data_perform_copy(fd);
2536 case FILEDATA_CHANGE_RENAME:
2537 return file_data_perform_move(fd); /* the same as move */
2538 case FILEDATA_CHANGE_DELETE:
2539 return file_data_perform_delete(fd);
2540 case FILEDATA_CHANGE_WRITE_METADATA:
2541 return metadata_write_perform(fd);
2542 case FILEDATA_CHANGE_UNSPECIFIED:
2543 /* nothing to do here */
2551 gboolean file_data_sc_perform_ci(FileData *fd)
2554 gboolean ret = TRUE;
2555 FileDataChangeType type = fd->change->type;
2557 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2559 work = fd->sidecar_files;
2562 FileData *sfd = work->data;
2564 if (!file_data_perform_ci(sfd)) ret = FALSE;
2568 if (!file_data_perform_ci(fd)) ret = FALSE;
2574 * updates FileData structure according to FileDataChangeInfo
2577 gboolean file_data_apply_ci(FileData *fd)
2579 FileDataChangeType type = fd->change->type;
2582 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2584 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2585 file_data_planned_change_remove(fd);
2587 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2589 /* this change overwrites another file which is already known to other modules
2590 renaming fd would create duplicate FileData structure
2591 the best thing we can do is nothing
2592 FIXME: maybe we could copy stuff like marks
2594 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2598 file_data_set_path(fd, fd->change->dest);
2601 file_data_increment_version(fd);
2602 file_data_send_notification(fd, NOTIFY_CHANGE);
2607 gboolean file_data_sc_apply_ci(FileData *fd)
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 file_data_apply_ci(sfd);
2623 file_data_apply_ci(fd);
2628 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2631 if (fd->parent) fd = fd->parent;
2632 if (!g_list_find(list, fd)) return FALSE;
2634 work = fd->sidecar_files;
2637 if (!g_list_find(list, work->data)) return FALSE;
2643 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2648 /* change partial groups to independent files */
2653 FileData *fd = work->data;
2656 if (!file_data_list_contains_whole_group(list, fd))
2658 file_data_disable_grouping(fd, TRUE);
2661 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2667 /* remove sidecars from the list,
2668 they can be still acessed via main_fd->sidecar_files */
2672 FileData *fd = work->data;
2676 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2678 out = g_list_prepend(out, file_data_ref(fd));
2682 filelist_free(list);
2683 out = g_list_reverse(out);
2693 * notify other modules about the change described by FileDataChangeInfo
2696 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2697 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2698 implementation in view_file_list.c */
2701 typedef struct _NotifyIdleData NotifyIdleData;
2703 struct _NotifyIdleData {
2709 typedef struct _NotifyData NotifyData;
2711 struct _NotifyData {
2712 FileDataNotifyFunc func;
2714 NotifyPriority priority;
2717 static GList *notify_func_list = NULL;
2719 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2721 NotifyData *nda = (NotifyData *)a;
2722 NotifyData *ndb = (NotifyData *)b;
2724 if (nda->priority < ndb->priority) return -1;
2725 if (nda->priority > ndb->priority) return 1;
2729 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2732 GList *work = notify_func_list;
2736 NotifyData *nd = (NotifyData *)work->data;
2738 if (nd->func == func && nd->data == data)
2740 g_warning("Notify func already registered");
2746 nd = g_new(NotifyData, 1);
2749 nd->priority = priority;
2751 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2752 DEBUG_2("Notify func registered: %p", nd);
2757 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2759 GList *work = notify_func_list;
2763 NotifyData *nd = (NotifyData *)work->data;
2765 if (nd->func == func && nd->data == data)
2767 notify_func_list = g_list_delete_link(notify_func_list, work);
2769 DEBUG_2("Notify func unregistered: %p", nd);
2775 g_warning("Notify func not found");
2780 gboolean file_data_send_notification_idle_cb(gpointer data)
2782 NotifyIdleData *nid = (NotifyIdleData *)data;
2783 GList *work = notify_func_list;
2787 NotifyData *nd = (NotifyData *)work->data;
2789 nd->func(nid->fd, nid->type, nd->data);
2792 file_data_unref(nid->fd);
2797 void file_data_send_notification(FileData *fd, NotifyType type)
2799 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
2800 nid->fd = file_data_ref(fd);
2802 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
2805 static GHashTable *file_data_monitor_pool = NULL;
2806 static guint realtime_monitor_id = 0; /* event source id */
2808 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2812 file_data_check_changed_files(fd);
2814 DEBUG_1("monitor %s", fd->path);
2817 static gboolean realtime_monitor_cb(gpointer data)
2819 if (!options->update_on_time_change) return TRUE;
2820 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2824 gboolean file_data_register_real_time_monitor(FileData *fd)
2830 if (!file_data_monitor_pool)
2831 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2833 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2835 DEBUG_1("Register realtime %d %s", count, fd->path);
2838 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2840 if (!realtime_monitor_id)
2842 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2848 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2852 g_assert(file_data_monitor_pool);
2854 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2856 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2858 g_assert(count > 0);
2863 g_hash_table_remove(file_data_monitor_pool, fd);
2865 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2867 file_data_unref(fd);
2869 if (g_hash_table_size(file_data_monitor_pool) == 0)
2871 g_source_remove(realtime_monitor_id);
2872 realtime_monitor_id = 0;
2878 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */