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;
432 fd->rating = STAR_RATING_NOT_READ;
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);
487 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
488 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), file, file->path);
493 uint year, month, day, hour, min, sec;
495 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
496 time_str.tm_year = year - 1900;
497 time_str.tm_mon = month - 1;
498 time_str.tm_mday = day;
499 time_str.tm_hour = hour;
500 time_str.tm_min = min;
501 time_str.tm_sec = sec;
502 time_str.tm_isdst = 0;
504 file->exifdate = mktime(&time_str);
510 void read_exif_time_digitized_data(FileData *file)
512 if (file->exifdate_digitized > 0)
514 DEBUG_1("%s set_exif_time_digitized_data: Already exists for %s", get_exec_time(), file->path);
525 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeDigitized");
526 DEBUG_2("%s set_exif_time_digitized_data: reading %p %s", get_exec_time(), file, file->path);
531 uint year, month, day, hour, min, sec;
533 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
534 time_str.tm_year = year - 1900;
535 time_str.tm_mon = month - 1;
536 time_str.tm_mday = day;
537 time_str.tm_hour = hour;
538 time_str.tm_min = min;
539 time_str.tm_sec = sec;
540 time_str.tm_isdst = 0;
542 file->exifdate_digitized = mktime(&time_str);
548 void read_rating_data(FileData *file)
552 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
555 file->rating = atoi(rating_str);
560 void set_exif_time_data(GList *files)
562 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
566 FileData *file = files->data;
568 read_exif_time_data(file);
573 void set_exif_time_digitized_data(GList *files)
575 DEBUG_1("%s set_exif_time_digitized_data: ...", get_exec_time());
579 FileData *file = files->data;
581 read_exif_time_digitized_data(file);
586 void set_rating_data(GList *files)
589 DEBUG_1("%s set_rating_data: ...", get_exec_time());
593 FileData *file = files->data;
594 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
597 file->rating = atoi(rating_str);
604 FileData *file_data_new_no_grouping(const gchar *path_utf8)
608 if (!stat_utf8(path_utf8, &st))
614 return file_data_new(path_utf8, &st, TRUE);
617 FileData *file_data_new_dir(const gchar *path_utf8)
621 if (!stat_utf8(path_utf8, &st))
627 /* dir or non-existing yet */
628 g_assert(S_ISDIR(st.st_mode));
630 return file_data_new(path_utf8, &st, TRUE);
634 *-----------------------------------------------------------------------------
636 *-----------------------------------------------------------------------------
639 #ifdef DEBUG_FILEDATA
640 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
642 FileData *file_data_ref(FileData *fd)
645 if (fd == NULL) return NULL;
646 if (fd->magick != FD_MAGICK)
647 #ifdef DEBUG_FILEDATA
648 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, fd);
650 log_printf("Error: fd magick mismatch fd=%p", fd);
652 g_assert(fd->magick == FD_MAGICK);
655 #ifdef DEBUG_FILEDATA
656 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", fd, fd->ref, fd->path, file, line);
658 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
663 static void file_data_free(FileData *fd)
665 g_assert(fd->magick == FD_MAGICK);
666 g_assert(fd->ref == 0);
667 g_assert(!fd->locked);
669 #ifdef DEBUG_FILEDATA
670 global_file_data_count--;
671 DEBUG_2("file data count--: %d", global_file_data_count);
674 metadata_cache_free(fd);
675 g_hash_table_remove(file_data_pool, fd->original_path);
678 g_free(fd->original_path);
679 g_free(fd->collate_key_name);
680 g_free(fd->collate_key_name_nocase);
681 g_free(fd->extended_extension);
682 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
683 histmap_free(fd->histmap);
685 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
687 file_data_change_info_free(NULL, fd);
692 * \brief Checks if the FileData is referenced
694 * Checks the refcount and whether the FileData is locked.
696 static gboolean file_data_check_has_ref(FileData *fd)
698 return fd->ref > 0 || fd->locked;
702 * \brief Consider freeing a FileData.
704 * This function will free a FileData and its children provided that neither its parent nor it has
705 * a positive refcount, and provided that neither is locked.
707 static void file_data_consider_free(FileData *fd)
710 FileData *parent = fd->parent ? fd->parent : fd;
712 g_assert(fd->magick == FD_MAGICK);
713 if (file_data_check_has_ref(fd)) return;
714 if (file_data_check_has_ref(parent)) return;
716 work = parent->sidecar_files;
719 FileData *sfd = work->data;
720 if (file_data_check_has_ref(sfd)) return;
724 /* Neither the parent nor the siblings are referenced, so we can free everything */
725 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
726 fd->path, fd->parent ? parent->path : "-");
728 work = parent->sidecar_files;
731 FileData *sfd = work->data;
736 g_list_free(parent->sidecar_files);
737 parent->sidecar_files = NULL;
739 file_data_free(parent);
742 #ifdef DEBUG_FILEDATA
743 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
745 void file_data_unref(FileData *fd)
748 if (fd == NULL) return;
749 if (fd->magick != FD_MAGICK)
750 #ifdef DEBUG_FILEDATA
751 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, fd);
753 log_printf("Error: fd magick mismatch fd=%p", fd);
755 g_assert(fd->magick == FD_MAGICK);
758 #ifdef DEBUG_FILEDATA
759 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", fd, fd->ref, fd->locked, fd->path,
762 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
765 // Free FileData if it's no longer ref'd
766 file_data_consider_free(fd);
770 * \brief Lock the FileData in memory.
772 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
773 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
774 * even if the code would continue to function properly even if the FileData were freed. Code that
775 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
777 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
778 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
780 void file_data_lock(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 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
792 * \brief Reset the maintain-FileData-in-memory lock
794 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
795 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
796 * keeping it from being freed.
798 void file_data_unlock(FileData *fd)
800 if (fd == NULL) return;
801 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", fd);
803 g_assert(fd->magick == FD_MAGICK);
806 // Free FileData if it's no longer ref'd
807 file_data_consider_free(fd);
811 * \brief Lock all of the FileDatas in the provided list
813 * \see file_data_lock(FileData)
815 void file_data_lock_list(GList *list)
822 FileData *fd = work->data;
829 * \brief Unlock all of the FileDatas in the provided list
831 * \see file_data_unlock(FileData)
833 void file_data_unlock_list(GList *list)
840 FileData *fd = work->data;
842 file_data_unlock(fd);
847 *-----------------------------------------------------------------------------
848 * sidecar file info struct
849 *-----------------------------------------------------------------------------
852 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
854 const FileData *fda = a;
855 const FileData *fdb = b;
857 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
858 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
860 return strcmp(fdb->extension, fda->extension);
864 static gint sidecar_file_priority(const gchar *extension)
869 if (extension == NULL)
872 work = sidecar_ext_get_list();
875 gchar *ext = work->data;
878 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
884 static void file_data_check_sidecars(const GList *basename_list)
886 /* basename_list contains the new group - first is the parent, then sorted sidecars */
887 /* all files in the list have ref count > 0 */
890 GList *s_work, *new_sidecars;
893 if (!basename_list) return;
896 DEBUG_2("basename start");
897 work = basename_list;
900 FileData *fd = work->data;
902 g_assert(fd->magick == FD_MAGICK);
903 DEBUG_2("basename: %p %s", fd, fd->name);
906 g_assert(fd->parent->magick == FD_MAGICK);
907 DEBUG_2(" parent: %p", fd->parent);
909 s_work = fd->sidecar_files;
912 FileData *sfd = s_work->data;
913 s_work = s_work->next;
914 g_assert(sfd->magick == FD_MAGICK);
915 DEBUG_2(" sidecar: %p %s", sfd, sfd->name);
918 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
921 parent_fd = basename_list->data;
923 /* check if the second and next entries of basename_list are already connected
924 as sidecars of the first entry (parent_fd) */
925 work = basename_list->next;
926 s_work = parent_fd->sidecar_files;
928 while (work && s_work)
930 if (work->data != s_work->data) break;
932 s_work = s_work->next;
935 if (!work && !s_work)
937 DEBUG_2("basename no change");
938 return; /* no change in grouping */
941 /* we have to regroup it */
943 /* first, disconnect everything and send notification*/
945 work = basename_list;
948 FileData *fd = work->data;
950 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
954 FileData *old_parent = fd->parent;
955 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
956 file_data_ref(old_parent);
957 file_data_disconnect_sidecar_file(old_parent, fd);
958 file_data_send_notification(old_parent, NOTIFY_REREAD);
959 file_data_unref(old_parent);
962 while (fd->sidecar_files)
964 FileData *sfd = fd->sidecar_files->data;
965 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
967 file_data_disconnect_sidecar_file(fd, sfd);
968 file_data_send_notification(sfd, NOTIFY_REREAD);
969 file_data_unref(sfd);
971 file_data_send_notification(fd, NOTIFY_GROUPING);
973 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
976 /* now we can form the new group */
977 work = basename_list->next;
981 FileData *sfd = work->data;
982 g_assert(sfd->magick == FD_MAGICK);
983 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
984 sfd->parent = parent_fd;
985 new_sidecars = g_list_prepend(new_sidecars, sfd);
988 g_assert(parent_fd->sidecar_files == NULL);
989 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
990 DEBUG_1("basename group changed for %s", parent_fd->path);
994 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
996 g_assert(target->magick == FD_MAGICK);
997 g_assert(sfd->magick == FD_MAGICK);
998 g_assert(g_list_find(target->sidecar_files, sfd));
1000 file_data_ref(target);
1003 g_assert(sfd->parent == target);
1005 file_data_increment_version(sfd); /* increments both sfd and target */
1007 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
1009 g_free(sfd->extended_extension);
1010 sfd->extended_extension = NULL;
1012 file_data_unref(target);
1013 file_data_unref(sfd);
1016 /* disables / enables grouping for particular file, sends UPDATE notification */
1017 void file_data_disable_grouping(FileData *fd, gboolean disable)
1019 if (!fd->disable_grouping == !disable) return;
1021 fd->disable_grouping = !!disable;
1027 FileData *parent = file_data_ref(fd->parent);
1028 file_data_disconnect_sidecar_file(parent, fd);
1029 file_data_send_notification(parent, NOTIFY_GROUPING);
1030 file_data_unref(parent);
1032 else if (fd->sidecar_files)
1034 GList *sidecar_files = filelist_copy(fd->sidecar_files);
1035 GList *work = sidecar_files;
1038 FileData *sfd = work->data;
1040 file_data_disconnect_sidecar_file(fd, sfd);
1041 file_data_send_notification(sfd, NOTIFY_GROUPING);
1043 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
1044 filelist_free(sidecar_files);
1048 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
1053 file_data_increment_version(fd);
1054 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
1056 file_data_send_notification(fd, NOTIFY_GROUPING);
1059 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
1066 FileData *fd = work->data;
1068 file_data_disable_grouping(fd, disable);
1076 *-----------------------------------------------------------------------------
1078 *-----------------------------------------------------------------------------
1082 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
1085 if (!filelist_sort_ascend)
1092 switch (filelist_sort_method)
1097 if (fa->size < fb->size) return -1;
1098 if (fa->size > fb->size) return 1;
1099 /* fall back to name */
1102 if (fa->date < fb->date) return -1;
1103 if (fa->date > fb->date) return 1;
1104 /* fall back to name */
1107 if (fa->cdate < fb->cdate) return -1;
1108 if (fa->cdate > fb->cdate) return 1;
1109 /* fall back to name */
1112 if (fa->exifdate < fb->exifdate) return -1;
1113 if (fa->exifdate > fb->exifdate) return 1;
1114 /* fall back to name */
1116 case SORT_EXIFTIMEDIGITIZED:
1117 if (fa->exifdate_digitized < fb->exifdate_digitized) return -1;
1118 if (fa->exifdate_digitized > fb->exifdate_digitized) return 1;
1119 /* fall back to name */
1122 if (fa->rating < fb->rating) return -1;
1123 if (fa->rating > fb->rating) return 1;
1124 /* fall back to name */
1127 if (fa->format_class < fb->format_class) return -1;
1128 if (fa->format_class > fb->format_class) return 1;
1129 /* fall back to name */
1131 #ifdef HAVE_STRVERSCMP
1133 ret = strverscmp(fa->name, fb->name);
1134 if (ret != 0) return ret;
1141 if (options->file_sort.case_sensitive)
1142 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1144 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1146 if (ret != 0) return ret;
1148 /* do not return 0 unless the files are really the same
1149 file_data_pool ensures that original_path is unique
1151 return strcmp(fa->original_path, fb->original_path);
1154 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1156 filelist_sort_method = method;
1157 filelist_sort_ascend = ascend;
1158 return filelist_sort_compare_filedata(fa, fb);
1161 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1163 return filelist_sort_compare_filedata(a, b);
1166 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1168 filelist_sort_method = method;
1169 filelist_sort_ascend = ascend;
1170 return g_list_sort(list, cb);
1173 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1175 filelist_sort_method = method;
1176 filelist_sort_ascend = ascend;
1177 return g_list_insert_sorted(list, data, cb);
1180 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1182 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1185 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
1187 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1191 *-----------------------------------------------------------------------------
1192 * basename hash - grouping of sidecars in filelist
1193 *-----------------------------------------------------------------------------
1197 static GHashTable *file_data_basename_hash_new(void)
1199 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1202 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1205 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1207 list = g_hash_table_lookup(basename_hash, basename);
1211 DEBUG_1("TG: basename_hash not found for %s",fd->path);
1212 const gchar *parent_extension = registered_extension_from_path(basename);
1214 if (parent_extension)
1216 DEBUG_1("TG: parent extension %s",parent_extension);
1217 gchar *parent_basename = g_strndup(basename, parent_extension - basename);
1218 DEBUG_1("TG: parent basename %s",parent_basename);
1219 FileData *parent_fd = g_hash_table_lookup(file_data_pool, basename);
1222 DEBUG_1("TG: parent fd found");
1223 list = g_hash_table_lookup(basename_hash, parent_basename);
1224 if (!g_list_find(list, parent_fd))
1226 DEBUG_1("TG: parent fd doesn't fit");
1227 g_free(parent_basename);
1233 basename = parent_basename;
1234 fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
1240 if (!g_list_find(list, fd))
1242 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1243 g_hash_table_insert(basename_hash, basename, list);
1252 static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
1254 file_data_basename_hash_insert((GHashTable *)basename_hash, (FileData *)fd);
1257 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
1259 filelist_free((GList *)value);
1262 static void file_data_basename_hash_free(GHashTable *basename_hash)
1264 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1265 g_hash_table_destroy(basename_hash);
1269 *-----------------------------------------------------------------------------
1270 * handling sidecars in filelist
1271 *-----------------------------------------------------------------------------
1274 static GList *filelist_filter_out_sidecars(GList *flist)
1276 GList *work = flist;
1277 GList *flist_filtered = NULL;
1281 FileData *fd = work->data;
1284 if (fd->parent) /* remove fd's that are children */
1285 file_data_unref(fd);
1287 flist_filtered = g_list_prepend(flist_filtered, fd);
1291 return flist_filtered;
1294 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
1296 GList *basename_list = (GList *)value;
1297 file_data_check_sidecars(basename_list);
1301 static gboolean is_hidden_file(const gchar *name)
1303 if (name[0] != '.') return FALSE;
1304 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1309 *-----------------------------------------------------------------------------
1310 * the main filelist function
1311 *-----------------------------------------------------------------------------
1314 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1319 GList *dlist = NULL;
1320 GList *flist = NULL;
1321 GList *xmp_files = NULL;
1322 gint (*stat_func)(const gchar *path, struct stat *buf);
1323 GHashTable *basename_hash = NULL;
1325 g_assert(files || dirs);
1327 if (files) *files = NULL;
1328 if (dirs) *dirs = NULL;
1330 pathl = path_from_utf8(dir_path);
1331 if (!pathl) return FALSE;
1333 dp = opendir(pathl);
1340 if (files) basename_hash = file_data_basename_hash_new();
1342 if (follow_symlinks)
1347 while ((dir = readdir(dp)) != NULL)
1349 struct stat ent_sbuf;
1350 const gchar *name = dir->d_name;
1353 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1356 filepath = g_build_filename(pathl, name, NULL);
1357 if (stat_func(filepath, &ent_sbuf) >= 0)
1359 if (S_ISDIR(ent_sbuf.st_mode))
1361 /* we ignore the .thumbnails dir for cleanliness */
1363 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1364 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1365 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1366 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1368 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1373 if (files && filter_name_exists(name))
1375 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1376 flist = g_list_prepend(flist, fd);
1377 if (fd->sidecar_priority && !fd->disable_grouping)
1379 if (strcmp(fd->extension, ".xmp") != 0)
1380 file_data_basename_hash_insert(basename_hash, fd);
1382 xmp_files = g_list_append(xmp_files, fd);
1389 if (errno == EOVERFLOW)
1391 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1403 g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
1404 g_list_free(xmp_files);
1407 if (dirs) *dirs = dlist;
1411 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1413 *files = filelist_filter_out_sidecars(flist);
1415 if (basename_hash) file_data_basename_hash_free(basename_hash);
1420 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1422 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1425 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1427 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1430 FileData *file_data_new_group(const gchar *path_utf8)
1437 if (!stat_utf8(path_utf8, &st))
1443 if (S_ISDIR(st.st_mode))
1444 return file_data_new(path_utf8, &st, TRUE);
1446 dir = remove_level_from_path(path_utf8);
1448 filelist_read_real(dir, &files, NULL, TRUE);
1450 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1451 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1457 filelist_free(files);
1463 void filelist_free(GList *list)
1470 file_data_unref((FileData *)work->data);
1478 GList *filelist_copy(GList *list)
1480 GList *new_list = NULL;
1491 new_list = g_list_prepend(new_list, file_data_ref(fd));
1494 return g_list_reverse(new_list);
1497 GList *filelist_from_path_list(GList *list)
1499 GList *new_list = NULL;
1510 new_list = g_list_prepend(new_list, file_data_new_group(path));
1513 return g_list_reverse(new_list);
1516 GList *filelist_to_path_list(GList *list)
1518 GList *new_list = NULL;
1529 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1532 return g_list_reverse(new_list);
1535 GList *filelist_filter(GList *list, gboolean is_dir_list)
1539 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1544 FileData *fd = (FileData *)(work->data);
1545 const gchar *name = fd->name;
1547 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1548 (!is_dir_list && !filter_name_exists(name)) ||
1549 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1550 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1554 list = g_list_remove_link(list, link);
1555 file_data_unref(fd);
1566 *-----------------------------------------------------------------------------
1567 * filelist recursive
1568 *-----------------------------------------------------------------------------
1571 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1573 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1576 GList *filelist_sort_path(GList *list)
1578 return g_list_sort(list, filelist_sort_path_cb);
1581 static void filelist_recursive_append(GList **list, GList *dirs)
1588 FileData *fd = (FileData *)(work->data);
1592 if (filelist_read(fd, &f, &d))
1594 f = filelist_filter(f, FALSE);
1595 f = filelist_sort_path(f);
1596 *list = g_list_concat(*list, f);
1598 d = filelist_filter(d, TRUE);
1599 d = filelist_sort_path(d);
1600 filelist_recursive_append(list, d);
1608 GList *filelist_recursive(FileData *dir_fd)
1613 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1614 list = filelist_filter(list, FALSE);
1615 list = filelist_sort_path(list);
1617 d = filelist_filter(d, TRUE);
1618 d = filelist_sort_path(d);
1619 filelist_recursive_append(&list, d);
1626 *-----------------------------------------------------------------------------
1627 * file modification support
1628 *-----------------------------------------------------------------------------
1632 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1634 if (!fdci && fd) fdci = fd->change;
1638 g_free(fdci->source);
1643 if (fd) fd->change = NULL;
1646 static gboolean file_data_can_write_directly(FileData *fd)
1648 return filter_name_is_writable(fd->extension);
1651 static gboolean file_data_can_write_sidecar(FileData *fd)
1653 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1656 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1658 gchar *sidecar_path = NULL;
1661 if (!file_data_can_write_sidecar(fd)) return NULL;
1663 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1664 gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1667 FileData *sfd = work->data;
1669 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1671 sidecar_path = g_strdup(sfd->path);
1675 g_free(extended_extension);
1677 if (!existing_only && !sidecar_path)
1679 if (options->metadata.sidecar_extended_name)
1680 sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1683 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1684 sidecar_path = g_strconcat(base, ".xmp", NULL);
1689 return sidecar_path;
1693 * marks and orientation
1696 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1697 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1698 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1699 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1701 gboolean file_data_get_mark(FileData *fd, gint n)
1703 gboolean valid = (fd->valid_marks & (1 << n));
1705 if (file_data_get_mark_func[n] && !valid)
1707 guint old = fd->marks;
1708 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1710 if (!value != !(fd->marks & (1 << n)))
1712 fd->marks = fd->marks ^ (1 << n);
1715 fd->valid_marks |= (1 << n);
1716 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1718 file_data_unref(fd);
1720 else if (!old && fd->marks)
1726 return !!(fd->marks & (1 << n));
1729 guint file_data_get_marks(FileData *fd)
1732 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1736 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1739 if (!value == !file_data_get_mark(fd, n)) return;
1741 if (file_data_set_mark_func[n])
1743 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1748 fd->marks = fd->marks ^ (1 << n);
1750 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1752 file_data_unref(fd);
1754 else if (!old && fd->marks)
1759 file_data_increment_version(fd);
1760 file_data_send_notification(fd, NOTIFY_MARKS);
1763 gboolean file_data_filter_marks(FileData *fd, guint filter)
1766 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1767 return ((fd->marks & filter) == filter);
1770 GList *file_data_filter_marks_list(GList *list, guint filter)
1777 FileData *fd = work->data;
1781 if (!file_data_filter_marks(fd, filter))
1783 list = g_list_remove_link(list, link);
1784 file_data_unref(fd);
1792 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1794 FileData *fd = value;
1795 file_data_increment_version(fd);
1796 file_data_send_notification(fd, NOTIFY_MARKS);
1799 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1801 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1803 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1805 file_data_get_mark_func[n] = get_mark_func;
1806 file_data_set_mark_func[n] = set_mark_func;
1807 file_data_mark_func_data[n] = data;
1808 file_data_destroy_mark_func[n] = notify;
1810 if (get_mark_func && file_data_pool)
1812 /* this effectively changes all known files */
1813 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1819 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1821 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1822 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1823 if (data) *data = file_data_mark_func_data[n];
1826 gint file_data_get_user_orientation(FileData *fd)
1828 return fd->user_orientation;
1831 void file_data_set_user_orientation(FileData *fd, gint value)
1833 if (fd->user_orientation == value) return;
1835 fd->user_orientation = value;
1836 file_data_increment_version(fd);
1837 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1842 * file_data - operates on the given fd
1843 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1847 /* return list of sidecar file extensions in a string */
1848 gchar *file_data_sc_list_to_string(FileData *fd)
1851 GString *result = g_string_new("");
1853 work = fd->sidecar_files;
1856 FileData *sfd = work->data;
1858 result = g_string_append(result, "+ ");
1859 result = g_string_append(result, sfd->extension);
1861 if (work) result = g_string_append_c(result, ' ');
1864 return g_string_free(result, FALSE);
1870 * add FileDataChangeInfo (see typedefs.h) for the given operation
1871 * uses file_data_add_change_info
1873 * fails if the fd->change already exists - change operations can't run in parallel
1874 * fd->change_info works as a lock
1876 * dest can be NULL - in this case the current name is used for now, it will
1881 FileDataChangeInfo types:
1883 MOVE - path is changed, name may be changed too
1884 RENAME - path remains unchanged, name is changed
1885 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1886 sidecar names are changed too, extensions are not changed
1888 UPDATE - file size, date or grouping has been changed
1891 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1893 FileDataChangeInfo *fdci;
1895 if (fd->change) return FALSE;
1897 fdci = g_new0(FileDataChangeInfo, 1);
1902 fdci->source = g_strdup(src);
1904 fdci->source = g_strdup(fd->path);
1907 fdci->dest = g_strdup(dest);
1914 static void file_data_planned_change_remove(FileData *fd)
1916 if (file_data_planned_change_hash &&
1917 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1919 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1921 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1922 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1923 file_data_unref(fd);
1924 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1926 g_hash_table_destroy(file_data_planned_change_hash);
1927 file_data_planned_change_hash = NULL;
1928 DEBUG_1("planned change: empty");
1935 void file_data_free_ci(FileData *fd)
1937 FileDataChangeInfo *fdci = fd->change;
1941 file_data_planned_change_remove(fd);
1943 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1945 g_free(fdci->source);
1953 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1955 FileDataChangeInfo *fdci = fd->change;
1957 fdci->regroup_when_finished = enable;
1960 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1964 if (fd->parent) fd = fd->parent;
1966 if (fd->change) return FALSE;
1968 work = fd->sidecar_files;
1971 FileData *sfd = work->data;
1973 if (sfd->change) return FALSE;
1977 file_data_add_ci(fd, type, NULL, NULL);
1979 work = fd->sidecar_files;
1982 FileData *sfd = work->data;
1984 file_data_add_ci(sfd, type, NULL, NULL);
1991 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1995 if (fd->parent) fd = fd->parent;
1997 if (!fd->change || fd->change->type != type) return FALSE;
1999 work = fd->sidecar_files;
2002 FileData *sfd = work->data;
2004 if (!sfd->change || sfd->change->type != type) return FALSE;
2012 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
2014 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
2015 file_data_sc_update_ci_copy(fd, dest_path);
2019 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
2021 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
2022 file_data_sc_update_ci_move(fd, dest_path);
2026 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
2028 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
2029 file_data_sc_update_ci_rename(fd, dest_path);
2033 gboolean file_data_sc_add_ci_delete(FileData *fd)
2035 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
2038 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
2040 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
2041 file_data_sc_update_ci_unspecified(fd, dest_path);
2045 gboolean file_data_add_ci_write_metadata(FileData *fd)
2047 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
2050 void file_data_sc_free_ci(FileData *fd)
2054 if (fd->parent) fd = fd->parent;
2056 file_data_free_ci(fd);
2058 work = fd->sidecar_files;
2061 FileData *sfd = work->data;
2063 file_data_free_ci(sfd);
2068 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2071 gboolean ret = TRUE;
2076 FileData *fd = work->data;
2078 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2085 static void file_data_sc_revert_ci_list(GList *fd_list)
2092 FileData *fd = work->data;
2094 file_data_sc_free_ci(fd);
2099 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2106 FileData *fd = work->data;
2108 if (!func(fd, dest))
2110 file_data_sc_revert_ci_list(work->prev);
2119 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2121 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2124 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2126 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2129 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2131 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2134 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2136 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2139 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2142 gboolean ret = TRUE;
2147 FileData *fd = work->data;
2149 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2156 void file_data_free_ci_list(GList *fd_list)
2163 FileData *fd = work->data;
2165 file_data_free_ci(fd);
2170 void file_data_sc_free_ci_list(GList *fd_list)
2177 FileData *fd = work->data;
2179 file_data_sc_free_ci(fd);
2185 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2186 * fails if fd->change does not exist or the change type does not match
2189 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2191 FileDataChangeType type = fd->change->type;
2193 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2197 if (!file_data_planned_change_hash)
2198 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2200 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2202 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2203 g_hash_table_remove(file_data_planned_change_hash, old_path);
2204 file_data_unref(fd);
2207 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
2212 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2213 g_hash_table_remove(file_data_planned_change_hash, new_path);
2214 file_data_unref(ofd);
2217 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2219 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2224 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2226 gchar *old_path = fd->change->dest;
2228 fd->change->dest = g_strdup(dest_path);
2229 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2233 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2235 const gchar *extension = registered_extension_from_path(fd->change->source);
2236 gchar *base = remove_extension_from_path(dest_path);
2237 gchar *old_path = fd->change->dest;
2239 fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2240 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2246 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2249 gchar *dest_path_full = NULL;
2251 if (fd->parent) fd = fd->parent;
2255 dest_path = fd->path;
2257 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2259 gchar *dir = remove_level_from_path(fd->path);
2261 dest_path_full = g_build_filename(dir, dest_path, NULL);
2263 dest_path = dest_path_full;
2265 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2267 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2268 dest_path = dest_path_full;
2271 file_data_update_ci_dest(fd, dest_path);
2273 work = fd->sidecar_files;
2276 FileData *sfd = work->data;
2278 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2282 g_free(dest_path_full);
2285 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2287 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2288 file_data_sc_update_ci(fd, dest_path);
2292 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2294 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2297 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2299 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2302 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2304 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2307 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2309 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2312 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2314 gboolean (*func)(FileData *, const gchar *))
2317 gboolean ret = TRUE;
2322 FileData *fd = work->data;
2324 if (!func(fd, dest)) ret = FALSE;
2331 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2333 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2336 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2338 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2341 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2343 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2348 * verify source and dest paths - dest image exists, etc.
2349 * it should detect all possible problems with the planned operation
2352 gint file_data_verify_ci(FileData *fd, GList *list)
2354 gint ret = CHANGE_OK;
2357 FileData *fd1 = NULL;
2361 DEBUG_1("Change checked: no change info: %s", fd->path);
2365 if (!isname(fd->path))
2367 /* this probably should not happen */
2368 ret |= CHANGE_NO_SRC;
2369 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2373 dir = remove_level_from_path(fd->path);
2375 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2376 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2377 fd->change->type != FILEDATA_CHANGE_RENAME &&
2378 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2381 ret |= CHANGE_WARN_UNSAVED_META;
2382 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2385 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2386 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2387 !access_file(fd->path, R_OK))
2389 ret |= CHANGE_NO_READ_PERM;
2390 DEBUG_1("Change checked: no read permission: %s", fd->path);
2392 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2393 !access_file(dir, W_OK))
2395 ret |= CHANGE_NO_WRITE_PERM_DIR;
2396 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2398 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2399 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2400 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2401 !access_file(fd->path, W_OK))
2403 ret |= CHANGE_WARN_NO_WRITE_PERM;
2404 DEBUG_1("Change checked: no write permission: %s", fd->path);
2406 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2407 - that means that there are no hard errors and warnings can be disabled
2408 - the destination is determined during the check
2410 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2412 /* determine destination file */
2413 gboolean have_dest = FALSE;
2414 gchar *dest_dir = NULL;
2416 if (options->metadata.save_in_image_file)
2418 if (file_data_can_write_directly(fd))
2420 /* we can write the file directly */
2421 if (access_file(fd->path, W_OK))
2427 if (options->metadata.warn_on_write_problems)
2429 ret |= CHANGE_WARN_NO_WRITE_PERM;
2430 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2434 else if (file_data_can_write_sidecar(fd))
2436 /* we can write sidecar */
2437 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2438 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2440 file_data_update_ci_dest(fd, sidecar);
2445 if (options->metadata.warn_on_write_problems)
2447 ret |= CHANGE_WARN_NO_WRITE_PERM;
2448 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2457 /* write private metadata file under ~/.geeqie */
2459 /* If an existing metadata file exists, we will try writing to
2460 * it's location regardless of the user's preference.
2462 gchar *metadata_path = NULL;
2464 /* but ignore XMP if we are not able to write it */
2465 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2467 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2469 if (metadata_path && !access_file(metadata_path, W_OK))
2471 g_free(metadata_path);
2472 metadata_path = NULL;
2479 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2480 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2482 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2484 metadata_path = g_build_filename(dest_dir, filename, NULL);
2488 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2490 file_data_update_ci_dest(fd, metadata_path);
2495 ret |= CHANGE_NO_WRITE_PERM_DEST;
2496 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2498 g_free(metadata_path);
2503 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2508 same = (strcmp(fd->path, fd->change->dest) == 0);
2512 const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2513 if (!dest_ext) dest_ext = "";
2514 if (!options->file_filter.disable_file_extension_checks)
2516 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2518 ret |= CHANGE_WARN_CHANGED_EXT;
2519 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2525 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2527 ret |= CHANGE_WARN_SAME;
2528 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2532 dest_dir = remove_level_from_path(fd->change->dest);
2534 if (!isdir(dest_dir))
2536 ret |= CHANGE_NO_DEST_DIR;
2537 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2539 else if (!access_file(dest_dir, W_OK))
2541 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2542 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2546 if (isfile(fd->change->dest))
2548 if (!access_file(fd->change->dest, W_OK))
2550 ret |= CHANGE_NO_WRITE_PERM_DEST;
2551 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2555 ret |= CHANGE_WARN_DEST_EXISTS;
2556 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2559 else if (isdir(fd->change->dest))
2561 ret |= CHANGE_DEST_EXISTS;
2562 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2569 /* During a rename operation, check if another planned destination file has
2572 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2573 fd->change->type == FILEDATA_CHANGE_COPY ||
2574 fd->change->type == FILEDATA_CHANGE_MOVE)
2581 if (fd1 != NULL && fd != fd1 )
2583 if (!strcmp(fd->change->dest, fd1->change->dest))
2585 ret |= CHANGE_DUPLICATE_DEST;
2591 fd->change->error = ret;
2592 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2599 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2604 ret = file_data_verify_ci(fd, list);
2606 work = fd->sidecar_files;
2609 FileData *sfd = work->data;
2611 ret |= file_data_verify_ci(sfd, list);
2618 gchar *file_data_get_error_string(gint error)
2620 GString *result = g_string_new("");
2622 if (error & CHANGE_NO_SRC)
2624 if (result->len > 0) g_string_append(result, ", ");
2625 g_string_append(result, _("file or directory does not exist"));
2628 if (error & CHANGE_DEST_EXISTS)
2630 if (result->len > 0) g_string_append(result, ", ");
2631 g_string_append(result, _("destination already exists"));
2634 if (error & CHANGE_NO_WRITE_PERM_DEST)
2636 if (result->len > 0) g_string_append(result, ", ");
2637 g_string_append(result, _("destination can't be overwritten"));
2640 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2642 if (result->len > 0) g_string_append(result, ", ");
2643 g_string_append(result, _("destination directory is not writable"));
2646 if (error & CHANGE_NO_DEST_DIR)
2648 if (result->len > 0) g_string_append(result, ", ");
2649 g_string_append(result, _("destination directory does not exist"));
2652 if (error & CHANGE_NO_WRITE_PERM_DIR)
2654 if (result->len > 0) g_string_append(result, ", ");
2655 g_string_append(result, _("source directory is not writable"));
2658 if (error & CHANGE_NO_READ_PERM)
2660 if (result->len > 0) g_string_append(result, ", ");
2661 g_string_append(result, _("no read permission"));
2664 if (error & CHANGE_WARN_NO_WRITE_PERM)
2666 if (result->len > 0) g_string_append(result, ", ");
2667 g_string_append(result, _("file is readonly"));
2670 if (error & CHANGE_WARN_DEST_EXISTS)
2672 if (result->len > 0) g_string_append(result, ", ");
2673 g_string_append(result, _("destination already exists and will be overwritten"));
2676 if (error & CHANGE_WARN_SAME)
2678 if (result->len > 0) g_string_append(result, ", ");
2679 g_string_append(result, _("source and destination are the same"));
2682 if (error & CHANGE_WARN_CHANGED_EXT)
2684 if (result->len > 0) g_string_append(result, ", ");
2685 g_string_append(result, _("source and destination have different extension"));
2688 if (error & CHANGE_WARN_UNSAVED_META)
2690 if (result->len > 0) g_string_append(result, ", ");
2691 g_string_append(result, _("there are unsaved metadata changes for the file"));
2694 if (error & CHANGE_DUPLICATE_DEST)
2696 if (result->len > 0) g_string_append(result, ", ");
2697 g_string_append(result, _("another destination file has the same filename"));
2700 return g_string_free(result, FALSE);
2703 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2706 gint all_errors = 0;
2707 gint common_errors = ~0;
2712 if (!list) return 0;
2714 num = g_list_length(list);
2715 errors = g_new(int, num);
2726 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2727 all_errors |= error;
2728 common_errors &= error;
2735 if (desc && all_errors)
2738 GString *result = g_string_new("");
2742 gchar *str = file_data_get_error_string(common_errors);
2743 g_string_append(result, str);
2744 g_string_append(result, "\n");
2758 error = errors[i] & ~common_errors;
2762 gchar *str = file_data_get_error_string(error);
2763 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2768 *desc = g_string_free(result, FALSE);
2777 * perform the change described by FileFataChangeInfo
2778 * it is used for internal operations,
2779 * this function actually operates with files on the filesystem
2780 * it should implement safe delete
2783 static gboolean file_data_perform_move(FileData *fd)
2785 g_assert(!strcmp(fd->change->source, fd->path));
2786 return move_file(fd->change->source, fd->change->dest);
2789 static gboolean file_data_perform_copy(FileData *fd)
2791 g_assert(!strcmp(fd->change->source, fd->path));
2792 return copy_file(fd->change->source, fd->change->dest);
2795 static gboolean file_data_perform_delete(FileData *fd)
2797 if (isdir(fd->path) && !islink(fd->path))
2798 return rmdir_utf8(fd->path);
2800 if (options->file_ops.safe_delete_enable)
2801 return file_util_safe_unlink(fd->path);
2803 return unlink_file(fd->path);
2806 gboolean file_data_perform_ci(FileData *fd)
2808 FileDataChangeType type = fd->change->type;
2812 case FILEDATA_CHANGE_MOVE:
2813 return file_data_perform_move(fd);
2814 case FILEDATA_CHANGE_COPY:
2815 return file_data_perform_copy(fd);
2816 case FILEDATA_CHANGE_RENAME:
2817 return file_data_perform_move(fd); /* the same as move */
2818 case FILEDATA_CHANGE_DELETE:
2819 return file_data_perform_delete(fd);
2820 case FILEDATA_CHANGE_WRITE_METADATA:
2821 return metadata_write_perform(fd);
2822 case FILEDATA_CHANGE_UNSPECIFIED:
2823 /* nothing to do here */
2831 gboolean file_data_sc_perform_ci(FileData *fd)
2834 gboolean ret = TRUE;
2835 FileDataChangeType type = fd->change->type;
2837 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2839 work = fd->sidecar_files;
2842 FileData *sfd = work->data;
2844 if (!file_data_perform_ci(sfd)) ret = FALSE;
2848 if (!file_data_perform_ci(fd)) ret = FALSE;
2854 * updates FileData structure according to FileDataChangeInfo
2857 gboolean file_data_apply_ci(FileData *fd)
2859 FileDataChangeType type = fd->change->type;
2862 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2864 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2865 file_data_planned_change_remove(fd);
2867 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2869 /* this change overwrites another file which is already known to other modules
2870 renaming fd would create duplicate FileData structure
2871 the best thing we can do is nothing
2872 FIXME: maybe we could copy stuff like marks
2874 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2878 file_data_set_path(fd, fd->change->dest);
2881 file_data_increment_version(fd);
2882 file_data_send_notification(fd, NOTIFY_CHANGE);
2887 gboolean file_data_sc_apply_ci(FileData *fd)
2890 FileDataChangeType type = fd->change->type;
2892 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2894 work = fd->sidecar_files;
2897 FileData *sfd = work->data;
2899 file_data_apply_ci(sfd);
2903 file_data_apply_ci(fd);
2908 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2911 if (fd->parent) fd = fd->parent;
2912 if (!g_list_find(list, fd)) return FALSE;
2914 work = fd->sidecar_files;
2917 if (!g_list_find(list, work->data)) return FALSE;
2923 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2928 /* change partial groups to independent files */
2933 FileData *fd = work->data;
2936 if (!file_data_list_contains_whole_group(list, fd))
2938 file_data_disable_grouping(fd, TRUE);
2941 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2947 /* remove sidecars from the list,
2948 they can be still acessed via main_fd->sidecar_files */
2952 FileData *fd = work->data;
2956 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2958 out = g_list_prepend(out, file_data_ref(fd));
2962 filelist_free(list);
2963 out = g_list_reverse(out);
2973 * notify other modules about the change described by FileDataChangeInfo
2976 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2977 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2978 implementation in view_file_list.c */
2981 typedef struct _NotifyIdleData NotifyIdleData;
2983 struct _NotifyIdleData {
2989 typedef struct _NotifyData NotifyData;
2991 struct _NotifyData {
2992 FileDataNotifyFunc func;
2994 NotifyPriority priority;
2997 static GList *notify_func_list = NULL;
2999 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
3001 NotifyData *nda = (NotifyData *)a;
3002 NotifyData *ndb = (NotifyData *)b;
3004 if (nda->priority < ndb->priority) return -1;
3005 if (nda->priority > ndb->priority) return 1;
3009 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
3012 GList *work = notify_func_list;
3016 NotifyData *nd = (NotifyData *)work->data;
3018 if (nd->func == func && nd->data == data)
3020 g_warning("Notify func already registered");
3026 nd = g_new(NotifyData, 1);
3029 nd->priority = priority;
3031 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
3032 DEBUG_2("Notify func registered: %p", nd);
3037 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
3039 GList *work = notify_func_list;
3043 NotifyData *nd = (NotifyData *)work->data;
3045 if (nd->func == func && nd->data == data)
3047 notify_func_list = g_list_delete_link(notify_func_list, work);
3049 DEBUG_2("Notify func unregistered: %p", nd);
3055 g_warning("Notify func not found");
3060 gboolean file_data_send_notification_idle_cb(gpointer data)
3062 NotifyIdleData *nid = (NotifyIdleData *)data;
3063 GList *work = notify_func_list;
3067 NotifyData *nd = (NotifyData *)work->data;
3069 nd->func(nid->fd, nid->type, nd->data);
3072 file_data_unref(nid->fd);
3077 void file_data_send_notification(FileData *fd, NotifyType type)
3079 GList *work = notify_func_list;
3083 NotifyData *nd = (NotifyData *)work->data;
3085 nd->func(fd, type, nd->data);
3089 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3090 nid->fd = file_data_ref(fd);
3092 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3096 static GHashTable *file_data_monitor_pool = NULL;
3097 static guint realtime_monitor_id = 0; /* event source id */
3099 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
3103 file_data_check_changed_files(fd);
3105 DEBUG_1("monitor %s", fd->path);
3108 static gboolean realtime_monitor_cb(gpointer data)
3110 if (!options->update_on_time_change) return TRUE;
3111 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
3115 gboolean file_data_register_real_time_monitor(FileData *fd)
3121 if (!file_data_monitor_pool)
3122 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3124 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3126 DEBUG_1("Register realtime %d %s", count, fd->path);
3129 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3131 if (!realtime_monitor_id)
3133 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
3139 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3143 g_assert(file_data_monitor_pool);
3145 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3147 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3149 g_assert(count > 0);
3154 g_hash_table_remove(file_data_monitor_pool, fd);
3156 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3158 file_data_unref(fd);
3160 if (g_hash_table_size(file_data_monitor_pool) == 0)
3162 g_source_remove(realtime_monitor_id);
3163 realtime_monitor_id = 0;
3171 *-----------------------------------------------------------------------------
3172 * Saving marks list, clearing marks
3173 * Uses file_data_pool
3174 *-----------------------------------------------------------------------------
3177 static void marks_get_files(gpointer key, gpointer value, gpointer userdata)
3179 gchar *file_name = key;
3180 GString *result = userdata;
3183 if (isfile(file_name))
3186 if (fd && fd->marks > 0)
3188 g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks);
3193 gboolean marks_list_load(const gchar *path)
3201 pathl = path_from_utf8(path);
3202 f = fopen(pathl, "r");
3204 if (!f) return FALSE;
3206 /* first line must start with Marks comment */
3207 if (!fgets(s_buf, sizeof(s_buf), f) ||
3208 strncmp(s_buf, "#Marks", 6) != 0)
3214 while (fgets(s_buf, sizeof(s_buf), f))
3216 if (s_buf[0]=='#') continue;
3217 file_path = strtok(s_buf, ",");
3218 marks_value = strtok(NULL, ",");
3219 if (isfile(file_path))
3221 FileData *fd = file_data_new_group(file_path);
3226 gint mark_no = 1 << n;
3227 if (atoi(marks_value) & mark_no)
3229 file_data_set_mark(fd, n , 1);
3240 gboolean marks_list_save(gchar *path, gboolean save)
3242 SecureSaveInfo *ssi;
3244 GString *marks = g_string_new("");
3246 pathl = path_from_utf8(path);
3247 ssi = secure_open(pathl);
3251 log_printf(_("Error: Unable to write marks lists to: %s\n"), path);
3255 secure_fprintf(ssi, "#Marks lists\n");
3259 g_hash_table_foreach(file_data_pool, marks_get_files, marks);
3261 secure_fprintf(ssi, "%s", marks->str);
3262 g_string_free(marks, FALSE);
3264 secure_fprintf(ssi, "#end\n");
3265 return (secure_close(ssi) == 0);
3268 static void marks_clear(gpointer key, gpointer value, gpointer userdata)
3270 gchar *file_name = key;
3275 if (isfile(file_name))
3278 if (fd && fd->marks > 0)
3284 if (fd->marks & mark_no)
3286 file_data_set_mark(fd, n , 0);
3294 void marks_clear_all()
3296 g_hash_table_foreach(file_data_pool, marks_clear, NULL);
3298 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */