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"
38 gint global_file_data_count = 0;
41 static GHashTable *file_data_pool = NULL;
42 static GHashTable *file_data_planned_change_hash = NULL;
44 static gint sidecar_file_priority(const gchar *extension);
45 static void file_data_check_sidecars(const GList *basename_list);
46 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
49 static SortType filelist_sort_method = SORT_NONE;
50 static gboolean filelist_sort_ascend = TRUE;
53 *-----------------------------------------------------------------------------
54 * text conversion utils
55 *-----------------------------------------------------------------------------
58 gchar *text_from_size(gint64 size)
64 /* what I would like to use is printf("%'d", size)
65 * BUT: not supported on every libc :(
69 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
70 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
74 a = g_strdup_printf("%d", (guint)size);
80 b = g_new(gchar, l + n + 1);
105 gchar *text_from_size_abrev(gint64 size)
107 if (size < (gint64)1024)
109 return g_strdup_printf(_("%d bytes"), (gint)size);
111 if (size < (gint64)1048576)
113 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
115 if (size < (gint64)1073741824)
117 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
120 /* to avoid overflowing the gdouble, do division in two steps */
122 return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
125 /* note: returned string is valid until next call to text_from_time() */
126 const gchar *text_from_time(time_t t)
128 static gchar *ret = NULL;
132 GError *error = NULL;
134 btime = localtime(&t);
136 /* the %x warning about 2 digit years is not an error */
137 buflen = strftime(buf, sizeof(buf), "%x %X", btime);
138 if (buflen < 1) return "";
141 ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
144 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
153 *-----------------------------------------------------------------------------
154 * changed files detection and notification
155 *-----------------------------------------------------------------------------
158 void file_data_increment_version(FileData *fd)
164 fd->parent->version++;
165 fd->parent->valid_marks = 0;
169 static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st)
171 if (fd->size != st->st_size ||
172 fd->date != st->st_mtime)
174 fd->size = st->st_size;
175 fd->date = st->st_mtime;
176 fd->cdate = st->st_ctime;
177 fd->mode = st->st_mode;
178 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
179 fd->thumb_pixbuf = NULL;
180 file_data_increment_version(fd);
181 file_data_send_notification(fd, NOTIFY_REREAD);
187 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
189 gboolean ret = FALSE;
192 ret = file_data_check_changed_single_file(fd, st);
194 work = fd->sidecar_files;
197 FileData *sfd = work->data;
201 if (!stat_utf8(sfd->path, &st))
206 file_data_disconnect_sidecar_file(fd, sfd);
208 file_data_increment_version(sfd);
209 file_data_send_notification(sfd, NOTIFY_REREAD);
210 file_data_unref(sfd);
214 ret |= file_data_check_changed_files_recursive(sfd, &st);
220 gboolean file_data_check_changed_files(FileData *fd)
222 gboolean ret = FALSE;
225 if (fd->parent) fd = fd->parent;
227 if (!stat_utf8(fd->path, &st))
231 FileData *sfd = NULL;
233 /* parent is missing, we have to rebuild whole group */
238 /* file_data_disconnect_sidecar_file might delete the file,
239 we have to keep the reference to prevent this */
240 sidecars = filelist_copy(fd->sidecar_files);
248 file_data_disconnect_sidecar_file(fd, sfd);
250 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
251 /* now we can release the sidecars */
252 filelist_free(sidecars);
253 file_data_increment_version(fd);
254 file_data_send_notification(fd, NOTIFY_REREAD);
259 ret |= file_data_check_changed_files_recursive(fd, &st);
266 *-----------------------------------------------------------------------------
267 * file name, extension, sorting, ...
268 *-----------------------------------------------------------------------------
271 static void file_data_set_collate_keys(FileData *fd)
273 gchar *caseless_name;
276 valid_name = g_filename_display_name(fd->name);
277 caseless_name = g_utf8_casefold(valid_name, -1);
279 g_free(fd->collate_key_name);
280 g_free(fd->collate_key_name_nocase);
282 #if GTK_CHECK_VERSION(2, 8, 0)
283 if (options->file_sort.natural)
285 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
286 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
290 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
291 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
294 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
295 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
299 g_free(caseless_name);
302 static void file_data_set_path(FileData *fd, const gchar *path)
304 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
305 g_assert(file_data_pool);
309 if (fd->original_path)
311 g_hash_table_remove(file_data_pool, fd->original_path);
312 g_free(fd->original_path);
315 g_assert(!g_hash_table_lookup(file_data_pool, path));
317 fd->original_path = g_strdup(path);
318 g_hash_table_insert(file_data_pool, fd->original_path, fd);
320 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
322 fd->path = g_strdup(path);
324 fd->extension = fd->name + 1;
325 file_data_set_collate_keys(fd);
329 fd->path = g_strdup(path);
330 fd->name = filename_from_path(fd->path);
332 if (strcmp(fd->name, "..") == 0)
334 gchar *dir = remove_level_from_path(path);
336 fd->path = remove_level_from_path(dir);
339 fd->extension = fd->name + 2;
340 file_data_set_collate_keys(fd);
343 else if (strcmp(fd->name, ".") == 0)
346 fd->path = remove_level_from_path(path);
348 fd->extension = fd->name + 1;
349 file_data_set_collate_keys(fd);
353 fd->extension = registered_extension_from_path(fd->path);
354 if (fd->extension == NULL)
356 fd->extension = fd->name + strlen(fd->name);
359 fd->sidecar_priority = sidecar_file_priority(fd->extension);
360 file_data_set_collate_keys(fd);
364 *-----------------------------------------------------------------------------
365 * create or reuse Filedata
366 *-----------------------------------------------------------------------------
369 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
373 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
375 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
378 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
380 fd = g_hash_table_lookup(file_data_pool, path_utf8);
386 if (!fd && file_data_planned_change_hash)
388 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
391 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
392 if (!isfile(fd->path))
395 file_data_apply_ci(fd);
408 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
411 changed = file_data_check_changed_single_file(fd, st);
413 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
418 fd = g_new0(FileData, 1);
419 #ifdef DEBUG_FILEDATA
420 global_file_data_count++;
421 DEBUG_2("file data count++: %d", global_file_data_count);
424 fd->size = st->st_size;
425 fd->date = st->st_mtime;
426 fd->cdate = st->st_ctime;
427 fd->mode = st->st_mode;
429 fd->magick = FD_MAGICK;
432 fd->format_class = filter_file_get_class(path_utf8);
434 if (disable_sidecars) fd->disable_grouping = TRUE;
436 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
441 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
443 gchar *path_utf8 = path_to_utf8(path);
444 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
450 FileData *file_data_new_simple(const gchar *path_utf8)
455 if (!stat_utf8(path_utf8, &st))
461 fd = g_hash_table_lookup(file_data_pool, path_utf8);
462 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
471 void read_exif_time_data(FileData *file)
473 if (file->exifdate > 0)
475 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
479 file->exif = exif_read_fd(file);
483 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
484 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), file, file->path);
489 uint year, month, day, hour, min, sec;
491 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
492 time_str.tm_year = year - 1900;
493 time_str.tm_mon = month - 1;
494 time_str.tm_mday = day;
495 time_str.tm_hour = hour;
496 time_str.tm_min = min;
497 time_str.tm_sec = sec;
498 time_str.tm_isdst = 0;
500 file->exifdate = mktime(&time_str);
506 void read_exif_time_digitized_data(FileData *file)
508 if (file->exifdate > 0)
510 DEBUG_1("%s set_exif_time_digitized_data: Already exists for %s", get_exec_time(), file->path);
514 file->exif = exif_read_fd(file);
518 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeDigitized");
519 DEBUG_2("%s set_exif_time_digitized_data: reading %p %s", get_exec_time(), file, file->path);
524 uint year, month, day, hour, min, sec;
526 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
527 time_str.tm_year = year - 1900;
528 time_str.tm_mon = month - 1;
529 time_str.tm_mday = day;
530 time_str.tm_hour = hour;
531 time_str.tm_min = min;
532 time_str.tm_sec = sec;
533 time_str.tm_isdst = 0;
535 file->exifdate_digitized = mktime(&time_str);
541 void set_exif_time_data(GList *files)
543 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
547 FileData *file = files->data;
549 read_exif_time_data(file);
554 void set_exif_time_digitized_data(GList *files)
556 DEBUG_1("%s set_exif_time_digitized_data: ...", get_exec_time());
560 FileData *file = files->data;
562 read_exif_time_digitized_data(file);
567 void set_rating_data(GList *files)
570 DEBUG_1("%s set_rating_data: ...", get_exec_time());
574 FileData *file = files->data;
575 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
578 file->rating = atoi(rating_str);
585 FileData *file_data_new_no_grouping(const gchar *path_utf8)
589 if (!stat_utf8(path_utf8, &st))
595 return file_data_new(path_utf8, &st, TRUE);
598 FileData *file_data_new_dir(const gchar *path_utf8)
602 if (!stat_utf8(path_utf8, &st))
608 /* dir or non-existing yet */
609 g_assert(S_ISDIR(st.st_mode));
611 return file_data_new(path_utf8, &st, TRUE);
615 *-----------------------------------------------------------------------------
617 *-----------------------------------------------------------------------------
620 #ifdef DEBUG_FILEDATA
621 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
623 FileData *file_data_ref(FileData *fd)
626 if (fd == NULL) return NULL;
627 if (fd->magick != FD_MAGICK)
628 #ifdef DEBUG_FILEDATA
629 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, fd);
631 log_printf("Error: fd magick mismatch fd=%p", fd);
633 g_assert(fd->magick == FD_MAGICK);
636 #ifdef DEBUG_FILEDATA
637 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", fd, fd->ref, fd->path, file, line);
639 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
644 static void file_data_free(FileData *fd)
646 g_assert(fd->magick == FD_MAGICK);
647 g_assert(fd->ref == 0);
648 g_assert(!fd->locked);
650 #ifdef DEBUG_FILEDATA
651 global_file_data_count--;
652 DEBUG_2("file data count--: %d", global_file_data_count);
655 metadata_cache_free(fd);
656 g_hash_table_remove(file_data_pool, fd->original_path);
659 g_free(fd->original_path);
660 g_free(fd->collate_key_name);
661 g_free(fd->collate_key_name_nocase);
662 g_free(fd->extended_extension);
663 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
664 histmap_free(fd->histmap);
666 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
668 file_data_change_info_free(NULL, fd);
673 * \brief Checks if the FileData is referenced
675 * Checks the refcount and whether the FileData is locked.
677 static gboolean file_data_check_has_ref(FileData *fd)
679 return fd->ref > 0 || fd->locked;
683 * \brief Consider freeing a FileData.
685 * This function will free a FileData and its children provided that neither its parent nor it has
686 * a positive refcount, and provided that neither is locked.
688 static void file_data_consider_free(FileData *fd)
691 FileData *parent = fd->parent ? fd->parent : fd;
693 g_assert(fd->magick == FD_MAGICK);
694 if (file_data_check_has_ref(fd)) return;
695 if (file_data_check_has_ref(parent)) return;
697 work = parent->sidecar_files;
700 FileData *sfd = work->data;
701 if (file_data_check_has_ref(sfd)) return;
705 /* Neither the parent nor the siblings are referenced, so we can free everything */
706 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
707 fd->path, fd->parent ? parent->path : "-");
709 work = parent->sidecar_files;
712 FileData *sfd = work->data;
717 g_list_free(parent->sidecar_files);
718 parent->sidecar_files = NULL;
720 file_data_free(parent);
723 #ifdef DEBUG_FILEDATA
724 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
726 void file_data_unref(FileData *fd)
729 if (fd == NULL) return;
730 if (fd->magick != FD_MAGICK)
731 #ifdef DEBUG_FILEDATA
732 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, fd);
734 log_printf("Error: fd magick mismatch fd=%p", fd);
736 g_assert(fd->magick == FD_MAGICK);
739 #ifdef DEBUG_FILEDATA
740 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", fd, fd->ref, fd->locked, fd->path,
743 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
746 // Free FileData if it's no longer ref'd
747 file_data_consider_free(fd);
751 * \brief Lock the FileData in memory.
753 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
754 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
755 * even if the code would continue to function properly even if the FileData were freed. Code that
756 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
758 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
759 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
761 void file_data_lock(FileData *fd)
763 if (fd == NULL) return;
764 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", fd);
766 g_assert(fd->magick == FD_MAGICK);
769 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
773 * \brief Reset the maintain-FileData-in-memory lock
775 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
776 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
777 * keeping it from being freed.
779 void file_data_unlock(FileData *fd)
781 if (fd == NULL) return;
782 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", fd);
784 g_assert(fd->magick == FD_MAGICK);
787 // Free FileData if it's no longer ref'd
788 file_data_consider_free(fd);
792 * \brief Lock all of the FileDatas in the provided list
794 * \see file_data_lock(FileData)
796 void file_data_lock_list(GList *list)
803 FileData *fd = work->data;
810 * \brief Unlock all of the FileDatas in the provided list
812 * \see file_data_unlock(FileData)
814 void file_data_unlock_list(GList *list)
821 FileData *fd = work->data;
823 file_data_unlock(fd);
828 *-----------------------------------------------------------------------------
829 * sidecar file info struct
830 *-----------------------------------------------------------------------------
833 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
835 const FileData *fda = a;
836 const FileData *fdb = b;
838 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
839 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
841 return strcmp(fdb->extension, fda->extension);
845 static gint sidecar_file_priority(const gchar *extension)
850 if (extension == NULL)
853 work = sidecar_ext_get_list();
856 gchar *ext = work->data;
859 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
865 static void file_data_check_sidecars(const GList *basename_list)
867 /* basename_list contains the new group - first is the parent, then sorted sidecars */
868 /* all files in the list have ref count > 0 */
871 GList *s_work, *new_sidecars;
874 if (!basename_list) return;
877 DEBUG_2("basename start");
878 work = basename_list;
881 FileData *fd = work->data;
883 g_assert(fd->magick == FD_MAGICK);
884 DEBUG_2("basename: %p %s", fd, fd->name);
887 g_assert(fd->parent->magick == FD_MAGICK);
888 DEBUG_2(" parent: %p", fd->parent);
890 s_work = fd->sidecar_files;
893 FileData *sfd = s_work->data;
894 s_work = s_work->next;
895 g_assert(sfd->magick == FD_MAGICK);
896 DEBUG_2(" sidecar: %p %s", sfd, sfd->name);
899 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
902 parent_fd = basename_list->data;
904 /* check if the second and next entries of basename_list are already connected
905 as sidecars of the first entry (parent_fd) */
906 work = basename_list->next;
907 s_work = parent_fd->sidecar_files;
909 while (work && s_work)
911 if (work->data != s_work->data) break;
913 s_work = s_work->next;
916 if (!work && !s_work)
918 DEBUG_2("basename no change");
919 return; /* no change in grouping */
922 /* we have to regroup it */
924 /* first, disconnect everything and send notification*/
926 work = basename_list;
929 FileData *fd = work->data;
931 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
935 FileData *old_parent = fd->parent;
936 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
937 file_data_ref(old_parent);
938 file_data_disconnect_sidecar_file(old_parent, fd);
939 file_data_send_notification(old_parent, NOTIFY_REREAD);
940 file_data_unref(old_parent);
943 while (fd->sidecar_files)
945 FileData *sfd = fd->sidecar_files->data;
946 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
948 file_data_disconnect_sidecar_file(fd, sfd);
949 file_data_send_notification(sfd, NOTIFY_REREAD);
950 file_data_unref(sfd);
952 file_data_send_notification(fd, NOTIFY_GROUPING);
954 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
957 /* now we can form the new group */
958 work = basename_list->next;
962 FileData *sfd = work->data;
963 g_assert(sfd->magick == FD_MAGICK);
964 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
965 sfd->parent = parent_fd;
966 new_sidecars = g_list_prepend(new_sidecars, sfd);
969 g_assert(parent_fd->sidecar_files == NULL);
970 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
971 DEBUG_1("basename group changed for %s", parent_fd->path);
975 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
977 g_assert(target->magick == FD_MAGICK);
978 g_assert(sfd->magick == FD_MAGICK);
979 g_assert(g_list_find(target->sidecar_files, sfd));
981 file_data_ref(target);
984 g_assert(sfd->parent == target);
986 file_data_increment_version(sfd); /* increments both sfd and target */
988 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
990 g_free(sfd->extended_extension);
991 sfd->extended_extension = NULL;
993 file_data_unref(target);
994 file_data_unref(sfd);
997 /* disables / enables grouping for particular file, sends UPDATE notification */
998 void file_data_disable_grouping(FileData *fd, gboolean disable)
1000 if (!fd->disable_grouping == !disable) return;
1002 fd->disable_grouping = !!disable;
1008 FileData *parent = file_data_ref(fd->parent);
1009 file_data_disconnect_sidecar_file(parent, fd);
1010 file_data_send_notification(parent, NOTIFY_GROUPING);
1011 file_data_unref(parent);
1013 else if (fd->sidecar_files)
1015 GList *sidecar_files = filelist_copy(fd->sidecar_files);
1016 GList *work = sidecar_files;
1019 FileData *sfd = work->data;
1021 file_data_disconnect_sidecar_file(fd, sfd);
1022 file_data_send_notification(sfd, NOTIFY_GROUPING);
1024 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
1025 filelist_free(sidecar_files);
1029 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
1034 file_data_increment_version(fd);
1035 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
1037 file_data_send_notification(fd, NOTIFY_GROUPING);
1040 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
1047 FileData *fd = work->data;
1049 file_data_disable_grouping(fd, disable);
1057 *-----------------------------------------------------------------------------
1059 *-----------------------------------------------------------------------------
1063 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
1066 if (!filelist_sort_ascend)
1073 switch (filelist_sort_method)
1078 if (fa->size < fb->size) return -1;
1079 if (fa->size > fb->size) return 1;
1080 /* fall back to name */
1083 if (fa->date < fb->date) return -1;
1084 if (fa->date > fb->date) return 1;
1085 /* fall back to name */
1088 if (fa->cdate < fb->cdate) return -1;
1089 if (fa->cdate > fb->cdate) return 1;
1090 /* fall back to name */
1093 if (fa->exifdate < fb->exifdate) return -1;
1094 if (fa->exifdate > fb->exifdate) return 1;
1095 /* fall back to name */
1097 case SORT_EXIFTIMEDIGITIZED:
1098 if (fa->exifdate_digitized < fb->exifdate_digitized) return -1;
1099 if (fa->exifdate_digitized > fb->exifdate_digitized) return 1;
1100 /* fall back to name */
1103 if (fa->rating < fb->rating) return -1;
1104 if (fa->rating > fb->rating) return 1;
1105 /* fall back to name */
1108 if (fa->format_class < fb->format_class) return -1;
1109 if (fa->format_class > fb->format_class) return 1;
1110 /* fall back to name */
1112 #ifdef HAVE_STRVERSCMP
1114 ret = strverscmp(fa->name, fb->name);
1115 if (ret != 0) return ret;
1122 if (options->file_sort.case_sensitive)
1123 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1125 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1127 if (ret != 0) return ret;
1129 /* do not return 0 unless the files are really the same
1130 file_data_pool ensures that original_path is unique
1132 return strcmp(fa->original_path, fb->original_path);
1135 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1137 filelist_sort_method = method;
1138 filelist_sort_ascend = ascend;
1139 return filelist_sort_compare_filedata(fa, fb);
1142 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1144 return filelist_sort_compare_filedata(a, b);
1147 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1149 filelist_sort_method = method;
1150 filelist_sort_ascend = ascend;
1151 return g_list_sort(list, cb);
1154 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1156 filelist_sort_method = method;
1157 filelist_sort_ascend = ascend;
1158 return g_list_insert_sorted(list, data, cb);
1161 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1163 if (method == SORT_EXIFTIME)
1165 set_exif_time_data(list);
1167 if (method == SORT_EXIFTIMEDIGITIZED)
1169 set_exif_time_digitized_data(list);
1171 if (method == SORT_RATING)
1173 set_rating_data(list);
1175 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1178 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
1180 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1184 *-----------------------------------------------------------------------------
1185 * basename hash - grouping of sidecars in filelist
1186 *-----------------------------------------------------------------------------
1190 static GHashTable *file_data_basename_hash_new(void)
1192 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1195 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1198 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1200 list = g_hash_table_lookup(basename_hash, basename);
1204 DEBUG_1("TG: basename_hash not found for %s",fd->path);
1205 const gchar *parent_extension = registered_extension_from_path(basename);
1207 if (parent_extension)
1209 DEBUG_1("TG: parent extension %s",parent_extension);
1210 gchar *parent_basename = g_strndup(basename, parent_extension - basename);
1211 DEBUG_1("TG: parent basename %s",parent_basename);
1212 FileData *parent_fd = g_hash_table_lookup(file_data_pool, basename);
1215 DEBUG_1("TG: parent fd found");
1216 list = g_hash_table_lookup(basename_hash, parent_basename);
1217 if (!g_list_find(list, parent_fd))
1219 DEBUG_1("TG: parent fd doesn't fit");
1220 g_free(parent_basename);
1226 basename = parent_basename;
1227 fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
1233 if (!g_list_find(list, fd))
1235 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1236 g_hash_table_insert(basename_hash, basename, list);
1245 static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
1247 file_data_basename_hash_insert((GHashTable *)basename_hash, (FileData *)fd);
1250 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
1252 filelist_free((GList *)value);
1255 static void file_data_basename_hash_free(GHashTable *basename_hash)
1257 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1258 g_hash_table_destroy(basename_hash);
1262 *-----------------------------------------------------------------------------
1263 * handling sidecars in filelist
1264 *-----------------------------------------------------------------------------
1267 static GList *filelist_filter_out_sidecars(GList *flist)
1269 GList *work = flist;
1270 GList *flist_filtered = NULL;
1274 FileData *fd = work->data;
1277 if (fd->parent) /* remove fd's that are children */
1278 file_data_unref(fd);
1280 flist_filtered = g_list_prepend(flist_filtered, fd);
1284 return flist_filtered;
1287 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
1289 GList *basename_list = (GList *)value;
1290 file_data_check_sidecars(basename_list);
1294 static gboolean is_hidden_file(const gchar *name)
1296 if (name[0] != '.') return FALSE;
1297 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1302 *-----------------------------------------------------------------------------
1303 * the main filelist function
1304 *-----------------------------------------------------------------------------
1307 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1312 GList *dlist = NULL;
1313 GList *flist = NULL;
1314 GList *xmp_files = NULL;
1315 gint (*stat_func)(const gchar *path, struct stat *buf);
1316 GHashTable *basename_hash = NULL;
1318 g_assert(files || dirs);
1320 if (files) *files = NULL;
1321 if (dirs) *dirs = NULL;
1323 pathl = path_from_utf8(dir_path);
1324 if (!pathl) return FALSE;
1326 dp = opendir(pathl);
1333 if (files) basename_hash = file_data_basename_hash_new();
1335 if (follow_symlinks)
1340 while ((dir = readdir(dp)) != NULL)
1342 struct stat ent_sbuf;
1343 const gchar *name = dir->d_name;
1346 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1349 filepath = g_build_filename(pathl, name, NULL);
1350 if (stat_func(filepath, &ent_sbuf) >= 0)
1352 if (S_ISDIR(ent_sbuf.st_mode))
1354 /* we ignore the .thumbnails dir for cleanliness */
1356 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1357 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1358 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1359 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1361 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1366 if (files && filter_name_exists(name))
1368 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1369 flist = g_list_prepend(flist, fd);
1370 if (fd->sidecar_priority && !fd->disable_grouping)
1372 if (strcmp(fd->extension, ".xmp") != 0)
1373 file_data_basename_hash_insert(basename_hash, fd);
1375 xmp_files = g_list_append(xmp_files, fd);
1382 if (errno == EOVERFLOW)
1384 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1396 g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
1397 g_list_free(xmp_files);
1400 if (dirs) *dirs = dlist;
1404 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1406 *files = filelist_filter_out_sidecars(flist);
1408 if (basename_hash) file_data_basename_hash_free(basename_hash);
1413 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1415 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1418 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1420 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1423 FileData *file_data_new_group(const gchar *path_utf8)
1430 if (!stat_utf8(path_utf8, &st))
1436 if (S_ISDIR(st.st_mode))
1437 return file_data_new(path_utf8, &st, TRUE);
1439 dir = remove_level_from_path(path_utf8);
1441 filelist_read_real(dir, &files, NULL, TRUE);
1443 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1444 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1450 filelist_free(files);
1456 void filelist_free(GList *list)
1463 file_data_unref((FileData *)work->data);
1471 GList *filelist_copy(GList *list)
1473 GList *new_list = NULL;
1484 new_list = g_list_prepend(new_list, file_data_ref(fd));
1487 return g_list_reverse(new_list);
1490 GList *filelist_from_path_list(GList *list)
1492 GList *new_list = NULL;
1503 new_list = g_list_prepend(new_list, file_data_new_group(path));
1506 return g_list_reverse(new_list);
1509 GList *filelist_to_path_list(GList *list)
1511 GList *new_list = NULL;
1522 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1525 return g_list_reverse(new_list);
1528 GList *filelist_filter(GList *list, gboolean is_dir_list)
1532 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1537 FileData *fd = (FileData *)(work->data);
1538 const gchar *name = fd->name;
1540 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1541 (!is_dir_list && !filter_name_exists(name)) ||
1542 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1543 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1547 list = g_list_remove_link(list, link);
1548 file_data_unref(fd);
1559 *-----------------------------------------------------------------------------
1560 * filelist recursive
1561 *-----------------------------------------------------------------------------
1564 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1566 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1569 GList *filelist_sort_path(GList *list)
1571 return g_list_sort(list, filelist_sort_path_cb);
1574 static void filelist_recursive_append(GList **list, GList *dirs)
1581 FileData *fd = (FileData *)(work->data);
1585 if (filelist_read(fd, &f, &d))
1587 f = filelist_filter(f, FALSE);
1588 f = filelist_sort_path(f);
1589 *list = g_list_concat(*list, f);
1591 d = filelist_filter(d, TRUE);
1592 d = filelist_sort_path(d);
1593 filelist_recursive_append(list, d);
1601 GList *filelist_recursive(FileData *dir_fd)
1606 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1607 list = filelist_filter(list, FALSE);
1608 list = filelist_sort_path(list);
1610 d = filelist_filter(d, TRUE);
1611 d = filelist_sort_path(d);
1612 filelist_recursive_append(&list, d);
1619 *-----------------------------------------------------------------------------
1620 * file modification support
1621 *-----------------------------------------------------------------------------
1625 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1627 if (!fdci && fd) fdci = fd->change;
1631 g_free(fdci->source);
1636 if (fd) fd->change = NULL;
1639 static gboolean file_data_can_write_directly(FileData *fd)
1641 return filter_name_is_writable(fd->extension);
1644 static gboolean file_data_can_write_sidecar(FileData *fd)
1646 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1649 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1651 gchar *sidecar_path = NULL;
1654 if (!file_data_can_write_sidecar(fd)) return NULL;
1656 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1657 gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1660 FileData *sfd = work->data;
1662 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1664 sidecar_path = g_strdup(sfd->path);
1668 g_free(extended_extension);
1670 if (!existing_only && !sidecar_path)
1672 if (options->metadata.sidecar_extended_name)
1673 sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1676 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1677 sidecar_path = g_strconcat(base, ".xmp", NULL);
1682 return sidecar_path;
1686 * marks and orientation
1689 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1690 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1691 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1692 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1694 gboolean file_data_get_mark(FileData *fd, gint n)
1696 gboolean valid = (fd->valid_marks & (1 << n));
1698 if (file_data_get_mark_func[n] && !valid)
1700 guint old = fd->marks;
1701 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1703 if (!value != !(fd->marks & (1 << n)))
1705 fd->marks = fd->marks ^ (1 << n);
1708 fd->valid_marks |= (1 << n);
1709 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1711 file_data_unref(fd);
1713 else if (!old && fd->marks)
1719 return !!(fd->marks & (1 << n));
1722 guint file_data_get_marks(FileData *fd)
1725 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1729 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1732 if (!value == !file_data_get_mark(fd, n)) return;
1734 if (file_data_set_mark_func[n])
1736 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1741 fd->marks = fd->marks ^ (1 << n);
1743 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1745 file_data_unref(fd);
1747 else if (!old && fd->marks)
1752 file_data_increment_version(fd);
1753 file_data_send_notification(fd, NOTIFY_MARKS);
1756 gboolean file_data_filter_marks(FileData *fd, guint filter)
1759 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1760 return ((fd->marks & filter) == filter);
1763 GList *file_data_filter_marks_list(GList *list, guint filter)
1770 FileData *fd = work->data;
1774 if (!file_data_filter_marks(fd, filter))
1776 list = g_list_remove_link(list, link);
1777 file_data_unref(fd);
1785 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1787 FileData *fd = value;
1788 file_data_increment_version(fd);
1789 file_data_send_notification(fd, NOTIFY_MARKS);
1792 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1794 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1796 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1798 file_data_get_mark_func[n] = get_mark_func;
1799 file_data_set_mark_func[n] = set_mark_func;
1800 file_data_mark_func_data[n] = data;
1801 file_data_destroy_mark_func[n] = notify;
1803 if (get_mark_func && file_data_pool)
1805 /* this effectively changes all known files */
1806 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1812 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1814 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1815 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1816 if (data) *data = file_data_mark_func_data[n];
1819 gint file_data_get_user_orientation(FileData *fd)
1821 return fd->user_orientation;
1824 void file_data_set_user_orientation(FileData *fd, gint value)
1826 if (fd->user_orientation == value) return;
1828 fd->user_orientation = value;
1829 file_data_increment_version(fd);
1830 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1835 * file_data - operates on the given fd
1836 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1840 /* return list of sidecar file extensions in a string */
1841 gchar *file_data_sc_list_to_string(FileData *fd)
1844 GString *result = g_string_new("");
1846 work = fd->sidecar_files;
1849 FileData *sfd = work->data;
1851 result = g_string_append(result, "+ ");
1852 result = g_string_append(result, sfd->extension);
1854 if (work) result = g_string_append_c(result, ' ');
1857 return g_string_free(result, FALSE);
1863 * add FileDataChangeInfo (see typedefs.h) for the given operation
1864 * uses file_data_add_change_info
1866 * fails if the fd->change already exists - change operations can't run in parallel
1867 * fd->change_info works as a lock
1869 * dest can be NULL - in this case the current name is used for now, it will
1874 FileDataChangeInfo types:
1876 MOVE - path is changed, name may be changed too
1877 RENAME - path remains unchanged, name is changed
1878 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1879 sidecar names are changed too, extensions are not changed
1881 UPDATE - file size, date or grouping has been changed
1884 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1886 FileDataChangeInfo *fdci;
1888 if (fd->change) return FALSE;
1890 fdci = g_new0(FileDataChangeInfo, 1);
1895 fdci->source = g_strdup(src);
1897 fdci->source = g_strdup(fd->path);
1900 fdci->dest = g_strdup(dest);
1907 static void file_data_planned_change_remove(FileData *fd)
1909 if (file_data_planned_change_hash &&
1910 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1912 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1914 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1915 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1916 file_data_unref(fd);
1917 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1919 g_hash_table_destroy(file_data_planned_change_hash);
1920 file_data_planned_change_hash = NULL;
1921 DEBUG_1("planned change: empty");
1928 void file_data_free_ci(FileData *fd)
1930 FileDataChangeInfo *fdci = fd->change;
1934 file_data_planned_change_remove(fd);
1936 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1938 g_free(fdci->source);
1946 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1948 FileDataChangeInfo *fdci = fd->change;
1950 fdci->regroup_when_finished = enable;
1953 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1957 if (fd->parent) fd = fd->parent;
1959 if (fd->change) return FALSE;
1961 work = fd->sidecar_files;
1964 FileData *sfd = work->data;
1966 if (sfd->change) return FALSE;
1970 file_data_add_ci(fd, type, NULL, NULL);
1972 work = fd->sidecar_files;
1975 FileData *sfd = work->data;
1977 file_data_add_ci(sfd, type, NULL, NULL);
1984 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1988 if (fd->parent) fd = fd->parent;
1990 if (!fd->change || fd->change->type != type) return FALSE;
1992 work = fd->sidecar_files;
1995 FileData *sfd = work->data;
1997 if (!sfd->change || sfd->change->type != type) return FALSE;
2005 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
2007 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
2008 file_data_sc_update_ci_copy(fd, dest_path);
2012 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
2014 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
2015 file_data_sc_update_ci_move(fd, dest_path);
2019 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
2021 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
2022 file_data_sc_update_ci_rename(fd, dest_path);
2026 gboolean file_data_sc_add_ci_delete(FileData *fd)
2028 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
2031 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
2033 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
2034 file_data_sc_update_ci_unspecified(fd, dest_path);
2038 gboolean file_data_add_ci_write_metadata(FileData *fd)
2040 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
2043 void file_data_sc_free_ci(FileData *fd)
2047 if (fd->parent) fd = fd->parent;
2049 file_data_free_ci(fd);
2051 work = fd->sidecar_files;
2054 FileData *sfd = work->data;
2056 file_data_free_ci(sfd);
2061 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2064 gboolean ret = TRUE;
2069 FileData *fd = work->data;
2071 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2078 static void file_data_sc_revert_ci_list(GList *fd_list)
2085 FileData *fd = work->data;
2087 file_data_sc_free_ci(fd);
2092 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2099 FileData *fd = work->data;
2101 if (!func(fd, dest))
2103 file_data_sc_revert_ci_list(work->prev);
2112 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2114 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2117 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2119 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2122 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2124 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2127 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2129 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2132 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2135 gboolean ret = TRUE;
2140 FileData *fd = work->data;
2142 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2149 void file_data_free_ci_list(GList *fd_list)
2156 FileData *fd = work->data;
2158 file_data_free_ci(fd);
2163 void file_data_sc_free_ci_list(GList *fd_list)
2170 FileData *fd = work->data;
2172 file_data_sc_free_ci(fd);
2178 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2179 * fails if fd->change does not exist or the change type does not match
2182 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2184 FileDataChangeType type = fd->change->type;
2186 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2190 if (!file_data_planned_change_hash)
2191 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2193 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2195 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2196 g_hash_table_remove(file_data_planned_change_hash, old_path);
2197 file_data_unref(fd);
2200 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
2205 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2206 g_hash_table_remove(file_data_planned_change_hash, new_path);
2207 file_data_unref(ofd);
2210 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2212 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2217 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2219 gchar *old_path = fd->change->dest;
2221 fd->change->dest = g_strdup(dest_path);
2222 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2226 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2228 const gchar *extension = registered_extension_from_path(fd->change->source);
2229 gchar *base = remove_extension_from_path(dest_path);
2230 gchar *old_path = fd->change->dest;
2232 fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2233 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2239 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2242 gchar *dest_path_full = NULL;
2244 if (fd->parent) fd = fd->parent;
2248 dest_path = fd->path;
2250 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2252 gchar *dir = remove_level_from_path(fd->path);
2254 dest_path_full = g_build_filename(dir, dest_path, NULL);
2256 dest_path = dest_path_full;
2258 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2260 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2261 dest_path = dest_path_full;
2264 file_data_update_ci_dest(fd, dest_path);
2266 work = fd->sidecar_files;
2269 FileData *sfd = work->data;
2271 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2275 g_free(dest_path_full);
2278 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2280 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2281 file_data_sc_update_ci(fd, dest_path);
2285 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2287 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2290 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2292 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2295 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2297 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2300 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2302 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2305 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2307 gboolean (*func)(FileData *, const gchar *))
2310 gboolean ret = TRUE;
2315 FileData *fd = work->data;
2317 if (!func(fd, dest)) ret = FALSE;
2324 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2326 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2329 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2331 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2334 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2336 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2341 * verify source and dest paths - dest image exists, etc.
2342 * it should detect all possible problems with the planned operation
2345 gint file_data_verify_ci(FileData *fd, GList *list)
2347 gint ret = CHANGE_OK;
2350 FileData *fd1 = NULL;
2354 DEBUG_1("Change checked: no change info: %s", fd->path);
2358 if (!isname(fd->path))
2360 /* this probably should not happen */
2361 ret |= CHANGE_NO_SRC;
2362 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2366 dir = remove_level_from_path(fd->path);
2368 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2369 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2370 fd->change->type != FILEDATA_CHANGE_RENAME &&
2371 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2374 ret |= CHANGE_WARN_UNSAVED_META;
2375 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2378 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2379 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2380 !access_file(fd->path, R_OK))
2382 ret |= CHANGE_NO_READ_PERM;
2383 DEBUG_1("Change checked: no read permission: %s", fd->path);
2385 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2386 !access_file(dir, W_OK))
2388 ret |= CHANGE_NO_WRITE_PERM_DIR;
2389 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2391 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2392 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2393 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2394 !access_file(fd->path, W_OK))
2396 ret |= CHANGE_WARN_NO_WRITE_PERM;
2397 DEBUG_1("Change checked: no write permission: %s", fd->path);
2399 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2400 - that means that there are no hard errors and warnings can be disabled
2401 - the destination is determined during the check
2403 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2405 /* determine destination file */
2406 gboolean have_dest = FALSE;
2407 gchar *dest_dir = NULL;
2409 if (options->metadata.save_in_image_file)
2411 if (file_data_can_write_directly(fd))
2413 /* we can write the file directly */
2414 if (access_file(fd->path, W_OK))
2420 if (options->metadata.warn_on_write_problems)
2422 ret |= CHANGE_WARN_NO_WRITE_PERM;
2423 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2427 else if (file_data_can_write_sidecar(fd))
2429 /* we can write sidecar */
2430 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2431 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2433 file_data_update_ci_dest(fd, sidecar);
2438 if (options->metadata.warn_on_write_problems)
2440 ret |= CHANGE_WARN_NO_WRITE_PERM;
2441 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2450 /* write private metadata file under ~/.geeqie */
2452 /* If an existing metadata file exists, we will try writing to
2453 * it's location regardless of the user's preference.
2455 gchar *metadata_path = NULL;
2457 /* but ignore XMP if we are not able to write it */
2458 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2460 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2462 if (metadata_path && !access_file(metadata_path, W_OK))
2464 g_free(metadata_path);
2465 metadata_path = NULL;
2472 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2473 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2475 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2477 metadata_path = g_build_filename(dest_dir, filename, NULL);
2481 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2483 file_data_update_ci_dest(fd, metadata_path);
2488 ret |= CHANGE_NO_WRITE_PERM_DEST;
2489 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2491 g_free(metadata_path);
2496 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2501 same = (strcmp(fd->path, fd->change->dest) == 0);
2505 const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2506 if (!dest_ext) dest_ext = "";
2507 if (!options->file_filter.disable_file_extension_checks)
2509 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2511 ret |= CHANGE_WARN_CHANGED_EXT;
2512 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2518 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2520 ret |= CHANGE_WARN_SAME;
2521 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2525 dest_dir = remove_level_from_path(fd->change->dest);
2527 if (!isdir(dest_dir))
2529 ret |= CHANGE_NO_DEST_DIR;
2530 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2532 else if (!access_file(dest_dir, W_OK))
2534 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2535 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2539 if (isfile(fd->change->dest))
2541 if (!access_file(fd->change->dest, W_OK))
2543 ret |= CHANGE_NO_WRITE_PERM_DEST;
2544 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2548 ret |= CHANGE_WARN_DEST_EXISTS;
2549 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2552 else if (isdir(fd->change->dest))
2554 ret |= CHANGE_DEST_EXISTS;
2555 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2562 /* During a rename operation, check if another planned destination file has
2565 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2566 fd->change->type == FILEDATA_CHANGE_COPY ||
2567 fd->change->type == FILEDATA_CHANGE_MOVE)
2574 if (fd1 != NULL && fd != fd1 )
2576 if (!strcmp(fd->change->dest, fd1->change->dest))
2578 ret |= CHANGE_DUPLICATE_DEST;
2584 fd->change->error = ret;
2585 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2592 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2597 ret = file_data_verify_ci(fd, list);
2599 work = fd->sidecar_files;
2602 FileData *sfd = work->data;
2604 ret |= file_data_verify_ci(sfd, list);
2611 gchar *file_data_get_error_string(gint error)
2613 GString *result = g_string_new("");
2615 if (error & CHANGE_NO_SRC)
2617 if (result->len > 0) g_string_append(result, ", ");
2618 g_string_append(result, _("file or directory does not exist"));
2621 if (error & CHANGE_DEST_EXISTS)
2623 if (result->len > 0) g_string_append(result, ", ");
2624 g_string_append(result, _("destination already exists"));
2627 if (error & CHANGE_NO_WRITE_PERM_DEST)
2629 if (result->len > 0) g_string_append(result, ", ");
2630 g_string_append(result, _("destination can't be overwritten"));
2633 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2635 if (result->len > 0) g_string_append(result, ", ");
2636 g_string_append(result, _("destination directory is not writable"));
2639 if (error & CHANGE_NO_DEST_DIR)
2641 if (result->len > 0) g_string_append(result, ", ");
2642 g_string_append(result, _("destination directory does not exist"));
2645 if (error & CHANGE_NO_WRITE_PERM_DIR)
2647 if (result->len > 0) g_string_append(result, ", ");
2648 g_string_append(result, _("source directory is not writable"));
2651 if (error & CHANGE_NO_READ_PERM)
2653 if (result->len > 0) g_string_append(result, ", ");
2654 g_string_append(result, _("no read permission"));
2657 if (error & CHANGE_WARN_NO_WRITE_PERM)
2659 if (result->len > 0) g_string_append(result, ", ");
2660 g_string_append(result, _("file is readonly"));
2663 if (error & CHANGE_WARN_DEST_EXISTS)
2665 if (result->len > 0) g_string_append(result, ", ");
2666 g_string_append(result, _("destination already exists and will be overwritten"));
2669 if (error & CHANGE_WARN_SAME)
2671 if (result->len > 0) g_string_append(result, ", ");
2672 g_string_append(result, _("source and destination are the same"));
2675 if (error & CHANGE_WARN_CHANGED_EXT)
2677 if (result->len > 0) g_string_append(result, ", ");
2678 g_string_append(result, _("source and destination have different extension"));
2681 if (error & CHANGE_WARN_UNSAVED_META)
2683 if (result->len > 0) g_string_append(result, ", ");
2684 g_string_append(result, _("there are unsaved metadata changes for the file"));
2687 if (error & CHANGE_DUPLICATE_DEST)
2689 if (result->len > 0) g_string_append(result, ", ");
2690 g_string_append(result, _("another destination file has the same filename"));
2693 return g_string_free(result, FALSE);
2696 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2699 gint all_errors = 0;
2700 gint common_errors = ~0;
2705 if (!list) return 0;
2707 num = g_list_length(list);
2708 errors = g_new(int, num);
2719 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2720 all_errors |= error;
2721 common_errors &= error;
2728 if (desc && all_errors)
2731 GString *result = g_string_new("");
2735 gchar *str = file_data_get_error_string(common_errors);
2736 g_string_append(result, str);
2737 g_string_append(result, "\n");
2751 error = errors[i] & ~common_errors;
2755 gchar *str = file_data_get_error_string(error);
2756 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2761 *desc = g_string_free(result, FALSE);
2770 * perform the change described by FileFataChangeInfo
2771 * it is used for internal operations,
2772 * this function actually operates with files on the filesystem
2773 * it should implement safe delete
2776 static gboolean file_data_perform_move(FileData *fd)
2778 g_assert(!strcmp(fd->change->source, fd->path));
2779 return move_file(fd->change->source, fd->change->dest);
2782 static gboolean file_data_perform_copy(FileData *fd)
2784 g_assert(!strcmp(fd->change->source, fd->path));
2785 return copy_file(fd->change->source, fd->change->dest);
2788 static gboolean file_data_perform_delete(FileData *fd)
2790 if (isdir(fd->path) && !islink(fd->path))
2791 return rmdir_utf8(fd->path);
2793 if (options->file_ops.safe_delete_enable)
2794 return file_util_safe_unlink(fd->path);
2796 return unlink_file(fd->path);
2799 gboolean file_data_perform_ci(FileData *fd)
2801 FileDataChangeType type = fd->change->type;
2805 case FILEDATA_CHANGE_MOVE:
2806 return file_data_perform_move(fd);
2807 case FILEDATA_CHANGE_COPY:
2808 return file_data_perform_copy(fd);
2809 case FILEDATA_CHANGE_RENAME:
2810 return file_data_perform_move(fd); /* the same as move */
2811 case FILEDATA_CHANGE_DELETE:
2812 return file_data_perform_delete(fd);
2813 case FILEDATA_CHANGE_WRITE_METADATA:
2814 return metadata_write_perform(fd);
2815 case FILEDATA_CHANGE_UNSPECIFIED:
2816 /* nothing to do here */
2824 gboolean file_data_sc_perform_ci(FileData *fd)
2827 gboolean ret = TRUE;
2828 FileDataChangeType type = fd->change->type;
2830 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2832 work = fd->sidecar_files;
2835 FileData *sfd = work->data;
2837 if (!file_data_perform_ci(sfd)) ret = FALSE;
2841 if (!file_data_perform_ci(fd)) ret = FALSE;
2847 * updates FileData structure according to FileDataChangeInfo
2850 gboolean file_data_apply_ci(FileData *fd)
2852 FileDataChangeType type = fd->change->type;
2855 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2857 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2858 file_data_planned_change_remove(fd);
2860 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2862 /* this change overwrites another file which is already known to other modules
2863 renaming fd would create duplicate FileData structure
2864 the best thing we can do is nothing
2865 FIXME: maybe we could copy stuff like marks
2867 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2871 file_data_set_path(fd, fd->change->dest);
2874 file_data_increment_version(fd);
2875 file_data_send_notification(fd, NOTIFY_CHANGE);
2880 gboolean file_data_sc_apply_ci(FileData *fd)
2883 FileDataChangeType type = fd->change->type;
2885 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2887 work = fd->sidecar_files;
2890 FileData *sfd = work->data;
2892 file_data_apply_ci(sfd);
2896 file_data_apply_ci(fd);
2901 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2904 if (fd->parent) fd = fd->parent;
2905 if (!g_list_find(list, fd)) return FALSE;
2907 work = fd->sidecar_files;
2910 if (!g_list_find(list, work->data)) return FALSE;
2916 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2921 /* change partial groups to independent files */
2926 FileData *fd = work->data;
2929 if (!file_data_list_contains_whole_group(list, fd))
2931 file_data_disable_grouping(fd, TRUE);
2934 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2940 /* remove sidecars from the list,
2941 they can be still acessed via main_fd->sidecar_files */
2945 FileData *fd = work->data;
2949 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2951 out = g_list_prepend(out, file_data_ref(fd));
2955 filelist_free(list);
2956 out = g_list_reverse(out);
2966 * notify other modules about the change described by FileDataChangeInfo
2969 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2970 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2971 implementation in view_file_list.c */
2974 typedef struct _NotifyIdleData NotifyIdleData;
2976 struct _NotifyIdleData {
2982 typedef struct _NotifyData NotifyData;
2984 struct _NotifyData {
2985 FileDataNotifyFunc func;
2987 NotifyPriority priority;
2990 static GList *notify_func_list = NULL;
2992 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2994 NotifyData *nda = (NotifyData *)a;
2995 NotifyData *ndb = (NotifyData *)b;
2997 if (nda->priority < ndb->priority) return -1;
2998 if (nda->priority > ndb->priority) return 1;
3002 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
3005 GList *work = notify_func_list;
3009 NotifyData *nd = (NotifyData *)work->data;
3011 if (nd->func == func && nd->data == data)
3013 g_warning("Notify func already registered");
3019 nd = g_new(NotifyData, 1);
3022 nd->priority = priority;
3024 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
3025 DEBUG_2("Notify func registered: %p", nd);
3030 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
3032 GList *work = notify_func_list;
3036 NotifyData *nd = (NotifyData *)work->data;
3038 if (nd->func == func && nd->data == data)
3040 notify_func_list = g_list_delete_link(notify_func_list, work);
3042 DEBUG_2("Notify func unregistered: %p", nd);
3048 g_warning("Notify func not found");
3053 gboolean file_data_send_notification_idle_cb(gpointer data)
3055 NotifyIdleData *nid = (NotifyIdleData *)data;
3056 GList *work = notify_func_list;
3060 NotifyData *nd = (NotifyData *)work->data;
3062 nd->func(nid->fd, nid->type, nd->data);
3065 file_data_unref(nid->fd);
3070 void file_data_send_notification(FileData *fd, NotifyType type)
3072 GList *work = notify_func_list;
3076 NotifyData *nd = (NotifyData *)work->data;
3078 nd->func(fd, type, nd->data);
3082 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3083 nid->fd = file_data_ref(fd);
3085 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3089 static GHashTable *file_data_monitor_pool = NULL;
3090 static guint realtime_monitor_id = 0; /* event source id */
3092 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
3096 file_data_check_changed_files(fd);
3098 DEBUG_1("monitor %s", fd->path);
3101 static gboolean realtime_monitor_cb(gpointer data)
3103 if (!options->update_on_time_change) return TRUE;
3104 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
3108 gboolean file_data_register_real_time_monitor(FileData *fd)
3114 if (!file_data_monitor_pool)
3115 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3117 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3119 DEBUG_1("Register realtime %d %s", count, fd->path);
3122 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3124 if (!realtime_monitor_id)
3126 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
3132 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3136 g_assert(file_data_monitor_pool);
3138 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3140 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3142 g_assert(count > 0);
3147 g_hash_table_remove(file_data_monitor_pool, fd);
3149 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3151 file_data_unref(fd);
3153 if (g_hash_table_size(file_data_monitor_pool) == 0)
3155 g_source_remove(realtime_monitor_id);
3156 realtime_monitor_id = 0;
3162 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */