2 * Copyright (C) 2006 John Ellis
3 * Copyright (C) 2008 - 2016 The Geeqie Team
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 #include "filefilter.h"
27 #include "thumb_standard.h"
28 #include "ui_fileops.h"
31 #include "histogram.h"
32 #include "secure_save.h"
39 gint global_file_data_count = 0;
42 static GHashTable *file_data_pool = NULL;
43 static GHashTable *file_data_planned_change_hash = NULL;
45 static gint sidecar_file_priority(const gchar *extension);
46 static void file_data_check_sidecars(const GList *basename_list);
47 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
50 static SortType filelist_sort_method = SORT_NONE;
51 static gboolean filelist_sort_ascend = TRUE;
54 *-----------------------------------------------------------------------------
55 * text conversion utils
56 *-----------------------------------------------------------------------------
59 gchar *text_from_size(gint64 size)
65 /* what I would like to use is printf("%'d", size)
66 * BUT: not supported on every libc :(
70 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
71 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
75 a = g_strdup_printf("%d", (guint)size);
81 b = g_new(gchar, l + n + 1);
106 gchar *text_from_size_abrev(gint64 size)
108 if (size < (gint64)1024)
110 return g_strdup_printf(_("%d bytes"), (gint)size);
112 if (size < (gint64)1048576)
114 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
116 if (size < (gint64)1073741824)
118 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
121 /* to avoid overflowing the gdouble, do division in two steps */
123 return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
126 /* note: returned string is valid until next call to text_from_time() */
127 const gchar *text_from_time(time_t t)
129 static gchar *ret = NULL;
133 GError *error = NULL;
135 btime = localtime(&t);
137 /* the %x warning about 2 digit years is not an error */
138 buflen = strftime(buf, sizeof(buf), "%x %X", btime);
139 if (buflen < 1) return "";
142 ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
145 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
154 *-----------------------------------------------------------------------------
155 * changed files detection and notification
156 *-----------------------------------------------------------------------------
159 void file_data_increment_version(FileData *fd)
165 fd->parent->version++;
166 fd->parent->valid_marks = 0;
170 static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st)
172 if (fd->size != st->st_size ||
173 fd->date != st->st_mtime)
175 fd->size = st->st_size;
176 fd->date = st->st_mtime;
177 fd->cdate = st->st_ctime;
178 fd->mode = st->st_mode;
179 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
180 fd->thumb_pixbuf = NULL;
181 file_data_increment_version(fd);
182 file_data_send_notification(fd, NOTIFY_REREAD);
188 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
190 gboolean ret = FALSE;
193 ret = file_data_check_changed_single_file(fd, st);
195 work = fd->sidecar_files;
198 FileData *sfd = work->data;
202 if (!stat_utf8(sfd->path, &st))
207 file_data_disconnect_sidecar_file(fd, sfd);
209 file_data_increment_version(sfd);
210 file_data_send_notification(sfd, NOTIFY_REREAD);
211 file_data_unref(sfd);
215 ret |= file_data_check_changed_files_recursive(sfd, &st);
221 gboolean file_data_check_changed_files(FileData *fd)
223 gboolean ret = FALSE;
226 if (fd->parent) fd = fd->parent;
228 if (!stat_utf8(fd->path, &st))
232 FileData *sfd = NULL;
234 /* parent is missing, we have to rebuild whole group */
239 /* file_data_disconnect_sidecar_file might delete the file,
240 we have to keep the reference to prevent this */
241 sidecars = filelist_copy(fd->sidecar_files);
249 file_data_disconnect_sidecar_file(fd, sfd);
251 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
252 /* now we can release the sidecars */
253 filelist_free(sidecars);
254 file_data_increment_version(fd);
255 file_data_send_notification(fd, NOTIFY_REREAD);
260 ret |= file_data_check_changed_files_recursive(fd, &st);
267 *-----------------------------------------------------------------------------
268 * file name, extension, sorting, ...
269 *-----------------------------------------------------------------------------
272 static void file_data_set_collate_keys(FileData *fd)
274 gchar *caseless_name;
277 valid_name = g_filename_display_name(fd->name);
278 caseless_name = g_utf8_casefold(valid_name, -1);
280 g_free(fd->collate_key_name);
281 g_free(fd->collate_key_name_nocase);
283 #if GTK_CHECK_VERSION(2, 8, 0)
284 if (options->file_sort.natural)
286 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
287 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
291 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
292 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
295 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
296 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
300 g_free(caseless_name);
303 static void file_data_set_path(FileData *fd, const gchar *path)
305 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
306 g_assert(file_data_pool);
310 if (fd->original_path)
312 g_hash_table_remove(file_data_pool, fd->original_path);
313 g_free(fd->original_path);
316 g_assert(!g_hash_table_lookup(file_data_pool, path));
318 fd->original_path = g_strdup(path);
319 g_hash_table_insert(file_data_pool, fd->original_path, fd);
321 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
323 fd->path = g_strdup(path);
325 fd->extension = fd->name + 1;
326 file_data_set_collate_keys(fd);
330 fd->path = g_strdup(path);
331 fd->name = filename_from_path(fd->path);
333 if (strcmp(fd->name, "..") == 0)
335 gchar *dir = remove_level_from_path(path);
337 fd->path = remove_level_from_path(dir);
340 fd->extension = fd->name + 2;
341 file_data_set_collate_keys(fd);
344 else if (strcmp(fd->name, ".") == 0)
347 fd->path = remove_level_from_path(path);
349 fd->extension = fd->name + 1;
350 file_data_set_collate_keys(fd);
354 fd->extension = registered_extension_from_path(fd->path);
355 if (fd->extension == NULL)
357 fd->extension = fd->name + strlen(fd->name);
360 fd->sidecar_priority = sidecar_file_priority(fd->extension);
361 file_data_set_collate_keys(fd);
365 *-----------------------------------------------------------------------------
366 * create or reuse Filedata
367 *-----------------------------------------------------------------------------
370 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
374 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
376 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
379 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
381 fd = g_hash_table_lookup(file_data_pool, path_utf8);
387 if (!fd && file_data_planned_change_hash)
389 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
392 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
393 if (!isfile(fd->path))
396 file_data_apply_ci(fd);
409 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
412 changed = file_data_check_changed_single_file(fd, st);
414 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
419 fd = g_new0(FileData, 1);
420 #ifdef DEBUG_FILEDATA
421 global_file_data_count++;
422 DEBUG_2("file data count++: %d", global_file_data_count);
425 fd->size = st->st_size;
426 fd->date = st->st_mtime;
427 fd->cdate = st->st_ctime;
428 fd->mode = st->st_mode;
430 fd->magick = FD_MAGICK;
433 fd->format_class = filter_file_get_class(path_utf8);
435 if (disable_sidecars) fd->disable_grouping = TRUE;
437 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
442 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
444 gchar *path_utf8 = path_to_utf8(path);
445 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
451 FileData *file_data_new_simple(const gchar *path_utf8)
456 if (!stat_utf8(path_utf8, &st))
462 fd = g_hash_table_lookup(file_data_pool, path_utf8);
463 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
472 void read_exif_time_data(FileData *file)
474 if (file->exifdate > 0)
476 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
480 file->exif = exif_read_fd(file);
484 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
485 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), file, file->path);
490 uint year, month, day, hour, min, sec;
492 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
493 time_str.tm_year = year - 1900;
494 time_str.tm_mon = month - 1;
495 time_str.tm_mday = day;
496 time_str.tm_hour = hour;
497 time_str.tm_min = min;
498 time_str.tm_sec = sec;
499 time_str.tm_isdst = 0;
501 file->exifdate = mktime(&time_str);
507 void read_exif_time_digitized_data(FileData *file)
509 if (file->exifdate_digitized > 0)
511 DEBUG_1("%s set_exif_time_digitized_data: Already exists for %s", get_exec_time(), file->path);
515 file->exif = exif_read_fd(file);
519 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeDigitized");
520 DEBUG_2("%s set_exif_time_digitized_data: reading %p %s", get_exec_time(), file, file->path);
525 uint year, month, day, hour, min, sec;
527 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
528 time_str.tm_year = year - 1900;
529 time_str.tm_mon = month - 1;
530 time_str.tm_mday = day;
531 time_str.tm_hour = hour;
532 time_str.tm_min = min;
533 time_str.tm_sec = sec;
534 time_str.tm_isdst = 0;
536 file->exifdate_digitized = mktime(&time_str);
542 void set_exif_time_data(GList *files)
544 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
548 FileData *file = files->data;
550 read_exif_time_data(file);
555 void set_exif_time_digitized_data(GList *files)
557 DEBUG_1("%s set_exif_time_digitized_data: ...", get_exec_time());
561 FileData *file = files->data;
563 read_exif_time_digitized_data(file);
568 void set_rating_data(GList *files)
571 DEBUG_1("%s set_rating_data: ...", get_exec_time());
575 FileData *file = files->data;
576 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
579 file->rating = atoi(rating_str);
586 FileData *file_data_new_no_grouping(const gchar *path_utf8)
590 if (!stat_utf8(path_utf8, &st))
596 return file_data_new(path_utf8, &st, TRUE);
599 FileData *file_data_new_dir(const gchar *path_utf8)
603 if (!stat_utf8(path_utf8, &st))
609 /* dir or non-existing yet */
610 g_assert(S_ISDIR(st.st_mode));
612 return file_data_new(path_utf8, &st, TRUE);
616 *-----------------------------------------------------------------------------
618 *-----------------------------------------------------------------------------
621 #ifdef DEBUG_FILEDATA
622 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
624 FileData *file_data_ref(FileData *fd)
627 if (fd == NULL) return NULL;
628 if (fd->magick != FD_MAGICK)
629 #ifdef DEBUG_FILEDATA
630 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, fd);
632 log_printf("Error: fd magick mismatch fd=%p", fd);
634 g_assert(fd->magick == FD_MAGICK);
637 #ifdef DEBUG_FILEDATA
638 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", fd, fd->ref, fd->path, file, line);
640 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
645 static void file_data_free(FileData *fd)
647 g_assert(fd->magick == FD_MAGICK);
648 g_assert(fd->ref == 0);
649 g_assert(!fd->locked);
651 #ifdef DEBUG_FILEDATA
652 global_file_data_count--;
653 DEBUG_2("file data count--: %d", global_file_data_count);
656 metadata_cache_free(fd);
657 g_hash_table_remove(file_data_pool, fd->original_path);
660 g_free(fd->original_path);
661 g_free(fd->collate_key_name);
662 g_free(fd->collate_key_name_nocase);
663 g_free(fd->extended_extension);
664 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
665 histmap_free(fd->histmap);
667 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
669 file_data_change_info_free(NULL, fd);
674 * \brief Checks if the FileData is referenced
676 * Checks the refcount and whether the FileData is locked.
678 static gboolean file_data_check_has_ref(FileData *fd)
680 return fd->ref > 0 || fd->locked;
684 * \brief Consider freeing a FileData.
686 * This function will free a FileData and its children provided that neither its parent nor it has
687 * a positive refcount, and provided that neither is locked.
689 static void file_data_consider_free(FileData *fd)
692 FileData *parent = fd->parent ? fd->parent : fd;
694 g_assert(fd->magick == FD_MAGICK);
695 if (file_data_check_has_ref(fd)) return;
696 if (file_data_check_has_ref(parent)) return;
698 work = parent->sidecar_files;
701 FileData *sfd = work->data;
702 if (file_data_check_has_ref(sfd)) return;
706 /* Neither the parent nor the siblings are referenced, so we can free everything */
707 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
708 fd->path, fd->parent ? parent->path : "-");
710 work = parent->sidecar_files;
713 FileData *sfd = work->data;
718 g_list_free(parent->sidecar_files);
719 parent->sidecar_files = NULL;
721 file_data_free(parent);
724 #ifdef DEBUG_FILEDATA
725 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
727 void file_data_unref(FileData *fd)
730 if (fd == NULL) return;
731 if (fd->magick != FD_MAGICK)
732 #ifdef DEBUG_FILEDATA
733 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, fd);
735 log_printf("Error: fd magick mismatch fd=%p", fd);
737 g_assert(fd->magick == FD_MAGICK);
740 #ifdef DEBUG_FILEDATA
741 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", fd, fd->ref, fd->locked, fd->path,
744 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
747 // Free FileData if it's no longer ref'd
748 file_data_consider_free(fd);
752 * \brief Lock the FileData in memory.
754 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
755 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
756 * even if the code would continue to function properly even if the FileData were freed. Code that
757 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
759 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
760 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
762 void file_data_lock(FileData *fd)
764 if (fd == NULL) return;
765 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", fd);
767 g_assert(fd->magick == FD_MAGICK);
770 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
774 * \brief Reset the maintain-FileData-in-memory lock
776 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
777 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
778 * keeping it from being freed.
780 void file_data_unlock(FileData *fd)
782 if (fd == NULL) return;
783 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", fd);
785 g_assert(fd->magick == FD_MAGICK);
788 // Free FileData if it's no longer ref'd
789 file_data_consider_free(fd);
793 * \brief Lock all of the FileDatas in the provided list
795 * \see file_data_lock(FileData)
797 void file_data_lock_list(GList *list)
804 FileData *fd = work->data;
811 * \brief Unlock all of the FileDatas in the provided list
813 * \see file_data_unlock(FileData)
815 void file_data_unlock_list(GList *list)
822 FileData *fd = work->data;
824 file_data_unlock(fd);
829 *-----------------------------------------------------------------------------
830 * sidecar file info struct
831 *-----------------------------------------------------------------------------
834 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
836 const FileData *fda = a;
837 const FileData *fdb = b;
839 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
840 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
842 return strcmp(fdb->extension, fda->extension);
846 static gint sidecar_file_priority(const gchar *extension)
851 if (extension == NULL)
854 work = sidecar_ext_get_list();
857 gchar *ext = work->data;
860 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
866 static void file_data_check_sidecars(const GList *basename_list)
868 /* basename_list contains the new group - first is the parent, then sorted sidecars */
869 /* all files in the list have ref count > 0 */
872 GList *s_work, *new_sidecars;
875 if (!basename_list) return;
878 DEBUG_2("basename start");
879 work = basename_list;
882 FileData *fd = work->data;
884 g_assert(fd->magick == FD_MAGICK);
885 DEBUG_2("basename: %p %s", fd, fd->name);
888 g_assert(fd->parent->magick == FD_MAGICK);
889 DEBUG_2(" parent: %p", fd->parent);
891 s_work = fd->sidecar_files;
894 FileData *sfd = s_work->data;
895 s_work = s_work->next;
896 g_assert(sfd->magick == FD_MAGICK);
897 DEBUG_2(" sidecar: %p %s", sfd, sfd->name);
900 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
903 parent_fd = basename_list->data;
905 /* check if the second and next entries of basename_list are already connected
906 as sidecars of the first entry (parent_fd) */
907 work = basename_list->next;
908 s_work = parent_fd->sidecar_files;
910 while (work && s_work)
912 if (work->data != s_work->data) break;
914 s_work = s_work->next;
917 if (!work && !s_work)
919 DEBUG_2("basename no change");
920 return; /* no change in grouping */
923 /* we have to regroup it */
925 /* first, disconnect everything and send notification*/
927 work = basename_list;
930 FileData *fd = work->data;
932 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
936 FileData *old_parent = fd->parent;
937 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
938 file_data_ref(old_parent);
939 file_data_disconnect_sidecar_file(old_parent, fd);
940 file_data_send_notification(old_parent, NOTIFY_REREAD);
941 file_data_unref(old_parent);
944 while (fd->sidecar_files)
946 FileData *sfd = fd->sidecar_files->data;
947 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
949 file_data_disconnect_sidecar_file(fd, sfd);
950 file_data_send_notification(sfd, NOTIFY_REREAD);
951 file_data_unref(sfd);
953 file_data_send_notification(fd, NOTIFY_GROUPING);
955 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
958 /* now we can form the new group */
959 work = basename_list->next;
963 FileData *sfd = work->data;
964 g_assert(sfd->magick == FD_MAGICK);
965 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
966 sfd->parent = parent_fd;
967 new_sidecars = g_list_prepend(new_sidecars, sfd);
970 g_assert(parent_fd->sidecar_files == NULL);
971 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
972 DEBUG_1("basename group changed for %s", parent_fd->path);
976 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
978 g_assert(target->magick == FD_MAGICK);
979 g_assert(sfd->magick == FD_MAGICK);
980 g_assert(g_list_find(target->sidecar_files, sfd));
982 file_data_ref(target);
985 g_assert(sfd->parent == target);
987 file_data_increment_version(sfd); /* increments both sfd and target */
989 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
991 g_free(sfd->extended_extension);
992 sfd->extended_extension = NULL;
994 file_data_unref(target);
995 file_data_unref(sfd);
998 /* disables / enables grouping for particular file, sends UPDATE notification */
999 void file_data_disable_grouping(FileData *fd, gboolean disable)
1001 if (!fd->disable_grouping == !disable) return;
1003 fd->disable_grouping = !!disable;
1009 FileData *parent = file_data_ref(fd->parent);
1010 file_data_disconnect_sidecar_file(parent, fd);
1011 file_data_send_notification(parent, NOTIFY_GROUPING);
1012 file_data_unref(parent);
1014 else if (fd->sidecar_files)
1016 GList *sidecar_files = filelist_copy(fd->sidecar_files);
1017 GList *work = sidecar_files;
1020 FileData *sfd = work->data;
1022 file_data_disconnect_sidecar_file(fd, sfd);
1023 file_data_send_notification(sfd, NOTIFY_GROUPING);
1025 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
1026 filelist_free(sidecar_files);
1030 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
1035 file_data_increment_version(fd);
1036 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
1038 file_data_send_notification(fd, NOTIFY_GROUPING);
1041 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
1048 FileData *fd = work->data;
1050 file_data_disable_grouping(fd, disable);
1058 *-----------------------------------------------------------------------------
1060 *-----------------------------------------------------------------------------
1064 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
1067 if (!filelist_sort_ascend)
1074 switch (filelist_sort_method)
1079 if (fa->size < fb->size) return -1;
1080 if (fa->size > fb->size) return 1;
1081 /* fall back to name */
1084 if (fa->date < fb->date) return -1;
1085 if (fa->date > fb->date) return 1;
1086 /* fall back to name */
1089 if (fa->cdate < fb->cdate) return -1;
1090 if (fa->cdate > fb->cdate) return 1;
1091 /* fall back to name */
1094 if (fa->exifdate < fb->exifdate) return -1;
1095 if (fa->exifdate > fb->exifdate) return 1;
1096 /* fall back to name */
1098 case SORT_EXIFTIMEDIGITIZED:
1099 if (fa->exifdate_digitized < fb->exifdate_digitized) return -1;
1100 if (fa->exifdate_digitized > fb->exifdate_digitized) return 1;
1101 /* fall back to name */
1104 if (fa->rating < fb->rating) return -1;
1105 if (fa->rating > fb->rating) return 1;
1106 /* fall back to name */
1109 if (fa->format_class < fb->format_class) return -1;
1110 if (fa->format_class > fb->format_class) return 1;
1111 /* fall back to name */
1113 #ifdef HAVE_STRVERSCMP
1115 ret = strverscmp(fa->name, fb->name);
1116 if (ret != 0) return ret;
1123 if (options->file_sort.case_sensitive)
1124 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1126 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1128 if (ret != 0) return ret;
1130 /* do not return 0 unless the files are really the same
1131 file_data_pool ensures that original_path is unique
1133 return strcmp(fa->original_path, fb->original_path);
1136 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1138 filelist_sort_method = method;
1139 filelist_sort_ascend = ascend;
1140 return filelist_sort_compare_filedata(fa, fb);
1143 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1145 return filelist_sort_compare_filedata(a, b);
1148 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1150 filelist_sort_method = method;
1151 filelist_sort_ascend = ascend;
1152 return g_list_sort(list, cb);
1155 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1157 filelist_sort_method = method;
1158 filelist_sort_ascend = ascend;
1159 return g_list_insert_sorted(list, data, cb);
1162 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1164 if (method == SORT_EXIFTIME)
1166 set_exif_time_data(list);
1168 if (method == SORT_EXIFTIMEDIGITIZED)
1170 set_exif_time_digitized_data(list);
1172 if (method == SORT_RATING)
1174 set_rating_data(list);
1176 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1179 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
1181 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1185 *-----------------------------------------------------------------------------
1186 * basename hash - grouping of sidecars in filelist
1187 *-----------------------------------------------------------------------------
1191 static GHashTable *file_data_basename_hash_new(void)
1193 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1196 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1199 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1201 list = g_hash_table_lookup(basename_hash, basename);
1205 DEBUG_1("TG: basename_hash not found for %s",fd->path);
1206 const gchar *parent_extension = registered_extension_from_path(basename);
1208 if (parent_extension)
1210 DEBUG_1("TG: parent extension %s",parent_extension);
1211 gchar *parent_basename = g_strndup(basename, parent_extension - basename);
1212 DEBUG_1("TG: parent basename %s",parent_basename);
1213 FileData *parent_fd = g_hash_table_lookup(file_data_pool, basename);
1216 DEBUG_1("TG: parent fd found");
1217 list = g_hash_table_lookup(basename_hash, parent_basename);
1218 if (!g_list_find(list, parent_fd))
1220 DEBUG_1("TG: parent fd doesn't fit");
1221 g_free(parent_basename);
1227 basename = parent_basename;
1228 fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
1234 if (!g_list_find(list, fd))
1236 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1237 g_hash_table_insert(basename_hash, basename, list);
1246 static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
1248 file_data_basename_hash_insert((GHashTable *)basename_hash, (FileData *)fd);
1251 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
1253 filelist_free((GList *)value);
1256 static void file_data_basename_hash_free(GHashTable *basename_hash)
1258 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1259 g_hash_table_destroy(basename_hash);
1263 *-----------------------------------------------------------------------------
1264 * handling sidecars in filelist
1265 *-----------------------------------------------------------------------------
1268 static GList *filelist_filter_out_sidecars(GList *flist)
1270 GList *work = flist;
1271 GList *flist_filtered = NULL;
1275 FileData *fd = work->data;
1278 if (fd->parent) /* remove fd's that are children */
1279 file_data_unref(fd);
1281 flist_filtered = g_list_prepend(flist_filtered, fd);
1285 return flist_filtered;
1288 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
1290 GList *basename_list = (GList *)value;
1291 file_data_check_sidecars(basename_list);
1295 static gboolean is_hidden_file(const gchar *name)
1297 if (name[0] != '.') return FALSE;
1298 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1303 *-----------------------------------------------------------------------------
1304 * the main filelist function
1305 *-----------------------------------------------------------------------------
1308 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1313 GList *dlist = NULL;
1314 GList *flist = NULL;
1315 GList *xmp_files = NULL;
1316 gint (*stat_func)(const gchar *path, struct stat *buf);
1317 GHashTable *basename_hash = NULL;
1319 g_assert(files || dirs);
1321 if (files) *files = NULL;
1322 if (dirs) *dirs = NULL;
1324 pathl = path_from_utf8(dir_path);
1325 if (!pathl) return FALSE;
1327 dp = opendir(pathl);
1334 if (files) basename_hash = file_data_basename_hash_new();
1336 if (follow_symlinks)
1341 while ((dir = readdir(dp)) != NULL)
1343 struct stat ent_sbuf;
1344 const gchar *name = dir->d_name;
1347 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1350 filepath = g_build_filename(pathl, name, NULL);
1351 if (stat_func(filepath, &ent_sbuf) >= 0)
1353 if (S_ISDIR(ent_sbuf.st_mode))
1355 /* we ignore the .thumbnails dir for cleanliness */
1357 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1358 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1359 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1360 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1362 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1367 if (files && filter_name_exists(name))
1369 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1370 flist = g_list_prepend(flist, fd);
1371 if (fd->sidecar_priority && !fd->disable_grouping)
1373 if (strcmp(fd->extension, ".xmp") != 0)
1374 file_data_basename_hash_insert(basename_hash, fd);
1376 xmp_files = g_list_append(xmp_files, fd);
1383 if (errno == EOVERFLOW)
1385 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1397 g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
1398 g_list_free(xmp_files);
1401 if (dirs) *dirs = dlist;
1405 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1407 *files = filelist_filter_out_sidecars(flist);
1409 if (basename_hash) file_data_basename_hash_free(basename_hash);
1414 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1416 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1419 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1421 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1424 FileData *file_data_new_group(const gchar *path_utf8)
1431 if (!stat_utf8(path_utf8, &st))
1437 if (S_ISDIR(st.st_mode))
1438 return file_data_new(path_utf8, &st, TRUE);
1440 dir = remove_level_from_path(path_utf8);
1442 filelist_read_real(dir, &files, NULL, TRUE);
1444 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1445 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1451 filelist_free(files);
1457 void filelist_free(GList *list)
1464 file_data_unref((FileData *)work->data);
1472 GList *filelist_copy(GList *list)
1474 GList *new_list = NULL;
1485 new_list = g_list_prepend(new_list, file_data_ref(fd));
1488 return g_list_reverse(new_list);
1491 GList *filelist_from_path_list(GList *list)
1493 GList *new_list = NULL;
1504 new_list = g_list_prepend(new_list, file_data_new_group(path));
1507 return g_list_reverse(new_list);
1510 GList *filelist_to_path_list(GList *list)
1512 GList *new_list = NULL;
1523 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1526 return g_list_reverse(new_list);
1529 GList *filelist_filter(GList *list, gboolean is_dir_list)
1533 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1538 FileData *fd = (FileData *)(work->data);
1539 const gchar *name = fd->name;
1541 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1542 (!is_dir_list && !filter_name_exists(name)) ||
1543 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1544 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1548 list = g_list_remove_link(list, link);
1549 file_data_unref(fd);
1560 *-----------------------------------------------------------------------------
1561 * filelist recursive
1562 *-----------------------------------------------------------------------------
1565 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1567 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1570 GList *filelist_sort_path(GList *list)
1572 return g_list_sort(list, filelist_sort_path_cb);
1575 static void filelist_recursive_append(GList **list, GList *dirs)
1582 FileData *fd = (FileData *)(work->data);
1586 if (filelist_read(fd, &f, &d))
1588 f = filelist_filter(f, FALSE);
1589 f = filelist_sort_path(f);
1590 *list = g_list_concat(*list, f);
1592 d = filelist_filter(d, TRUE);
1593 d = filelist_sort_path(d);
1594 filelist_recursive_append(list, d);
1602 GList *filelist_recursive(FileData *dir_fd)
1607 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1608 list = filelist_filter(list, FALSE);
1609 list = filelist_sort_path(list);
1611 d = filelist_filter(d, TRUE);
1612 d = filelist_sort_path(d);
1613 filelist_recursive_append(&list, d);
1620 *-----------------------------------------------------------------------------
1621 * file modification support
1622 *-----------------------------------------------------------------------------
1626 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1628 if (!fdci && fd) fdci = fd->change;
1632 g_free(fdci->source);
1637 if (fd) fd->change = NULL;
1640 static gboolean file_data_can_write_directly(FileData *fd)
1642 return filter_name_is_writable(fd->extension);
1645 static gboolean file_data_can_write_sidecar(FileData *fd)
1647 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1650 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1652 gchar *sidecar_path = NULL;
1655 if (!file_data_can_write_sidecar(fd)) return NULL;
1657 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1658 gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1661 FileData *sfd = work->data;
1663 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1665 sidecar_path = g_strdup(sfd->path);
1669 g_free(extended_extension);
1671 if (!existing_only && !sidecar_path)
1673 if (options->metadata.sidecar_extended_name)
1674 sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1677 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1678 sidecar_path = g_strconcat(base, ".xmp", NULL);
1683 return sidecar_path;
1687 * marks and orientation
1690 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1691 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1692 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1693 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1695 gboolean file_data_get_mark(FileData *fd, gint n)
1697 gboolean valid = (fd->valid_marks & (1 << n));
1699 if (file_data_get_mark_func[n] && !valid)
1701 guint old = fd->marks;
1702 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1704 if (!value != !(fd->marks & (1 << n)))
1706 fd->marks = fd->marks ^ (1 << n);
1709 fd->valid_marks |= (1 << n);
1710 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1712 file_data_unref(fd);
1714 else if (!old && fd->marks)
1720 return !!(fd->marks & (1 << n));
1723 guint file_data_get_marks(FileData *fd)
1726 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1730 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1733 if (!value == !file_data_get_mark(fd, n)) return;
1735 if (file_data_set_mark_func[n])
1737 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1742 fd->marks = fd->marks ^ (1 << n);
1744 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1746 file_data_unref(fd);
1748 else if (!old && fd->marks)
1753 file_data_increment_version(fd);
1754 file_data_send_notification(fd, NOTIFY_MARKS);
1757 gboolean file_data_filter_marks(FileData *fd, guint filter)
1760 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1761 return ((fd->marks & filter) == filter);
1764 GList *file_data_filter_marks_list(GList *list, guint filter)
1771 FileData *fd = work->data;
1775 if (!file_data_filter_marks(fd, filter))
1777 list = g_list_remove_link(list, link);
1778 file_data_unref(fd);
1786 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1788 FileData *fd = value;
1789 file_data_increment_version(fd);
1790 file_data_send_notification(fd, NOTIFY_MARKS);
1793 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1795 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1797 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1799 file_data_get_mark_func[n] = get_mark_func;
1800 file_data_set_mark_func[n] = set_mark_func;
1801 file_data_mark_func_data[n] = data;
1802 file_data_destroy_mark_func[n] = notify;
1804 if (get_mark_func && file_data_pool)
1806 /* this effectively changes all known files */
1807 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1813 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1815 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1816 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1817 if (data) *data = file_data_mark_func_data[n];
1820 gint file_data_get_user_orientation(FileData *fd)
1822 return fd->user_orientation;
1825 void file_data_set_user_orientation(FileData *fd, gint value)
1827 if (fd->user_orientation == value) return;
1829 fd->user_orientation = value;
1830 file_data_increment_version(fd);
1831 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1836 * file_data - operates on the given fd
1837 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1841 /* return list of sidecar file extensions in a string */
1842 gchar *file_data_sc_list_to_string(FileData *fd)
1845 GString *result = g_string_new("");
1847 work = fd->sidecar_files;
1850 FileData *sfd = work->data;
1852 result = g_string_append(result, "+ ");
1853 result = g_string_append(result, sfd->extension);
1855 if (work) result = g_string_append_c(result, ' ');
1858 return g_string_free(result, FALSE);
1864 * add FileDataChangeInfo (see typedefs.h) for the given operation
1865 * uses file_data_add_change_info
1867 * fails if the fd->change already exists - change operations can't run in parallel
1868 * fd->change_info works as a lock
1870 * dest can be NULL - in this case the current name is used for now, it will
1875 FileDataChangeInfo types:
1877 MOVE - path is changed, name may be changed too
1878 RENAME - path remains unchanged, name is changed
1879 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1880 sidecar names are changed too, extensions are not changed
1882 UPDATE - file size, date or grouping has been changed
1885 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1887 FileDataChangeInfo *fdci;
1889 if (fd->change) return FALSE;
1891 fdci = g_new0(FileDataChangeInfo, 1);
1896 fdci->source = g_strdup(src);
1898 fdci->source = g_strdup(fd->path);
1901 fdci->dest = g_strdup(dest);
1908 static void file_data_planned_change_remove(FileData *fd)
1910 if (file_data_planned_change_hash &&
1911 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1913 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1915 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1916 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1917 file_data_unref(fd);
1918 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1920 g_hash_table_destroy(file_data_planned_change_hash);
1921 file_data_planned_change_hash = NULL;
1922 DEBUG_1("planned change: empty");
1929 void file_data_free_ci(FileData *fd)
1931 FileDataChangeInfo *fdci = fd->change;
1935 file_data_planned_change_remove(fd);
1937 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1939 g_free(fdci->source);
1947 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1949 FileDataChangeInfo *fdci = fd->change;
1951 fdci->regroup_when_finished = enable;
1954 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1958 if (fd->parent) fd = fd->parent;
1960 if (fd->change) return FALSE;
1962 work = fd->sidecar_files;
1965 FileData *sfd = work->data;
1967 if (sfd->change) return FALSE;
1971 file_data_add_ci(fd, type, NULL, NULL);
1973 work = fd->sidecar_files;
1976 FileData *sfd = work->data;
1978 file_data_add_ci(sfd, type, NULL, NULL);
1985 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1989 if (fd->parent) fd = fd->parent;
1991 if (!fd->change || fd->change->type != type) return FALSE;
1993 work = fd->sidecar_files;
1996 FileData *sfd = work->data;
1998 if (!sfd->change || sfd->change->type != type) return FALSE;
2006 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
2008 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
2009 file_data_sc_update_ci_copy(fd, dest_path);
2013 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
2015 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
2016 file_data_sc_update_ci_move(fd, dest_path);
2020 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
2022 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
2023 file_data_sc_update_ci_rename(fd, dest_path);
2027 gboolean file_data_sc_add_ci_delete(FileData *fd)
2029 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
2032 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
2034 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
2035 file_data_sc_update_ci_unspecified(fd, dest_path);
2039 gboolean file_data_add_ci_write_metadata(FileData *fd)
2041 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
2044 void file_data_sc_free_ci(FileData *fd)
2048 if (fd->parent) fd = fd->parent;
2050 file_data_free_ci(fd);
2052 work = fd->sidecar_files;
2055 FileData *sfd = work->data;
2057 file_data_free_ci(sfd);
2062 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2065 gboolean ret = TRUE;
2070 FileData *fd = work->data;
2072 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2079 static void file_data_sc_revert_ci_list(GList *fd_list)
2086 FileData *fd = work->data;
2088 file_data_sc_free_ci(fd);
2093 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2100 FileData *fd = work->data;
2102 if (!func(fd, dest))
2104 file_data_sc_revert_ci_list(work->prev);
2113 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2115 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2118 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2120 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2123 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2125 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2128 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2130 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2133 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2136 gboolean ret = TRUE;
2141 FileData *fd = work->data;
2143 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2150 void file_data_free_ci_list(GList *fd_list)
2157 FileData *fd = work->data;
2159 file_data_free_ci(fd);
2164 void file_data_sc_free_ci_list(GList *fd_list)
2171 FileData *fd = work->data;
2173 file_data_sc_free_ci(fd);
2179 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2180 * fails if fd->change does not exist or the change type does not match
2183 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2185 FileDataChangeType type = fd->change->type;
2187 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2191 if (!file_data_planned_change_hash)
2192 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2194 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2196 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2197 g_hash_table_remove(file_data_planned_change_hash, old_path);
2198 file_data_unref(fd);
2201 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
2206 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2207 g_hash_table_remove(file_data_planned_change_hash, new_path);
2208 file_data_unref(ofd);
2211 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2213 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2218 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2220 gchar *old_path = fd->change->dest;
2222 fd->change->dest = g_strdup(dest_path);
2223 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2227 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2229 const gchar *extension = registered_extension_from_path(fd->change->source);
2230 gchar *base = remove_extension_from_path(dest_path);
2231 gchar *old_path = fd->change->dest;
2233 fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2234 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2240 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2243 gchar *dest_path_full = NULL;
2245 if (fd->parent) fd = fd->parent;
2249 dest_path = fd->path;
2251 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2253 gchar *dir = remove_level_from_path(fd->path);
2255 dest_path_full = g_build_filename(dir, dest_path, NULL);
2257 dest_path = dest_path_full;
2259 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2261 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2262 dest_path = dest_path_full;
2265 file_data_update_ci_dest(fd, dest_path);
2267 work = fd->sidecar_files;
2270 FileData *sfd = work->data;
2272 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2276 g_free(dest_path_full);
2279 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2281 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2282 file_data_sc_update_ci(fd, dest_path);
2286 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2288 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2291 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2293 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2296 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2298 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2301 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2303 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2306 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2308 gboolean (*func)(FileData *, const gchar *))
2311 gboolean ret = TRUE;
2316 FileData *fd = work->data;
2318 if (!func(fd, dest)) ret = FALSE;
2325 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2327 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2330 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2332 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2335 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2337 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2342 * verify source and dest paths - dest image exists, etc.
2343 * it should detect all possible problems with the planned operation
2346 gint file_data_verify_ci(FileData *fd, GList *list)
2348 gint ret = CHANGE_OK;
2351 FileData *fd1 = NULL;
2355 DEBUG_1("Change checked: no change info: %s", fd->path);
2359 if (!isname(fd->path))
2361 /* this probably should not happen */
2362 ret |= CHANGE_NO_SRC;
2363 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2367 dir = remove_level_from_path(fd->path);
2369 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2370 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2371 fd->change->type != FILEDATA_CHANGE_RENAME &&
2372 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2375 ret |= CHANGE_WARN_UNSAVED_META;
2376 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2379 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2380 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2381 !access_file(fd->path, R_OK))
2383 ret |= CHANGE_NO_READ_PERM;
2384 DEBUG_1("Change checked: no read permission: %s", fd->path);
2386 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2387 !access_file(dir, W_OK))
2389 ret |= CHANGE_NO_WRITE_PERM_DIR;
2390 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2392 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2393 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2394 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2395 !access_file(fd->path, W_OK))
2397 ret |= CHANGE_WARN_NO_WRITE_PERM;
2398 DEBUG_1("Change checked: no write permission: %s", fd->path);
2400 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2401 - that means that there are no hard errors and warnings can be disabled
2402 - the destination is determined during the check
2404 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2406 /* determine destination file */
2407 gboolean have_dest = FALSE;
2408 gchar *dest_dir = NULL;
2410 if (options->metadata.save_in_image_file)
2412 if (file_data_can_write_directly(fd))
2414 /* we can write the file directly */
2415 if (access_file(fd->path, W_OK))
2421 if (options->metadata.warn_on_write_problems)
2423 ret |= CHANGE_WARN_NO_WRITE_PERM;
2424 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2428 else if (file_data_can_write_sidecar(fd))
2430 /* we can write sidecar */
2431 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2432 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2434 file_data_update_ci_dest(fd, sidecar);
2439 if (options->metadata.warn_on_write_problems)
2441 ret |= CHANGE_WARN_NO_WRITE_PERM;
2442 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2451 /* write private metadata file under ~/.geeqie */
2453 /* If an existing metadata file exists, we will try writing to
2454 * it's location regardless of the user's preference.
2456 gchar *metadata_path = NULL;
2458 /* but ignore XMP if we are not able to write it */
2459 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2461 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2463 if (metadata_path && !access_file(metadata_path, W_OK))
2465 g_free(metadata_path);
2466 metadata_path = NULL;
2473 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2474 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2476 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2478 metadata_path = g_build_filename(dest_dir, filename, NULL);
2482 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2484 file_data_update_ci_dest(fd, metadata_path);
2489 ret |= CHANGE_NO_WRITE_PERM_DEST;
2490 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2492 g_free(metadata_path);
2497 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2502 same = (strcmp(fd->path, fd->change->dest) == 0);
2506 const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2507 if (!dest_ext) dest_ext = "";
2508 if (!options->file_filter.disable_file_extension_checks)
2510 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2512 ret |= CHANGE_WARN_CHANGED_EXT;
2513 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2519 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2521 ret |= CHANGE_WARN_SAME;
2522 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2526 dest_dir = remove_level_from_path(fd->change->dest);
2528 if (!isdir(dest_dir))
2530 ret |= CHANGE_NO_DEST_DIR;
2531 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2533 else if (!access_file(dest_dir, W_OK))
2535 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2536 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2540 if (isfile(fd->change->dest))
2542 if (!access_file(fd->change->dest, W_OK))
2544 ret |= CHANGE_NO_WRITE_PERM_DEST;
2545 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2549 ret |= CHANGE_WARN_DEST_EXISTS;
2550 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2553 else if (isdir(fd->change->dest))
2555 ret |= CHANGE_DEST_EXISTS;
2556 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2563 /* During a rename operation, check if another planned destination file has
2566 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2567 fd->change->type == FILEDATA_CHANGE_COPY ||
2568 fd->change->type == FILEDATA_CHANGE_MOVE)
2575 if (fd1 != NULL && fd != fd1 )
2577 if (!strcmp(fd->change->dest, fd1->change->dest))
2579 ret |= CHANGE_DUPLICATE_DEST;
2585 fd->change->error = ret;
2586 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2593 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2598 ret = file_data_verify_ci(fd, list);
2600 work = fd->sidecar_files;
2603 FileData *sfd = work->data;
2605 ret |= file_data_verify_ci(sfd, list);
2612 gchar *file_data_get_error_string(gint error)
2614 GString *result = g_string_new("");
2616 if (error & CHANGE_NO_SRC)
2618 if (result->len > 0) g_string_append(result, ", ");
2619 g_string_append(result, _("file or directory does not exist"));
2622 if (error & CHANGE_DEST_EXISTS)
2624 if (result->len > 0) g_string_append(result, ", ");
2625 g_string_append(result, _("destination already exists"));
2628 if (error & CHANGE_NO_WRITE_PERM_DEST)
2630 if (result->len > 0) g_string_append(result, ", ");
2631 g_string_append(result, _("destination can't be overwritten"));
2634 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2636 if (result->len > 0) g_string_append(result, ", ");
2637 g_string_append(result, _("destination directory is not writable"));
2640 if (error & CHANGE_NO_DEST_DIR)
2642 if (result->len > 0) g_string_append(result, ", ");
2643 g_string_append(result, _("destination directory does not exist"));
2646 if (error & CHANGE_NO_WRITE_PERM_DIR)
2648 if (result->len > 0) g_string_append(result, ", ");
2649 g_string_append(result, _("source directory is not writable"));
2652 if (error & CHANGE_NO_READ_PERM)
2654 if (result->len > 0) g_string_append(result, ", ");
2655 g_string_append(result, _("no read permission"));
2658 if (error & CHANGE_WARN_NO_WRITE_PERM)
2660 if (result->len > 0) g_string_append(result, ", ");
2661 g_string_append(result, _("file is readonly"));
2664 if (error & CHANGE_WARN_DEST_EXISTS)
2666 if (result->len > 0) g_string_append(result, ", ");
2667 g_string_append(result, _("destination already exists and will be overwritten"));
2670 if (error & CHANGE_WARN_SAME)
2672 if (result->len > 0) g_string_append(result, ", ");
2673 g_string_append(result, _("source and destination are the same"));
2676 if (error & CHANGE_WARN_CHANGED_EXT)
2678 if (result->len > 0) g_string_append(result, ", ");
2679 g_string_append(result, _("source and destination have different extension"));
2682 if (error & CHANGE_WARN_UNSAVED_META)
2684 if (result->len > 0) g_string_append(result, ", ");
2685 g_string_append(result, _("there are unsaved metadata changes for the file"));
2688 if (error & CHANGE_DUPLICATE_DEST)
2690 if (result->len > 0) g_string_append(result, ", ");
2691 g_string_append(result, _("another destination file has the same filename"));
2694 return g_string_free(result, FALSE);
2697 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2700 gint all_errors = 0;
2701 gint common_errors = ~0;
2706 if (!list) return 0;
2708 num = g_list_length(list);
2709 errors = g_new(int, num);
2720 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2721 all_errors |= error;
2722 common_errors &= error;
2729 if (desc && all_errors)
2732 GString *result = g_string_new("");
2736 gchar *str = file_data_get_error_string(common_errors);
2737 g_string_append(result, str);
2738 g_string_append(result, "\n");
2752 error = errors[i] & ~common_errors;
2756 gchar *str = file_data_get_error_string(error);
2757 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2762 *desc = g_string_free(result, FALSE);
2771 * perform the change described by FileFataChangeInfo
2772 * it is used for internal operations,
2773 * this function actually operates with files on the filesystem
2774 * it should implement safe delete
2777 static gboolean file_data_perform_move(FileData *fd)
2779 g_assert(!strcmp(fd->change->source, fd->path));
2780 return move_file(fd->change->source, fd->change->dest);
2783 static gboolean file_data_perform_copy(FileData *fd)
2785 g_assert(!strcmp(fd->change->source, fd->path));
2786 return copy_file(fd->change->source, fd->change->dest);
2789 static gboolean file_data_perform_delete(FileData *fd)
2791 if (isdir(fd->path) && !islink(fd->path))
2792 return rmdir_utf8(fd->path);
2794 if (options->file_ops.safe_delete_enable)
2795 return file_util_safe_unlink(fd->path);
2797 return unlink_file(fd->path);
2800 gboolean file_data_perform_ci(FileData *fd)
2802 FileDataChangeType type = fd->change->type;
2806 case FILEDATA_CHANGE_MOVE:
2807 return file_data_perform_move(fd);
2808 case FILEDATA_CHANGE_COPY:
2809 return file_data_perform_copy(fd);
2810 case FILEDATA_CHANGE_RENAME:
2811 return file_data_perform_move(fd); /* the same as move */
2812 case FILEDATA_CHANGE_DELETE:
2813 return file_data_perform_delete(fd);
2814 case FILEDATA_CHANGE_WRITE_METADATA:
2815 return metadata_write_perform(fd);
2816 case FILEDATA_CHANGE_UNSPECIFIED:
2817 /* nothing to do here */
2825 gboolean file_data_sc_perform_ci(FileData *fd)
2828 gboolean ret = TRUE;
2829 FileDataChangeType type = fd->change->type;
2831 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2833 work = fd->sidecar_files;
2836 FileData *sfd = work->data;
2838 if (!file_data_perform_ci(sfd)) ret = FALSE;
2842 if (!file_data_perform_ci(fd)) ret = FALSE;
2848 * updates FileData structure according to FileDataChangeInfo
2851 gboolean file_data_apply_ci(FileData *fd)
2853 FileDataChangeType type = fd->change->type;
2856 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2858 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2859 file_data_planned_change_remove(fd);
2861 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2863 /* this change overwrites another file which is already known to other modules
2864 renaming fd would create duplicate FileData structure
2865 the best thing we can do is nothing
2866 FIXME: maybe we could copy stuff like marks
2868 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2872 file_data_set_path(fd, fd->change->dest);
2875 file_data_increment_version(fd);
2876 file_data_send_notification(fd, NOTIFY_CHANGE);
2881 gboolean file_data_sc_apply_ci(FileData *fd)
2884 FileDataChangeType type = fd->change->type;
2886 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2888 work = fd->sidecar_files;
2891 FileData *sfd = work->data;
2893 file_data_apply_ci(sfd);
2897 file_data_apply_ci(fd);
2902 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2905 if (fd->parent) fd = fd->parent;
2906 if (!g_list_find(list, fd)) return FALSE;
2908 work = fd->sidecar_files;
2911 if (!g_list_find(list, work->data)) return FALSE;
2917 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2922 /* change partial groups to independent files */
2927 FileData *fd = work->data;
2930 if (!file_data_list_contains_whole_group(list, fd))
2932 file_data_disable_grouping(fd, TRUE);
2935 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2941 /* remove sidecars from the list,
2942 they can be still acessed via main_fd->sidecar_files */
2946 FileData *fd = work->data;
2950 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2952 out = g_list_prepend(out, file_data_ref(fd));
2956 filelist_free(list);
2957 out = g_list_reverse(out);
2967 * notify other modules about the change described by FileDataChangeInfo
2970 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2971 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2972 implementation in view_file_list.c */
2975 typedef struct _NotifyIdleData NotifyIdleData;
2977 struct _NotifyIdleData {
2983 typedef struct _NotifyData NotifyData;
2985 struct _NotifyData {
2986 FileDataNotifyFunc func;
2988 NotifyPriority priority;
2991 static GList *notify_func_list = NULL;
2993 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2995 NotifyData *nda = (NotifyData *)a;
2996 NotifyData *ndb = (NotifyData *)b;
2998 if (nda->priority < ndb->priority) return -1;
2999 if (nda->priority > ndb->priority) return 1;
3003 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
3006 GList *work = notify_func_list;
3010 NotifyData *nd = (NotifyData *)work->data;
3012 if (nd->func == func && nd->data == data)
3014 g_warning("Notify func already registered");
3020 nd = g_new(NotifyData, 1);
3023 nd->priority = priority;
3025 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
3026 DEBUG_2("Notify func registered: %p", nd);
3031 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
3033 GList *work = notify_func_list;
3037 NotifyData *nd = (NotifyData *)work->data;
3039 if (nd->func == func && nd->data == data)
3041 notify_func_list = g_list_delete_link(notify_func_list, work);
3043 DEBUG_2("Notify func unregistered: %p", nd);
3049 g_warning("Notify func not found");
3054 gboolean file_data_send_notification_idle_cb(gpointer data)
3056 NotifyIdleData *nid = (NotifyIdleData *)data;
3057 GList *work = notify_func_list;
3061 NotifyData *nd = (NotifyData *)work->data;
3063 nd->func(nid->fd, nid->type, nd->data);
3066 file_data_unref(nid->fd);
3071 void file_data_send_notification(FileData *fd, NotifyType type)
3073 GList *work = notify_func_list;
3077 NotifyData *nd = (NotifyData *)work->data;
3079 nd->func(fd, type, nd->data);
3083 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3084 nid->fd = file_data_ref(fd);
3086 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3090 static GHashTable *file_data_monitor_pool = NULL;
3091 static guint realtime_monitor_id = 0; /* event source id */
3093 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
3097 file_data_check_changed_files(fd);
3099 DEBUG_1("monitor %s", fd->path);
3102 static gboolean realtime_monitor_cb(gpointer data)
3104 if (!options->update_on_time_change) return TRUE;
3105 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
3109 gboolean file_data_register_real_time_monitor(FileData *fd)
3115 if (!file_data_monitor_pool)
3116 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3118 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3120 DEBUG_1("Register realtime %d %s", count, fd->path);
3123 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3125 if (!realtime_monitor_id)
3127 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
3133 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3137 g_assert(file_data_monitor_pool);
3139 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3141 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3143 g_assert(count > 0);
3148 g_hash_table_remove(file_data_monitor_pool, fd);
3150 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3152 file_data_unref(fd);
3154 if (g_hash_table_size(file_data_monitor_pool) == 0)
3156 g_source_remove(realtime_monitor_id);
3157 realtime_monitor_id = 0;
3165 *-----------------------------------------------------------------------------
3166 * Saving marks list, clearing marks
3167 * Uses file_data_pool
3168 *-----------------------------------------------------------------------------
3171 static void marks_get_files(gpointer key, gpointer value, gpointer userdata)
3173 gchar *file_name = key;
3174 GString *result = userdata;
3177 if (isfile(file_name))
3180 if (fd && fd->marks > 0)
3182 g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks);
3187 gboolean marks_list_load(const gchar *path)
3195 pathl = path_from_utf8(path);
3196 f = fopen(pathl, "r");
3198 if (!f) return FALSE;
3200 /* first line must start with Marks comment */
3201 if (!fgets(s_buf, sizeof(s_buf), f) ||
3202 strncmp(s_buf, "#Marks", 6) != 0)
3208 while (fgets(s_buf, sizeof(s_buf), f))
3210 if (s_buf[0]=='#') continue;
3211 file_path = strtok(s_buf, ",");
3212 marks_value = strtok(NULL, ",");
3213 if (isfile(file_path))
3215 FileData *fd = file_data_new_group(file_path);
3220 gint mark_no = 1 << n;
3221 if (atoi(marks_value) & mark_no)
3223 file_data_set_mark(fd, n , 1);
3234 gboolean marks_list_save(gchar *path, gboolean save)
3236 SecureSaveInfo *ssi;
3238 GString *marks = g_string_new("");
3240 pathl = path_from_utf8(path);
3241 ssi = secure_open(pathl);
3245 log_printf(_("Error: Unable to write marks lists to: %s\n"), path);
3249 secure_fprintf(ssi, "#Marks lists\n");
3253 g_hash_table_foreach(file_data_pool, marks_get_files, marks);
3255 secure_fprintf(ssi, "%s", marks->str);
3256 g_string_free(marks, FALSE);
3258 secure_fprintf(ssi, "#end\n");
3259 return (secure_close(ssi) == 0);
3262 static void marks_clear(gpointer key, gpointer value, gpointer userdata)
3264 gchar *file_name = key;
3269 if (isfile(file_name))
3272 if (fd && fd->marks > 0)
3278 if (fd->marks & mark_no)
3280 file_data_set_mark(fd, n , 0);
3288 void marks_clear_all()
3290 g_hash_table_foreach(file_data_pool, marks_clear, NULL);
3292 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */