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->cdate = st->st_ctime;
169 fd->mode = st->st_mode;
170 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
171 fd->thumb_pixbuf = NULL;
172 file_data_increment_version(fd);
173 file_data_send_notification(fd, NOTIFY_REREAD);
179 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
181 gboolean ret = FALSE;
184 ret = file_data_check_changed_single_file(fd, st);
186 work = fd->sidecar_files;
189 FileData *sfd = work->data;
193 if (!stat_utf8(sfd->path, &st))
198 file_data_disconnect_sidecar_file(fd, sfd);
200 file_data_increment_version(sfd);
201 file_data_send_notification(sfd, NOTIFY_REREAD);
202 file_data_unref(sfd);
206 ret |= file_data_check_changed_files_recursive(sfd, &st);
212 gboolean file_data_check_changed_files(FileData *fd)
214 gboolean ret = FALSE;
217 if (fd->parent) fd = fd->parent;
219 if (!stat_utf8(fd->path, &st))
223 FileData *sfd = NULL;
225 /* parent is missing, we have to rebuild whole group */
230 /* file_data_disconnect_sidecar_file might delete the file,
231 we have to keep the reference to prevent this */
232 sidecars = filelist_copy(fd->sidecar_files);
240 file_data_disconnect_sidecar_file(fd, sfd);
242 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
243 /* now we can release the sidecars */
244 filelist_free(sidecars);
245 file_data_increment_version(fd);
246 file_data_send_notification(fd, NOTIFY_REREAD);
251 ret |= file_data_check_changed_files_recursive(fd, &st);
258 *-----------------------------------------------------------------------------
259 * file name, extension, sorting, ...
260 *-----------------------------------------------------------------------------
263 static void file_data_set_collate_keys(FileData *fd)
265 gchar *caseless_name;
268 valid_name = g_filename_display_name(fd->name);
269 caseless_name = g_utf8_casefold(valid_name, -1);
271 g_free(fd->collate_key_name);
272 g_free(fd->collate_key_name_nocase);
274 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
275 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
278 g_free(caseless_name);
281 static void file_data_set_path(FileData *fd, const gchar *path)
283 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
284 g_assert(file_data_pool);
288 if (fd->original_path)
290 g_hash_table_remove(file_data_pool, fd->original_path);
291 g_free(fd->original_path);
294 g_assert(!g_hash_table_lookup(file_data_pool, path));
296 fd->original_path = g_strdup(path);
297 g_hash_table_insert(file_data_pool, fd->original_path, fd);
299 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
301 fd->path = g_strdup(path);
303 fd->extension = fd->name + 1;
304 file_data_set_collate_keys(fd);
308 fd->path = g_strdup(path);
309 fd->name = filename_from_path(fd->path);
311 if (strcmp(fd->name, "..") == 0)
313 gchar *dir = remove_level_from_path(path);
315 fd->path = remove_level_from_path(dir);
318 fd->extension = fd->name + 2;
319 file_data_set_collate_keys(fd);
322 else if (strcmp(fd->name, ".") == 0)
325 fd->path = remove_level_from_path(path);
327 fd->extension = fd->name + 1;
328 file_data_set_collate_keys(fd);
332 fd->extension = registered_extension_from_path(fd->path);
333 if (fd->extension == NULL)
335 fd->extension = fd->name + strlen(fd->name);
338 fd->sidecar_priority = sidecar_file_priority(fd->extension);
339 file_data_set_collate_keys(fd);
343 *-----------------------------------------------------------------------------
344 * create or reuse Filedata
345 *-----------------------------------------------------------------------------
348 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
352 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
354 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
357 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
359 fd = g_hash_table_lookup(file_data_pool, path_utf8);
365 if (!fd && file_data_planned_change_hash)
367 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
370 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
372 file_data_apply_ci(fd);
380 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
383 changed = file_data_check_changed_single_file(fd, st);
385 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
390 fd = g_new0(FileData, 1);
391 #ifdef DEBUG_FILEDATA
392 global_file_data_count++;
393 DEBUG_2("file data count++: %d", global_file_data_count);
396 fd->size = st->st_size;
397 fd->date = st->st_mtime;
398 fd->cdate = st->st_ctime;
399 fd->mode = st->st_mode;
401 fd->magick = FD_MAGICK;
403 if (disable_sidecars) fd->disable_grouping = TRUE;
405 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
410 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
412 gchar *path_utf8 = path_to_utf8(path);
413 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
419 FileData *file_data_new_simple(const gchar *path_utf8)
424 if (!stat_utf8(path_utf8, &st))
430 fd = g_hash_table_lookup(file_data_pool, path_utf8);
431 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
440 void init_exif_time_data(GList *files)
443 DEBUG_1("%s init_exif_time_data: ...", get_exec_time());
455 void read_exif_time_data(FileData *file)
457 if (file->exifdate > 0)
459 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
463 file->exif = exif_read_fd(file);
467 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
468 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), file, file->path);
473 uint year, month, day, hour, min, sec;
475 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
476 time_str.tm_year = year - 1900;
477 time_str.tm_mon = month - 1;
478 time_str.tm_mday = day;
479 time_str.tm_hour = hour;
480 time_str.tm_min = min;
481 time_str.tm_sec = sec;
482 time_str.tm_isdst = 0;
484 file->exifdate = mktime(&time_str);
490 void set_exif_time_data(GList *files)
492 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
496 FileData *file = files->data;
498 read_exif_time_data(file);
503 FileData *file_data_new_no_grouping(const gchar *path_utf8)
507 if (!stat_utf8(path_utf8, &st))
513 return file_data_new(path_utf8, &st, TRUE);
516 FileData *file_data_new_dir(const gchar *path_utf8)
520 if (!stat_utf8(path_utf8, &st))
526 /* dir or non-existing yet */
527 g_assert(S_ISDIR(st.st_mode));
529 return file_data_new(path_utf8, &st, TRUE);
533 *-----------------------------------------------------------------------------
535 *-----------------------------------------------------------------------------
538 #ifdef DEBUG_FILEDATA
539 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
541 FileData *file_data_ref(FileData *fd)
544 if (fd == NULL) return NULL;
545 if (fd->magick != FD_MAGICK)
546 #ifdef DEBUG_FILEDATA
547 DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
549 DEBUG_0("fd magick mismatch fd=%p", fd);
551 g_assert(fd->magick == FD_MAGICK);
554 #ifdef DEBUG_FILEDATA
555 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", fd, fd->ref, fd->path, file, line);
557 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
562 static void file_data_free(FileData *fd)
564 g_assert(fd->magick == FD_MAGICK);
565 g_assert(fd->ref == 0);
566 g_assert(!fd->locked);
568 #ifdef DEBUG_FILEDATA
569 global_file_data_count--;
570 DEBUG_2("file data count--: %d", global_file_data_count);
573 metadata_cache_free(fd);
574 g_hash_table_remove(file_data_pool, fd->original_path);
577 g_free(fd->original_path);
578 g_free(fd->collate_key_name);
579 g_free(fd->collate_key_name_nocase);
580 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
581 histmap_free(fd->histmap);
583 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
585 file_data_change_info_free(NULL, fd);
590 * \brief Checks if the FileData is referenced
592 * Checks the refcount and whether the FileData is locked.
594 static gboolean file_data_check_has_ref(FileData *fd)
596 return fd->ref > 0 || fd->locked;
600 * \brief Consider freeing a FileData.
602 * This function will free a FileData and its children provided that neither its parent nor it has
603 * a positive refcount, and provided that neither is locked.
605 static void file_data_consider_free(FileData *fd)
608 FileData *parent = fd->parent ? fd->parent : fd;
610 g_assert(fd->magick == FD_MAGICK);
611 if (file_data_check_has_ref(fd)) return;
612 if (file_data_check_has_ref(parent)) return;
614 work = parent->sidecar_files;
617 FileData *sfd = work->data;
618 if (file_data_check_has_ref(sfd)) return;
622 /* Neither the parent nor the siblings are referenced, so we can free everything */
623 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
624 fd->path, fd->parent ? parent->path : "-");
626 work = parent->sidecar_files;
629 FileData *sfd = work->data;
634 g_list_free(parent->sidecar_files);
635 parent->sidecar_files = NULL;
637 file_data_free(parent);
640 #ifdef DEBUG_FILEDATA
641 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
643 void file_data_unref(FileData *fd)
646 if (fd == NULL) return;
647 if (fd->magick != FD_MAGICK)
648 #ifdef DEBUG_FILEDATA
649 DEBUG_0("fd magick mismatch @ %s:%d fd=%p", file, line, fd);
651 DEBUG_0("fd magick mismatch fd=%p", fd);
653 g_assert(fd->magick == FD_MAGICK);
656 #ifdef DEBUG_FILEDATA
657 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", fd, fd->ref, fd->locked, fd->path,
660 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
663 // Free FileData if it's no longer ref'd
664 file_data_consider_free(fd);
668 * \brief Lock the FileData in memory.
670 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
671 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
672 * even if the code would continue to function properly even if the FileData were freed. Code that
673 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
675 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
676 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
678 void file_data_lock(FileData *fd)
680 if (fd == NULL) return;
681 if (fd->magick != FD_MAGICK) DEBUG_0("fd magick mismatch fd=%p", fd);
683 g_assert(fd->magick == FD_MAGICK);
686 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
690 * \brief Reset the maintain-FileData-in-memory lock
692 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
693 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
694 * keeping it from being freed.
696 void file_data_unlock(FileData *fd)
698 if (fd == NULL) return;
699 if (fd->magick != FD_MAGICK) DEBUG_0("fd magick mismatch fd=%p", fd);
701 g_assert(fd->magick == FD_MAGICK);
704 // Free FileData if it's no longer ref'd
705 file_data_consider_free(fd);
709 * \brief Lock all of the FileDatas in the provided list
711 * \see file_data_lock(FileData)
713 void file_data_lock_list(GList *list)
720 FileData *fd = work->data;
727 * \brief Unlock all of the FileDatas in the provided list
729 * \see file_data_unlock(FileData)
731 void file_data_unlock_list(GList *list)
738 FileData *fd = work->data;
740 file_data_unlock(fd);
745 *-----------------------------------------------------------------------------
746 * sidecar file info struct
747 *-----------------------------------------------------------------------------
750 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
752 const FileData *fda = a;
753 const FileData *fdb = b;
755 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
756 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
758 return strcmp(fdb->extension, fda->extension);
762 static gint sidecar_file_priority(const gchar *extension)
767 if (extension == NULL)
770 work = sidecar_ext_get_list();
773 gchar *ext = work->data;
776 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
782 static void file_data_check_sidecars(const GList *basename_list)
784 /* basename_list contains the new group - first is the parent, then sorted sidecars */
785 /* all files in the list have ref count > 0 */
788 GList *s_work, *new_sidecars;
791 if (!basename_list) return;
794 DEBUG_2("basename start");
795 work = basename_list;
798 FileData *fd = work->data;
800 g_assert(fd->magick == FD_MAGICK);
801 DEBUG_2("basename: %p %s", fd, fd->name);
804 g_assert(fd->parent->magick == FD_MAGICK);
805 DEBUG_2(" parent: %p", fd->parent);
807 s_work = fd->sidecar_files;
810 FileData *sfd = s_work->data;
811 s_work = s_work->next;
812 g_assert(sfd->magick == FD_MAGICK);
813 DEBUG_2(" sidecar: %p %s", sfd, sfd->name);
816 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
819 parent_fd = basename_list->data;
821 /* check if the second and next entries of basename_list are already connected
822 as sidecars of the first entry (parent_fd) */
823 work = basename_list->next;
824 s_work = parent_fd->sidecar_files;
826 while (work && s_work)
828 if (work->data != s_work->data) break;
830 s_work = s_work->next;
833 if (!work && !s_work)
835 DEBUG_2("basename no change");
836 return; /* no change in grouping */
839 /* we have to regroup it */
841 /* first, disconnect everything and send notification*/
843 work = basename_list;
846 FileData *fd = work->data;
848 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
852 FileData *old_parent = fd->parent;
853 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
854 file_data_ref(old_parent);
855 file_data_disconnect_sidecar_file(old_parent, fd);
856 file_data_send_notification(old_parent, NOTIFY_REREAD);
857 file_data_unref(old_parent);
860 while (fd->sidecar_files)
862 FileData *sfd = fd->sidecar_files->data;
863 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
865 file_data_disconnect_sidecar_file(fd, sfd);
866 file_data_send_notification(sfd, NOTIFY_REREAD);
867 file_data_unref(sfd);
869 file_data_send_notification(fd, NOTIFY_GROUPING);
871 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
874 /* now we can form the new group */
875 work = basename_list->next;
879 FileData *sfd = work->data;
880 g_assert(sfd->magick == FD_MAGICK);
881 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
882 sfd->parent = parent_fd;
883 new_sidecars = g_list_prepend(new_sidecars, sfd);
886 g_assert(parent_fd->sidecar_files == NULL);
887 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
888 DEBUG_1("basename group changed for %s", parent_fd->path);
892 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
894 g_assert(target->magick == FD_MAGICK);
895 g_assert(sfd->magick == FD_MAGICK);
896 g_assert(g_list_find(target->sidecar_files, sfd));
898 file_data_ref(target);
901 g_assert(sfd->parent == target);
903 file_data_increment_version(sfd); /* increments both sfd and target */
905 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
908 file_data_unref(target);
909 file_data_unref(sfd);
912 /* disables / enables grouping for particular file, sends UPDATE notification */
913 void file_data_disable_grouping(FileData *fd, gboolean disable)
915 if (!fd->disable_grouping == !disable) return;
917 fd->disable_grouping = !!disable;
923 FileData *parent = file_data_ref(fd->parent);
924 file_data_disconnect_sidecar_file(parent, fd);
925 file_data_send_notification(parent, NOTIFY_GROUPING);
926 file_data_unref(parent);
928 else if (fd->sidecar_files)
930 GList *sidecar_files = filelist_copy(fd->sidecar_files);
931 GList *work = sidecar_files;
934 FileData *sfd = work->data;
936 file_data_disconnect_sidecar_file(fd, sfd);
937 file_data_send_notification(sfd, NOTIFY_GROUPING);
939 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
940 filelist_free(sidecar_files);
944 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
949 file_data_increment_version(fd);
950 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
952 file_data_send_notification(fd, NOTIFY_GROUPING);
955 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
962 FileData *fd = work->data;
964 file_data_disable_grouping(fd, disable);
972 *-----------------------------------------------------------------------------
974 *-----------------------------------------------------------------------------
978 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
981 if (!filelist_sort_ascend)
988 switch (filelist_sort_method)
993 if (fa->size < fb->size) return -1;
994 if (fa->size > fb->size) return 1;
995 /* fall back to name */
998 if (fa->date < fb->date) return -1;
999 if (fa->date > fb->date) return 1;
1000 /* fall back to name */
1003 if (fa->cdate < fb->cdate) return -1;
1004 if (fa->cdate > fb->cdate) return 1;
1005 /* fall back to name */
1008 if (fa->exifdate < fb->exifdate) return -1;
1009 if (fa->exifdate > fb->exifdate) return 1;
1010 /* fall back to name */
1012 #ifdef HAVE_STRVERSCMP
1014 ret = strverscmp(fa->name, fb->name);
1015 if (ret != 0) return ret;
1022 if (options->file_sort.case_sensitive)
1023 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1025 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1027 if (ret != 0) return ret;
1029 /* do not return 0 unless the files are really the same
1030 file_data_pool ensures that original_path is unique
1032 return strcmp(fa->original_path, fb->original_path);
1035 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1037 filelist_sort_method = method;
1038 filelist_sort_ascend = ascend;
1039 return filelist_sort_compare_filedata(fa, fb);
1042 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1044 return filelist_sort_compare_filedata(a, b);
1047 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1049 filelist_sort_method = method;
1050 filelist_sort_ascend = ascend;
1051 return g_list_sort(list, cb);
1054 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1056 filelist_sort_method = method;
1057 filelist_sort_ascend = ascend;
1058 return g_list_insert_sorted(list, data, cb);
1061 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1063 if (method == SORT_EXIFTIME)
1065 set_exif_time_data(list);
1067 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1070 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
1072 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1076 *-----------------------------------------------------------------------------
1077 * basename hash - grouping of sidecars in filelist
1078 *-----------------------------------------------------------------------------
1082 static GHashTable *file_data_basename_hash_new(void)
1084 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1087 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1090 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1092 list = g_hash_table_lookup(basename_hash, basename);
1094 if (!g_list_find(list, fd))
1096 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1097 g_hash_table_insert(basename_hash, basename, list);
1106 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
1108 filelist_free((GList *)value);
1111 static void file_data_basename_hash_free(GHashTable *basename_hash)
1113 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1114 g_hash_table_destroy(basename_hash);
1118 *-----------------------------------------------------------------------------
1119 * handling sidecars in filelist
1120 *-----------------------------------------------------------------------------
1123 static GList *filelist_filter_out_sidecars(GList *flist)
1125 GList *work = flist;
1126 GList *flist_filtered = NULL;
1130 FileData *fd = work->data;
1133 if (fd->parent) /* remove fd's that are children */
1134 file_data_unref(fd);
1136 flist_filtered = g_list_prepend(flist_filtered, fd);
1140 return flist_filtered;
1143 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
1145 GList *basename_list = (GList *)value;
1146 file_data_check_sidecars(basename_list);
1150 static gboolean is_hidden_file(const gchar *name)
1152 if (name[0] != '.') return FALSE;
1153 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1158 *-----------------------------------------------------------------------------
1159 * the main filelist function
1160 *-----------------------------------------------------------------------------
1163 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1168 GList *dlist = NULL;
1169 GList *flist = NULL;
1170 gint (*stat_func)(const gchar *path, struct stat *buf);
1171 GHashTable *basename_hash = NULL;
1173 g_assert(files || dirs);
1175 if (files) *files = NULL;
1176 if (dirs) *dirs = NULL;
1178 pathl = path_from_utf8(dir_path);
1179 if (!pathl) return FALSE;
1181 dp = opendir(pathl);
1188 if (files) basename_hash = file_data_basename_hash_new();
1190 if (follow_symlinks)
1195 while ((dir = readdir(dp)) != NULL)
1197 struct stat ent_sbuf;
1198 const gchar *name = dir->d_name;
1201 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1204 filepath = g_build_filename(pathl, name, NULL);
1205 if (stat_func(filepath, &ent_sbuf) >= 0)
1207 if (S_ISDIR(ent_sbuf.st_mode))
1209 /* we ignore the .thumbnails dir for cleanliness */
1211 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1212 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1213 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1214 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1216 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1221 if (files && filter_name_exists(name))
1223 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1224 flist = g_list_prepend(flist, fd);
1225 if (fd->sidecar_priority && !fd->disable_grouping)
1227 file_data_basename_hash_insert(basename_hash, fd);
1234 if (errno == EOVERFLOW)
1236 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1246 if (dirs) *dirs = dlist;
1250 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1252 *files = filelist_filter_out_sidecars(flist);
1254 if (basename_hash) file_data_basename_hash_free(basename_hash);
1256 // Call a separate function to initialize the exif datestamps for the found files..
1257 if (files) init_exif_time_data(*files);
1262 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1264 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1267 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1269 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1272 FileData *file_data_new_group(const gchar *path_utf8)
1279 if (!stat_utf8(path_utf8, &st))
1285 if (S_ISDIR(st.st_mode))
1286 return file_data_new(path_utf8, &st, TRUE);
1288 dir = remove_level_from_path(path_utf8);
1290 filelist_read_real(dir, &files, NULL, TRUE);
1292 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1293 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1299 filelist_free(files);
1305 void filelist_free(GList *list)
1312 file_data_unref((FileData *)work->data);
1320 GList *filelist_copy(GList *list)
1322 GList *new_list = NULL;
1333 new_list = g_list_prepend(new_list, file_data_ref(fd));
1336 return g_list_reverse(new_list);
1339 GList *filelist_from_path_list(GList *list)
1341 GList *new_list = NULL;
1352 new_list = g_list_prepend(new_list, file_data_new_group(path));
1355 return g_list_reverse(new_list);
1358 GList *filelist_to_path_list(GList *list)
1360 GList *new_list = NULL;
1371 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1374 return g_list_reverse(new_list);
1377 GList *filelist_filter(GList *list, gboolean is_dir_list)
1381 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1386 FileData *fd = (FileData *)(work->data);
1387 const gchar *name = fd->name;
1389 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1390 (!is_dir_list && !filter_name_exists(name)) ||
1391 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1392 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1396 list = g_list_remove_link(list, link);
1397 file_data_unref(fd);
1408 *-----------------------------------------------------------------------------
1409 * filelist recursive
1410 *-----------------------------------------------------------------------------
1413 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1415 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1418 GList *filelist_sort_path(GList *list)
1420 return g_list_sort(list, filelist_sort_path_cb);
1423 static void filelist_recursive_append(GList **list, GList *dirs)
1430 FileData *fd = (FileData *)(work->data);
1434 if (filelist_read(fd, &f, &d))
1436 f = filelist_filter(f, FALSE);
1437 f = filelist_sort_path(f);
1438 *list = g_list_concat(*list, f);
1440 d = filelist_filter(d, TRUE);
1441 d = filelist_sort_path(d);
1442 filelist_recursive_append(list, d);
1450 GList *filelist_recursive(FileData *dir_fd)
1455 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1456 list = filelist_filter(list, FALSE);
1457 list = filelist_sort_path(list);
1459 d = filelist_filter(d, TRUE);
1460 d = filelist_sort_path(d);
1461 filelist_recursive_append(&list, d);
1468 *-----------------------------------------------------------------------------
1469 * file modification support
1470 *-----------------------------------------------------------------------------
1474 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1476 if (!fdci && fd) fdci = fd->change;
1480 g_free(fdci->source);
1485 if (fd) fd->change = NULL;
1488 static gboolean file_data_can_write_directly(FileData *fd)
1490 return filter_name_is_writable(fd->extension);
1493 static gboolean file_data_can_write_sidecar(FileData *fd)
1495 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1498 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1500 gchar *sidecar_path = NULL;
1503 if (!file_data_can_write_sidecar(fd)) return NULL;
1505 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1508 FileData *sfd = work->data;
1510 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
1512 sidecar_path = g_strdup(sfd->path);
1517 if (!existing_only && !sidecar_path)
1519 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1520 sidecar_path = g_strconcat(base, ".xmp", NULL);
1524 return sidecar_path;
1528 * marks and orientation
1531 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1532 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1533 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1534 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1536 gboolean file_data_get_mark(FileData *fd, gint n)
1538 gboolean valid = (fd->valid_marks & (1 << n));
1540 if (file_data_get_mark_func[n] && !valid)
1542 guint old = fd->marks;
1543 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1545 if (!value != !(fd->marks & (1 << n)))
1547 fd->marks = fd->marks ^ (1 << n);
1550 fd->valid_marks |= (1 << n);
1551 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1553 file_data_unref(fd);
1555 else if (!old && fd->marks)
1561 return !!(fd->marks & (1 << n));
1564 guint file_data_get_marks(FileData *fd)
1567 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1571 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1574 if (!value == !file_data_get_mark(fd, n)) return;
1576 if (file_data_set_mark_func[n])
1578 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1583 fd->marks = fd->marks ^ (1 << n);
1585 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1587 file_data_unref(fd);
1589 else if (!old && fd->marks)
1594 file_data_increment_version(fd);
1595 file_data_send_notification(fd, NOTIFY_MARKS);
1598 gboolean file_data_filter_marks(FileData *fd, guint filter)
1601 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1602 return ((fd->marks & filter) == filter);
1605 GList *file_data_filter_marks_list(GList *list, guint filter)
1612 FileData *fd = work->data;
1616 if (!file_data_filter_marks(fd, filter))
1618 list = g_list_remove_link(list, link);
1619 file_data_unref(fd);
1627 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1629 FileData *fd = value;
1630 file_data_increment_version(fd);
1631 file_data_send_notification(fd, NOTIFY_MARKS);
1634 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1636 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1638 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1640 file_data_get_mark_func[n] = get_mark_func;
1641 file_data_set_mark_func[n] = set_mark_func;
1642 file_data_mark_func_data[n] = data;
1643 file_data_destroy_mark_func[n] = notify;
1647 /* this effectively changes all known files */
1648 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1654 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1656 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1657 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1658 if (data) *data = file_data_mark_func_data[n];
1661 gint file_data_get_user_orientation(FileData *fd)
1663 return fd->user_orientation;
1666 void file_data_set_user_orientation(FileData *fd, gint value)
1668 if (fd->user_orientation == value) return;
1670 fd->user_orientation = value;
1671 file_data_increment_version(fd);
1672 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1677 * file_data - operates on the given fd
1678 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1682 /* return list of sidecar file extensions in a string */
1683 gchar *file_data_sc_list_to_string(FileData *fd)
1686 GString *result = g_string_new("");
1688 work = fd->sidecar_files;
1691 FileData *sfd = work->data;
1693 result = g_string_append(result, "+ ");
1694 result = g_string_append(result, sfd->extension);
1696 if (work) result = g_string_append_c(result, ' ');
1699 return g_string_free(result, FALSE);
1705 * add FileDataChangeInfo (see typedefs.h) for the given operation
1706 * uses file_data_add_change_info
1708 * fails if the fd->change already exists - change operations can't run in parallel
1709 * fd->change_info works as a lock
1711 * dest can be NULL - in this case the current name is used for now, it will
1716 FileDataChangeInfo types:
1718 MOVE - path is changed, name may be changed too
1719 RENAME - path remains unchanged, name is changed
1720 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1721 sidecar names are changed too, extensions are not changed
1723 UPDATE - file size, date or grouping has been changed
1726 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1728 FileDataChangeInfo *fdci;
1730 if (fd->change) return FALSE;
1732 fdci = g_new0(FileDataChangeInfo, 1);
1737 fdci->source = g_strdup(src);
1739 fdci->source = g_strdup(fd->path);
1742 fdci->dest = g_strdup(dest);
1749 static void file_data_planned_change_remove(FileData *fd)
1751 if (file_data_planned_change_hash &&
1752 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1754 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1756 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1757 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1758 file_data_unref(fd);
1759 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1761 g_hash_table_destroy(file_data_planned_change_hash);
1762 file_data_planned_change_hash = NULL;
1763 DEBUG_1("planned change: empty");
1770 void file_data_free_ci(FileData *fd)
1772 FileDataChangeInfo *fdci = fd->change;
1776 file_data_planned_change_remove(fd);
1778 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1780 g_free(fdci->source);
1788 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1790 FileDataChangeInfo *fdci = fd->change;
1792 fdci->regroup_when_finished = enable;
1795 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1799 if (fd->parent) fd = fd->parent;
1801 if (fd->change) return FALSE;
1803 work = fd->sidecar_files;
1806 FileData *sfd = work->data;
1808 if (sfd->change) return FALSE;
1812 file_data_add_ci(fd, type, NULL, NULL);
1814 work = fd->sidecar_files;
1817 FileData *sfd = work->data;
1819 file_data_add_ci(sfd, type, NULL, NULL);
1826 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1830 if (fd->parent) fd = fd->parent;
1832 if (!fd->change || fd->change->type != type) return FALSE;
1834 work = fd->sidecar_files;
1837 FileData *sfd = work->data;
1839 if (!sfd->change || sfd->change->type != type) return FALSE;
1847 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1849 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1850 file_data_sc_update_ci_copy(fd, dest_path);
1854 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1856 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1857 file_data_sc_update_ci_move(fd, dest_path);
1861 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1863 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1864 file_data_sc_update_ci_rename(fd, dest_path);
1868 gboolean file_data_sc_add_ci_delete(FileData *fd)
1870 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1873 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1875 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1876 file_data_sc_update_ci_unspecified(fd, dest_path);
1880 gboolean file_data_add_ci_write_metadata(FileData *fd)
1882 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1885 void file_data_sc_free_ci(FileData *fd)
1889 if (fd->parent) fd = fd->parent;
1891 file_data_free_ci(fd);
1893 work = fd->sidecar_files;
1896 FileData *sfd = work->data;
1898 file_data_free_ci(sfd);
1903 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1906 gboolean ret = TRUE;
1911 FileData *fd = work->data;
1913 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1920 static void file_data_sc_revert_ci_list(GList *fd_list)
1927 FileData *fd = work->data;
1929 file_data_sc_free_ci(fd);
1934 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1941 FileData *fd = work->data;
1943 if (!func(fd, dest))
1945 file_data_sc_revert_ci_list(work->prev);
1954 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1956 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1959 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1961 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1964 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1966 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1969 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1971 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1974 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1977 gboolean ret = TRUE;
1982 FileData *fd = work->data;
1984 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1991 void file_data_free_ci_list(GList *fd_list)
1998 FileData *fd = work->data;
2000 file_data_free_ci(fd);
2005 void file_data_sc_free_ci_list(GList *fd_list)
2012 FileData *fd = work->data;
2014 file_data_sc_free_ci(fd);
2020 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2021 * fails if fd->change does not exist or the change type does not match
2024 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2026 FileDataChangeType type = fd->change->type;
2028 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2032 if (!file_data_planned_change_hash)
2033 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2035 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2037 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2038 g_hash_table_remove(file_data_planned_change_hash, old_path);
2039 file_data_unref(fd);
2042 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
2047 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2048 g_hash_table_remove(file_data_planned_change_hash, new_path);
2049 file_data_unref(ofd);
2052 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2054 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2059 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2061 gchar *old_path = fd->change->dest;
2063 fd->change->dest = g_strdup(dest_path);
2064 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2068 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2070 const gchar *extension = extension_from_path(fd->change->source);
2071 gchar *base = remove_extension_from_path(dest_path);
2072 gchar *old_path = fd->change->dest;
2074 fd->change->dest = g_strconcat(base, extension, NULL);
2075 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2081 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2084 gchar *dest_path_full = NULL;
2086 if (fd->parent) fd = fd->parent;
2090 dest_path = fd->path;
2092 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2094 gchar *dir = remove_level_from_path(fd->path);
2096 dest_path_full = g_build_filename(dir, dest_path, NULL);
2098 dest_path = dest_path_full;
2100 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2102 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2103 dest_path = dest_path_full;
2106 file_data_update_ci_dest(fd, dest_path);
2108 work = fd->sidecar_files;
2111 FileData *sfd = work->data;
2113 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2117 g_free(dest_path_full);
2120 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2122 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2123 file_data_sc_update_ci(fd, dest_path);
2127 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2129 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2132 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2134 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2137 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2139 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2142 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2144 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2147 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2149 gboolean (*func)(FileData *, const gchar *))
2152 gboolean ret = TRUE;
2157 FileData *fd = work->data;
2159 if (!func(fd, dest)) ret = FALSE;
2166 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2168 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2171 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2173 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2176 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2178 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2183 * verify source and dest paths - dest image exists, etc.
2184 * it should detect all possible problems with the planned operation
2187 gint file_data_verify_ci(FileData *fd, GList *list)
2189 gint ret = CHANGE_OK;
2192 FileData *fd1 = NULL;
2196 DEBUG_1("Change checked: no change info: %s", fd->path);
2200 if (!isname(fd->path))
2202 /* this probably should not happen */
2203 ret |= CHANGE_NO_SRC;
2204 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2208 dir = remove_level_from_path(fd->path);
2210 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2211 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2212 fd->change->type != FILEDATA_CHANGE_RENAME &&
2213 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2216 ret |= CHANGE_WARN_UNSAVED_META;
2217 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2220 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2221 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2222 !access_file(fd->path, R_OK))
2224 ret |= CHANGE_NO_READ_PERM;
2225 DEBUG_1("Change checked: no read permission: %s", fd->path);
2227 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2228 !access_file(dir, W_OK))
2230 ret |= CHANGE_NO_WRITE_PERM_DIR;
2231 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2233 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2234 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2235 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2236 !access_file(fd->path, W_OK))
2238 ret |= CHANGE_WARN_NO_WRITE_PERM;
2239 DEBUG_1("Change checked: no write permission: %s", fd->path);
2241 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2242 - that means that there are no hard errors and warnings can be disabled
2243 - the destination is determined during the check
2245 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2247 /* determine destination file */
2248 gboolean have_dest = FALSE;
2249 gchar *dest_dir = NULL;
2251 if (options->metadata.save_in_image_file)
2253 if (file_data_can_write_directly(fd))
2255 /* we can write the file directly */
2256 if (access_file(fd->path, W_OK))
2262 if (options->metadata.warn_on_write_problems)
2264 ret |= CHANGE_WARN_NO_WRITE_PERM;
2265 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2269 else if (file_data_can_write_sidecar(fd))
2271 /* we can write sidecar */
2272 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2273 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2275 file_data_update_ci_dest(fd, sidecar);
2280 if (options->metadata.warn_on_write_problems)
2282 ret |= CHANGE_WARN_NO_WRITE_PERM;
2283 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2292 /* write private metadata file under ~/.geeqie */
2294 /* If an existing metadata file exists, we will try writing to
2295 * it's location regardless of the user's preference.
2297 gchar *metadata_path = NULL;
2299 /* but ignore XMP if we are not able to write it */
2300 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2302 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2304 if (metadata_path && !access_file(metadata_path, W_OK))
2306 g_free(metadata_path);
2307 metadata_path = NULL;
2314 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2315 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2317 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2319 metadata_path = g_build_filename(dest_dir, filename, NULL);
2323 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2325 file_data_update_ci_dest(fd, metadata_path);
2330 ret |= CHANGE_NO_WRITE_PERM_DEST;
2331 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2333 g_free(metadata_path);
2338 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2343 same = (strcmp(fd->path, fd->change->dest) == 0);
2347 const gchar *dest_ext = extension_from_path(fd->change->dest);
2348 if (!dest_ext) dest_ext = "";
2349 if (!options->file_filter.disable_file_extension_checks)
2351 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2353 ret |= CHANGE_WARN_CHANGED_EXT;
2354 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2360 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2362 ret |= CHANGE_WARN_SAME;
2363 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2367 dest_dir = remove_level_from_path(fd->change->dest);
2369 if (!isdir(dest_dir))
2371 ret |= CHANGE_NO_DEST_DIR;
2372 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2374 else if (!access_file(dest_dir, W_OK))
2376 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2377 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2381 if (isfile(fd->change->dest))
2383 if (!access_file(fd->change->dest, W_OK))
2385 ret |= CHANGE_NO_WRITE_PERM_DEST;
2386 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2390 ret |= CHANGE_WARN_DEST_EXISTS;
2391 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2394 else if (isdir(fd->change->dest))
2396 ret |= CHANGE_DEST_EXISTS;
2397 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2404 /* During a rename operation, check if another planned destination file has
2407 if(fd->change->type == FILEDATA_CHANGE_RENAME)
2414 if (fd1 != NULL && fd != fd1 )
2416 if (!strcmp(fd->change->dest, fd1->change->dest))
2418 ret |= CHANGE_DUPLICATE_DEST;
2424 fd->change->error = ret;
2425 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2432 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2437 ret = file_data_verify_ci(fd, list);
2439 work = fd->sidecar_files;
2442 FileData *sfd = work->data;
2444 ret |= file_data_verify_ci(sfd, list);
2451 gchar *file_data_get_error_string(gint error)
2453 GString *result = g_string_new("");
2455 if (error & CHANGE_NO_SRC)
2457 if (result->len > 0) g_string_append(result, ", ");
2458 g_string_append(result, _("file or directory does not exist"));
2461 if (error & CHANGE_DEST_EXISTS)
2463 if (result->len > 0) g_string_append(result, ", ");
2464 g_string_append(result, _("destination already exists"));
2467 if (error & CHANGE_NO_WRITE_PERM_DEST)
2469 if (result->len > 0) g_string_append(result, ", ");
2470 g_string_append(result, _("destination can't be overwritten"));
2473 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2475 if (result->len > 0) g_string_append(result, ", ");
2476 g_string_append(result, _("destination directory is not writable"));
2479 if (error & CHANGE_NO_DEST_DIR)
2481 if (result->len > 0) g_string_append(result, ", ");
2482 g_string_append(result, _("destination directory does not exist"));
2485 if (error & CHANGE_NO_WRITE_PERM_DIR)
2487 if (result->len > 0) g_string_append(result, ", ");
2488 g_string_append(result, _("source directory is not writable"));
2491 if (error & CHANGE_NO_READ_PERM)
2493 if (result->len > 0) g_string_append(result, ", ");
2494 g_string_append(result, _("no read permission"));
2497 if (error & CHANGE_WARN_NO_WRITE_PERM)
2499 if (result->len > 0) g_string_append(result, ", ");
2500 g_string_append(result, _("file is readonly"));
2503 if (error & CHANGE_WARN_DEST_EXISTS)
2505 if (result->len > 0) g_string_append(result, ", ");
2506 g_string_append(result, _("destination already exists and will be overwritten"));
2509 if (error & CHANGE_WARN_SAME)
2511 if (result->len > 0) g_string_append(result, ", ");
2512 g_string_append(result, _("source and destination are the same"));
2515 if (error & CHANGE_WARN_CHANGED_EXT)
2517 if (result->len > 0) g_string_append(result, ", ");
2518 g_string_append(result, _("source and destination have different extension"));
2521 if (error & CHANGE_WARN_UNSAVED_META)
2523 if (result->len > 0) g_string_append(result, ", ");
2524 g_string_append(result, _("there are unsaved metadata changes for the file"));
2527 if (error & CHANGE_DUPLICATE_DEST)
2529 if (result->len > 0) g_string_append(result, ", ");
2530 g_string_append(result, _("another destination file has the same filename"));
2533 return g_string_free(result, FALSE);
2536 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2539 gint all_errors = 0;
2540 gint common_errors = ~0;
2545 if (!list) return 0;
2547 num = g_list_length(list);
2548 errors = g_new(int, num);
2559 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2560 all_errors |= error;
2561 common_errors &= error;
2568 if (desc && all_errors)
2571 GString *result = g_string_new("");
2575 gchar *str = file_data_get_error_string(common_errors);
2576 g_string_append(result, str);
2577 g_string_append(result, "\n");
2591 error = errors[i] & ~common_errors;
2595 gchar *str = file_data_get_error_string(error);
2596 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2601 *desc = g_string_free(result, FALSE);
2610 * perform the change described by FileFataChangeInfo
2611 * it is used for internal operations,
2612 * this function actually operates with files on the filesystem
2613 * it should implement safe delete
2616 static gboolean file_data_perform_move(FileData *fd)
2618 g_assert(!strcmp(fd->change->source, fd->path));
2619 return move_file(fd->change->source, fd->change->dest);
2622 static gboolean file_data_perform_copy(FileData *fd)
2624 g_assert(!strcmp(fd->change->source, fd->path));
2625 return copy_file(fd->change->source, fd->change->dest);
2628 static gboolean file_data_perform_delete(FileData *fd)
2630 if (isdir(fd->path) && !islink(fd->path))
2631 return rmdir_utf8(fd->path);
2633 if (options->file_ops.safe_delete_enable)
2634 return file_util_safe_unlink(fd->path);
2636 return unlink_file(fd->path);
2639 gboolean file_data_perform_ci(FileData *fd)
2641 FileDataChangeType type = fd->change->type;
2645 case FILEDATA_CHANGE_MOVE:
2646 return file_data_perform_move(fd);
2647 case FILEDATA_CHANGE_COPY:
2648 return file_data_perform_copy(fd);
2649 case FILEDATA_CHANGE_RENAME:
2650 return file_data_perform_move(fd); /* the same as move */
2651 case FILEDATA_CHANGE_DELETE:
2652 return file_data_perform_delete(fd);
2653 case FILEDATA_CHANGE_WRITE_METADATA:
2654 return metadata_write_perform(fd);
2655 case FILEDATA_CHANGE_UNSPECIFIED:
2656 /* nothing to do here */
2664 gboolean file_data_sc_perform_ci(FileData *fd)
2667 gboolean ret = TRUE;
2668 FileDataChangeType type = fd->change->type;
2670 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2672 work = fd->sidecar_files;
2675 FileData *sfd = work->data;
2677 if (!file_data_perform_ci(sfd)) ret = FALSE;
2681 if (!file_data_perform_ci(fd)) ret = FALSE;
2687 * updates FileData structure according to FileDataChangeInfo
2690 gboolean file_data_apply_ci(FileData *fd)
2692 FileDataChangeType type = fd->change->type;
2695 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2697 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2698 file_data_planned_change_remove(fd);
2700 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2702 /* this change overwrites another file which is already known to other modules
2703 renaming fd would create duplicate FileData structure
2704 the best thing we can do is nothing
2705 FIXME: maybe we could copy stuff like marks
2707 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2711 file_data_set_path(fd, fd->change->dest);
2714 file_data_increment_version(fd);
2715 file_data_send_notification(fd, NOTIFY_CHANGE);
2720 gboolean file_data_sc_apply_ci(FileData *fd)
2723 FileDataChangeType type = fd->change->type;
2725 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2727 work = fd->sidecar_files;
2730 FileData *sfd = work->data;
2732 file_data_apply_ci(sfd);
2736 file_data_apply_ci(fd);
2741 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2744 if (fd->parent) fd = fd->parent;
2745 if (!g_list_find(list, fd)) return FALSE;
2747 work = fd->sidecar_files;
2750 if (!g_list_find(list, work->data)) return FALSE;
2756 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2761 /* change partial groups to independent files */
2766 FileData *fd = work->data;
2769 if (!file_data_list_contains_whole_group(list, fd))
2771 file_data_disable_grouping(fd, TRUE);
2774 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2780 /* remove sidecars from the list,
2781 they can be still acessed via main_fd->sidecar_files */
2785 FileData *fd = work->data;
2789 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2791 out = g_list_prepend(out, file_data_ref(fd));
2795 filelist_free(list);
2796 out = g_list_reverse(out);
2806 * notify other modules about the change described by FileDataChangeInfo
2809 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2810 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2811 implementation in view_file_list.c */
2814 typedef struct _NotifyIdleData NotifyIdleData;
2816 struct _NotifyIdleData {
2822 typedef struct _NotifyData NotifyData;
2824 struct _NotifyData {
2825 FileDataNotifyFunc func;
2827 NotifyPriority priority;
2830 static GList *notify_func_list = NULL;
2832 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2834 NotifyData *nda = (NotifyData *)a;
2835 NotifyData *ndb = (NotifyData *)b;
2837 if (nda->priority < ndb->priority) return -1;
2838 if (nda->priority > ndb->priority) return 1;
2842 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2845 GList *work = notify_func_list;
2849 NotifyData *nd = (NotifyData *)work->data;
2851 if (nd->func == func && nd->data == data)
2853 g_warning("Notify func already registered");
2859 nd = g_new(NotifyData, 1);
2862 nd->priority = priority;
2864 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2865 DEBUG_2("Notify func registered: %p", nd);
2870 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2872 GList *work = notify_func_list;
2876 NotifyData *nd = (NotifyData *)work->data;
2878 if (nd->func == func && nd->data == data)
2880 notify_func_list = g_list_delete_link(notify_func_list, work);
2882 DEBUG_2("Notify func unregistered: %p", nd);
2888 g_warning("Notify func not found");
2893 gboolean file_data_send_notification_idle_cb(gpointer data)
2895 NotifyIdleData *nid = (NotifyIdleData *)data;
2896 GList *work = notify_func_list;
2900 NotifyData *nd = (NotifyData *)work->data;
2902 nd->func(nid->fd, nid->type, nd->data);
2905 file_data_unref(nid->fd);
2910 void file_data_send_notification(FileData *fd, NotifyType type)
2912 GList *work = notify_func_list;
2916 NotifyData *nd = (NotifyData *)work->data;
2918 nd->func(fd, type, nd->data);
2922 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
2923 nid->fd = file_data_ref(fd);
2925 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
2929 static GHashTable *file_data_monitor_pool = NULL;
2930 static guint realtime_monitor_id = 0; /* event source id */
2932 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2936 file_data_check_changed_files(fd);
2938 DEBUG_1("monitor %s", fd->path);
2941 static gboolean realtime_monitor_cb(gpointer data)
2943 if (!options->update_on_time_change) return TRUE;
2944 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2948 gboolean file_data_register_real_time_monitor(FileData *fd)
2954 if (!file_data_monitor_pool)
2955 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2957 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2959 DEBUG_1("Register realtime %d %s", count, fd->path);
2962 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2964 if (!realtime_monitor_id)
2966 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2972 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2976 g_assert(file_data_monitor_pool);
2978 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2980 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2982 g_assert(count > 0);
2987 g_hash_table_remove(file_data_monitor_pool, fd);
2989 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2991 file_data_unref(fd);
2993 if (g_hash_table_size(file_data_monitor_pool) == 0)
2995 g_source_remove(realtime_monitor_id);
2996 realtime_monitor_id = 0;
3002 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */