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"
40 gint global_file_data_count = 0;
43 static GHashTable *file_data_pool = NULL;
44 static GHashTable *file_data_planned_change_hash = NULL;
46 static gint sidecar_file_priority(const gchar *extension);
47 static void file_data_check_sidecars(const GList *basename_list);
48 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
51 static SortType filelist_sort_method = SORT_NONE;
52 static gboolean filelist_sort_ascend = TRUE;
55 *-----------------------------------------------------------------------------
56 * text conversion utils
57 *-----------------------------------------------------------------------------
60 gchar *text_from_size(gint64 size)
66 /* what I would like to use is printf("%'d", size)
67 * BUT: not supported on every libc :(
71 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
72 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
76 a = g_strdup_printf("%d", (guint)size);
82 b = g_new(gchar, l + n + 1);
107 gchar *text_from_size_abrev(gint64 size)
109 if (size < (gint64)1024)
111 return g_strdup_printf(_("%d bytes"), (gint)size);
113 if (size < (gint64)1048576)
115 return g_strdup_printf(_("%.1f KiB"), (gdouble)size / 1024.0);
117 if (size < (gint64)1073741824)
119 return g_strdup_printf(_("%.1f MiB"), (gdouble)size / 1048576.0);
122 /* to avoid overflowing the gdouble, do division in two steps */
124 return g_strdup_printf(_("%.1f GiB"), (gdouble)size / 1024.0);
127 /* note: returned string is valid until next call to text_from_time() */
128 const gchar *text_from_time(time_t t)
130 static gchar *ret = NULL;
134 GError *error = NULL;
136 btime = localtime(&t);
138 /* the %x warning about 2 digit years is not an error */
139 buflen = strftime(buf, sizeof(buf), "%x %X", btime);
140 if (buflen < 1) return "";
143 ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
146 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
155 *-----------------------------------------------------------------------------
156 * changed files detection and notification
157 *-----------------------------------------------------------------------------
160 void file_data_increment_version(FileData *fd)
166 fd->parent->version++;
167 fd->parent->valid_marks = 0;
171 static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st)
173 if (fd->size != st->st_size ||
174 fd->date != st->st_mtime)
176 fd->size = st->st_size;
177 fd->date = st->st_mtime;
178 fd->cdate = st->st_ctime;
179 fd->mode = st->st_mode;
180 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
181 fd->thumb_pixbuf = NULL;
182 file_data_increment_version(fd);
183 file_data_send_notification(fd, NOTIFY_REREAD);
189 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
191 gboolean ret = FALSE;
194 ret = file_data_check_changed_single_file(fd, st);
196 work = fd->sidecar_files;
199 FileData *sfd = work->data;
203 if (!stat_utf8(sfd->path, &st))
208 file_data_disconnect_sidecar_file(fd, sfd);
210 file_data_increment_version(sfd);
211 file_data_send_notification(sfd, NOTIFY_REREAD);
212 file_data_unref(sfd);
216 ret |= file_data_check_changed_files_recursive(sfd, &st);
222 gboolean file_data_check_changed_files(FileData *fd)
224 gboolean ret = FALSE;
227 if (fd->parent) fd = fd->parent;
229 if (!stat_utf8(fd->path, &st))
233 FileData *sfd = NULL;
235 /* parent is missing, we have to rebuild whole group */
240 /* file_data_disconnect_sidecar_file might delete the file,
241 we have to keep the reference to prevent this */
242 sidecars = filelist_copy(fd->sidecar_files);
250 file_data_disconnect_sidecar_file(fd, sfd);
252 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
253 /* now we can release the sidecars */
254 filelist_free(sidecars);
255 file_data_increment_version(fd);
256 file_data_send_notification(fd, NOTIFY_REREAD);
261 ret |= file_data_check_changed_files_recursive(fd, &st);
268 *-----------------------------------------------------------------------------
269 * file name, extension, sorting, ...
270 *-----------------------------------------------------------------------------
273 static void file_data_set_collate_keys(FileData *fd)
275 gchar *caseless_name;
278 valid_name = g_filename_display_name(fd->name);
279 caseless_name = g_utf8_casefold(valid_name, -1);
281 g_free(fd->collate_key_name);
282 g_free(fd->collate_key_name_nocase);
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);
296 g_free(caseless_name);
299 static void file_data_set_path(FileData *fd, const gchar *path)
301 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
302 g_assert(file_data_pool);
306 if (fd->original_path)
308 g_hash_table_remove(file_data_pool, fd->original_path);
309 g_free(fd->original_path);
312 g_assert(!g_hash_table_lookup(file_data_pool, path));
314 fd->original_path = g_strdup(path);
315 g_hash_table_insert(file_data_pool, fd->original_path, fd);
317 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
319 fd->path = g_strdup(path);
321 fd->extension = fd->name + 1;
322 file_data_set_collate_keys(fd);
326 fd->path = g_strdup(path);
327 fd->name = filename_from_path(fd->path);
329 if (strcmp(fd->name, "..") == 0)
331 gchar *dir = remove_level_from_path(path);
333 fd->path = remove_level_from_path(dir);
336 fd->extension = fd->name + 2;
337 file_data_set_collate_keys(fd);
340 else if (strcmp(fd->name, ".") == 0)
343 fd->path = remove_level_from_path(path);
345 fd->extension = fd->name + 1;
346 file_data_set_collate_keys(fd);
350 fd->extension = registered_extension_from_path(fd->path);
351 if (fd->extension == NULL)
353 fd->extension = fd->name + strlen(fd->name);
356 fd->sidecar_priority = sidecar_file_priority(fd->extension);
357 file_data_set_collate_keys(fd);
361 *-----------------------------------------------------------------------------
362 * create or reuse Filedata
363 *-----------------------------------------------------------------------------
366 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
372 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
374 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
377 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
379 fd = g_hash_table_lookup(file_data_pool, path_utf8);
385 if (!fd && file_data_planned_change_hash)
387 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
390 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
391 if (!isfile(fd->path))
394 file_data_apply_ci(fd);
407 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
410 changed = file_data_check_changed_single_file(fd, st);
412 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
417 fd = g_new0(FileData, 1);
418 #ifdef DEBUG_FILEDATA
419 global_file_data_count++;
420 DEBUG_2("file data count++: %d", global_file_data_count);
423 fd->size = st->st_size;
424 fd->date = st->st_mtime;
425 fd->cdate = st->st_ctime;
426 fd->mode = st->st_mode;
428 fd->magick = FD_MAGICK;
430 fd->rating = STAR_RATING_NOT_READ;
431 fd->format_class = filter_file_get_class(path_utf8);
435 user = getpwuid(st->st_uid);
438 fd->owner = g_strdup_printf("%u", st->st_uid);
442 fd->owner = g_strdup(user->pw_name);
445 group = getgrgid(st->st_gid);
448 fd->group = g_strdup_printf("%u", st->st_gid);
452 fd->group = g_strdup(group->gr_name);
455 fd->sym_link = get_symbolic_link(path_utf8);
457 if (disable_sidecars) fd->disable_grouping = TRUE;
459 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
464 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
466 gchar *path_utf8 = path_to_utf8(path);
467 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
473 FileData *file_data_new_simple(const gchar *path_utf8)
478 if (!stat_utf8(path_utf8, &st))
484 fd = g_hash_table_lookup(file_data_pool, path_utf8);
485 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
494 void read_exif_time_data(FileData *file)
496 if (file->exifdate > 0)
498 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
509 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
510 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), (void *)file, file->path);
515 uint year, month, day, hour, min, sec;
517 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
518 time_str.tm_year = year - 1900;
519 time_str.tm_mon = month - 1;
520 time_str.tm_mday = day;
521 time_str.tm_hour = hour;
522 time_str.tm_min = min;
523 time_str.tm_sec = sec;
524 time_str.tm_isdst = 0;
526 file->exifdate = mktime(&time_str);
532 void read_exif_time_digitized_data(FileData *file)
534 if (file->exifdate_digitized > 0)
536 DEBUG_1("%s set_exif_time_digitized_data: Already exists for %s", get_exec_time(), file->path);
547 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeDigitized");
548 DEBUG_2("%s set_exif_time_digitized_data: reading %p %s", get_exec_time(), (void *)file, file->path);
553 uint year, month, day, hour, min, sec;
555 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
556 time_str.tm_year = year - 1900;
557 time_str.tm_mon = month - 1;
558 time_str.tm_mday = day;
559 time_str.tm_hour = hour;
560 time_str.tm_min = min;
561 time_str.tm_sec = sec;
562 time_str.tm_isdst = 0;
564 file->exifdate_digitized = mktime(&time_str);
570 void read_rating_data(FileData *file)
574 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
577 file->rating = atoi(rating_str);
586 //void set_exif_time_data(GList *files)
588 //DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
592 //FileData *file = files->data;
594 //read_exif_time_data(file);
595 //files = files->next;
599 //void set_exif_time_digitized_data(GList *files)
601 //DEBUG_1("%s set_exif_time_digitized_data: ...", get_exec_time());
605 //FileData *file = files->data;
607 //read_exif_time_digitized_data(file);
608 //files = files->next;
612 //void set_rating_data(GList *files)
615 //DEBUG_1("%s set_rating_data: ...", get_exec_time());
619 //FileData *file = files->data;
620 //rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
623 //file->rating = atoi(rating_str);
624 //g_free(rating_str);
626 //files = files->next;
630 FileData *file_data_new_no_grouping(const gchar *path_utf8)
634 if (!stat_utf8(path_utf8, &st))
640 return file_data_new(path_utf8, &st, TRUE);
643 FileData *file_data_new_dir(const gchar *path_utf8)
647 if (!stat_utf8(path_utf8, &st))
653 /* dir or non-existing yet */
654 g_assert(S_ISDIR(st.st_mode));
656 return file_data_new(path_utf8, &st, TRUE);
660 *-----------------------------------------------------------------------------
662 *-----------------------------------------------------------------------------
665 #ifdef DEBUG_FILEDATA
666 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
668 FileData *file_data_ref(FileData *fd)
671 if (fd == NULL) return NULL;
672 if (fd->magick != FD_MAGICK)
673 #ifdef DEBUG_FILEDATA
674 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, (void *)fd);
676 log_printf("Error: fd magick mismatch fd=%p", fd);
678 g_assert(fd->magick == FD_MAGICK);
681 #ifdef DEBUG_FILEDATA
682 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->path, file, line);
684 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
689 static void file_data_free(FileData *fd)
691 g_assert(fd->magick == FD_MAGICK);
692 g_assert(fd->ref == 0);
693 g_assert(!fd->locked);
695 #ifdef DEBUG_FILEDATA
696 global_file_data_count--;
697 DEBUG_2("file data count--: %d", global_file_data_count);
700 metadata_cache_free(fd);
701 g_hash_table_remove(file_data_pool, fd->original_path);
704 g_free(fd->original_path);
705 g_free(fd->collate_key_name);
706 g_free(fd->collate_key_name_nocase);
707 g_free(fd->extended_extension);
708 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
709 histmap_free(fd->histmap);
712 g_free(fd->sym_link);
713 g_free(fd->format_name);
714 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
716 file_data_change_info_free(NULL, fd);
721 * @brief Checks if the FileData is referenced
723 * Checks the refcount and whether the FileData is locked.
725 static gboolean file_data_check_has_ref(FileData *fd)
727 return fd->ref > 0 || fd->locked;
731 * @brief Consider freeing a FileData.
733 * This function will free a FileData and its children provided that neither its parent nor it has
734 * a positive refcount, and provided that neither is locked.
736 static void file_data_consider_free(FileData *fd)
739 FileData *parent = fd->parent ? fd->parent : fd;
741 g_assert(fd->magick == FD_MAGICK);
742 if (file_data_check_has_ref(fd)) return;
743 if (file_data_check_has_ref(parent)) return;
745 work = parent->sidecar_files;
748 FileData *sfd = work->data;
749 if (file_data_check_has_ref(sfd)) return;
753 /* Neither the parent nor the siblings are referenced, so we can free everything */
754 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
755 fd->path, fd->parent ? parent->path : "-");
757 work = parent->sidecar_files;
760 FileData *sfd = work->data;
765 g_list_free(parent->sidecar_files);
766 parent->sidecar_files = NULL;
768 file_data_free(parent);
771 #ifdef DEBUG_FILEDATA
772 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
774 void file_data_unref(FileData *fd)
777 if (fd == NULL) return;
778 if (fd->magick != FD_MAGICK)
779 #ifdef DEBUG_FILEDATA
780 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, (void *)fd);
782 log_printf("Error: fd magick mismatch fd=%p", fd);
784 g_assert(fd->magick == FD_MAGICK);
787 #ifdef DEBUG_FILEDATA
788 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->locked, fd->path,
791 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
794 // Free FileData if it's no longer ref'd
795 file_data_consider_free(fd);
799 * @brief Lock the FileData in memory.
801 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
802 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
803 * even if the code would continue to function properly even if the FileData were freed. Code that
804 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
806 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
807 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
809 void file_data_lock(FileData *fd)
811 if (fd == NULL) return;
812 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
814 g_assert(fd->magick == FD_MAGICK);
817 DEBUG_2("file_data_ref fd=%p (%d): '%s'", (void *)fd, fd->ref, fd->path);
821 * @brief Reset the maintain-FileData-in-memory lock
823 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
824 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
825 * keeping it from being freed.
827 void file_data_unlock(FileData *fd)
829 if (fd == NULL) return;
830 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
832 g_assert(fd->magick == FD_MAGICK);
835 // Free FileData if it's no longer ref'd
836 file_data_consider_free(fd);
840 * @brief Lock all of the FileDatas in the provided list
842 * @see file_data_lock(#FileData)
844 void file_data_lock_list(GList *list)
851 FileData *fd = work->data;
858 * @brief Unlock all of the FileDatas in the provided list
860 * @see #file_data_unlock(#FileData)
862 void file_data_unlock_list(GList *list)
869 FileData *fd = work->data;
871 file_data_unlock(fd);
876 *-----------------------------------------------------------------------------
877 * sidecar file info struct
878 *-----------------------------------------------------------------------------
881 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
883 const FileData *fda = a;
884 const FileData *fdb = b;
886 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
887 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
889 return strcmp(fdb->extension, fda->extension);
893 static gint sidecar_file_priority(const gchar *extension)
898 if (extension == NULL)
901 work = sidecar_ext_get_list();
904 gchar *ext = work->data;
907 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
913 static void file_data_check_sidecars(const GList *basename_list)
915 /* basename_list contains the new group - first is the parent, then sorted sidecars */
916 /* all files in the list have ref count > 0 */
919 GList *s_work, *new_sidecars;
922 if (!basename_list) return;
925 DEBUG_2("basename start");
926 work = basename_list;
929 FileData *fd = work->data;
931 g_assert(fd->magick == FD_MAGICK);
932 DEBUG_2("basename: %p %s", (void *)fd, fd->name);
935 g_assert(fd->parent->magick == FD_MAGICK);
936 DEBUG_2(" parent: %p", (void *)fd->parent);
938 s_work = fd->sidecar_files;
941 FileData *sfd = s_work->data;
942 s_work = s_work->next;
943 g_assert(sfd->magick == FD_MAGICK);
944 DEBUG_2(" sidecar: %p %s", (void *)sfd, sfd->name);
947 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
950 parent_fd = basename_list->data;
952 /* check if the second and next entries of basename_list are already connected
953 as sidecars of the first entry (parent_fd) */
954 work = basename_list->next;
955 s_work = parent_fd->sidecar_files;
957 while (work && s_work)
959 if (work->data != s_work->data) break;
961 s_work = s_work->next;
964 if (!work && !s_work)
966 DEBUG_2("basename no change");
967 return; /* no change in grouping */
970 /* we have to regroup it */
972 /* first, disconnect everything and send notification*/
974 work = basename_list;
977 FileData *fd = work->data;
979 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
983 FileData *old_parent = fd->parent;
984 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
985 file_data_ref(old_parent);
986 file_data_disconnect_sidecar_file(old_parent, fd);
987 file_data_send_notification(old_parent, NOTIFY_REREAD);
988 file_data_unref(old_parent);
991 while (fd->sidecar_files)
993 FileData *sfd = fd->sidecar_files->data;
994 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
996 file_data_disconnect_sidecar_file(fd, sfd);
997 file_data_send_notification(sfd, NOTIFY_REREAD);
998 file_data_unref(sfd);
1000 file_data_send_notification(fd, NOTIFY_GROUPING);
1002 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
1005 /* now we can form the new group */
1006 work = basename_list->next;
1007 new_sidecars = NULL;
1010 FileData *sfd = work->data;
1011 g_assert(sfd->magick == FD_MAGICK);
1012 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
1013 sfd->parent = parent_fd;
1014 new_sidecars = g_list_prepend(new_sidecars, sfd);
1017 g_assert(parent_fd->sidecar_files == NULL);
1018 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
1019 DEBUG_1("basename group changed for %s", parent_fd->path);
1023 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
1025 g_assert(target->magick == FD_MAGICK);
1026 g_assert(sfd->magick == FD_MAGICK);
1027 g_assert(g_list_find(target->sidecar_files, sfd));
1029 file_data_ref(target);
1032 g_assert(sfd->parent == target);
1034 file_data_increment_version(sfd); /* increments both sfd and target */
1036 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
1038 g_free(sfd->extended_extension);
1039 sfd->extended_extension = NULL;
1041 file_data_unref(target);
1042 file_data_unref(sfd);
1045 /* disables / enables grouping for particular file, sends UPDATE notification */
1046 void file_data_disable_grouping(FileData *fd, gboolean disable)
1048 if (!fd->disable_grouping == !disable) return;
1050 fd->disable_grouping = !!disable;
1056 FileData *parent = file_data_ref(fd->parent);
1057 file_data_disconnect_sidecar_file(parent, fd);
1058 file_data_send_notification(parent, NOTIFY_GROUPING);
1059 file_data_unref(parent);
1061 else if (fd->sidecar_files)
1063 GList *sidecar_files = filelist_copy(fd->sidecar_files);
1064 GList *work = sidecar_files;
1067 FileData *sfd = work->data;
1069 file_data_disconnect_sidecar_file(fd, sfd);
1070 file_data_send_notification(sfd, NOTIFY_GROUPING);
1072 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
1073 filelist_free(sidecar_files);
1077 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
1082 file_data_increment_version(fd);
1083 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
1085 file_data_send_notification(fd, NOTIFY_GROUPING);
1088 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
1095 FileData *fd = work->data;
1097 file_data_disable_grouping(fd, disable);
1105 *-----------------------------------------------------------------------------
1107 *-----------------------------------------------------------------------------
1111 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
1114 if (!filelist_sort_ascend)
1121 switch (filelist_sort_method)
1126 if (fa->size < fb->size) return -1;
1127 if (fa->size > fb->size) return 1;
1128 /* fall back to name */
1131 if (fa->date < fb->date) return -1;
1132 if (fa->date > fb->date) return 1;
1133 /* fall back to name */
1136 if (fa->cdate < fb->cdate) return -1;
1137 if (fa->cdate > fb->cdate) return 1;
1138 /* fall back to name */
1141 if (fa->exifdate < fb->exifdate) return -1;
1142 if (fa->exifdate > fb->exifdate) return 1;
1143 /* fall back to name */
1145 case SORT_EXIFTIMEDIGITIZED:
1146 if (fa->exifdate_digitized < fb->exifdate_digitized) return -1;
1147 if (fa->exifdate_digitized > fb->exifdate_digitized) return 1;
1148 /* fall back to name */
1151 if (fa->rating < fb->rating) return -1;
1152 if (fa->rating > fb->rating) return 1;
1153 /* fall back to name */
1156 if (fa->format_class < fb->format_class) return -1;
1157 if (fa->format_class > fb->format_class) return 1;
1158 /* fall back to name */
1160 #ifdef HAVE_STRVERSCMP
1162 ret = strverscmp(fa->name, fb->name);
1163 if (ret != 0) return ret;
1170 if (options->file_sort.case_sensitive)
1171 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1173 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1175 if (ret != 0) return ret;
1177 /* do not return 0 unless the files are really the same
1178 file_data_pool ensures that original_path is unique
1180 return strcmp(fa->original_path, fb->original_path);
1183 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1185 filelist_sort_method = method;
1186 filelist_sort_ascend = ascend;
1187 return filelist_sort_compare_filedata(fa, fb);
1190 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1192 return filelist_sort_compare_filedata(a, b);
1195 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1197 filelist_sort_method = method;
1198 filelist_sort_ascend = ascend;
1199 return g_list_sort(list, cb);
1202 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1204 filelist_sort_method = method;
1205 filelist_sort_ascend = ascend;
1206 return g_list_insert_sorted(list, data, cb);
1209 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1211 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1214 //GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
1216 //return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1220 *-----------------------------------------------------------------------------
1221 * basename hash - grouping of sidecars in filelist
1222 *-----------------------------------------------------------------------------
1226 static GHashTable *file_data_basename_hash_new(void)
1228 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1231 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1234 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1236 list = g_hash_table_lookup(basename_hash, basename);
1240 DEBUG_1("TG: basename_hash not found for %s",fd->path);
1241 const gchar *parent_extension = registered_extension_from_path(basename);
1243 if (parent_extension)
1245 DEBUG_1("TG: parent extension %s",parent_extension);
1246 gchar *parent_basename = g_strndup(basename, parent_extension - basename);
1247 DEBUG_1("TG: parent basename %s",parent_basename);
1248 FileData *parent_fd = g_hash_table_lookup(file_data_pool, basename);
1251 DEBUG_1("TG: parent fd found");
1252 list = g_hash_table_lookup(basename_hash, parent_basename);
1253 if (!g_list_find(list, parent_fd))
1255 DEBUG_1("TG: parent fd doesn't fit");
1256 g_free(parent_basename);
1262 basename = parent_basename;
1263 fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
1269 if (!g_list_find(list, fd))
1271 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1272 g_hash_table_insert(basename_hash, basename, list);
1281 static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
1283 file_data_basename_hash_insert((GHashTable *)basename_hash, (FileData *)fd);
1286 static void file_data_basename_hash_remove_list(gpointer UNUSED(key), gpointer value, gpointer UNUSED(data))
1288 filelist_free((GList *)value);
1291 static void file_data_basename_hash_free(GHashTable *basename_hash)
1293 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1294 g_hash_table_destroy(basename_hash);
1298 *-----------------------------------------------------------------------------
1299 * handling sidecars in filelist
1300 *-----------------------------------------------------------------------------
1303 static GList *filelist_filter_out_sidecars(GList *flist)
1305 GList *work = flist;
1306 GList *flist_filtered = NULL;
1310 FileData *fd = work->data;
1313 if (fd->parent) /* remove fd's that are children */
1314 file_data_unref(fd);
1316 flist_filtered = g_list_prepend(flist_filtered, fd);
1320 return flist_filtered;
1323 static void file_data_basename_hash_to_sidecars(gpointer UNUSED(key), gpointer value, gpointer UNUSED(data))
1325 GList *basename_list = (GList *)value;
1326 file_data_check_sidecars(basename_list);
1330 static gboolean is_hidden_file(const gchar *name)
1332 if (name[0] != '.') return FALSE;
1333 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1338 *-----------------------------------------------------------------------------
1339 * the main filelist function
1340 *-----------------------------------------------------------------------------
1343 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1348 GList *dlist = NULL;
1349 GList *flist = NULL;
1350 GList *xmp_files = NULL;
1351 gint (*stat_func)(const gchar *path, struct stat *buf);
1352 GHashTable *basename_hash = NULL;
1354 g_assert(files || dirs);
1356 if (files) *files = NULL;
1357 if (dirs) *dirs = NULL;
1359 pathl = path_from_utf8(dir_path);
1360 if (!pathl) return FALSE;
1362 dp = opendir(pathl);
1369 if (files) basename_hash = file_data_basename_hash_new();
1371 if (follow_symlinks)
1376 while ((dir = readdir(dp)) != NULL)
1378 struct stat ent_sbuf;
1379 const gchar *name = dir->d_name;
1382 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1385 filepath = g_build_filename(pathl, name, NULL);
1386 if (stat_func(filepath, &ent_sbuf) >= 0)
1388 if (S_ISDIR(ent_sbuf.st_mode))
1390 /* we ignore the .thumbnails dir for cleanliness */
1392 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1393 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1394 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1395 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1397 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1402 if (files && filter_name_exists(name))
1404 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1405 flist = g_list_prepend(flist, fd);
1406 if (fd->sidecar_priority && !fd->disable_grouping)
1408 if (strcmp(fd->extension, ".xmp") != 0)
1409 file_data_basename_hash_insert(basename_hash, fd);
1411 xmp_files = g_list_append(xmp_files, fd);
1418 if (errno == EOVERFLOW)
1420 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1432 g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
1433 g_list_free(xmp_files);
1436 if (dirs) *dirs = dlist;
1440 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1442 *files = filelist_filter_out_sidecars(flist);
1444 if (basename_hash) file_data_basename_hash_free(basename_hash);
1449 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1451 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1454 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1456 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1459 FileData *file_data_new_group(const gchar *path_utf8)
1466 if (!stat_utf8(path_utf8, &st))
1472 if (S_ISDIR(st.st_mode))
1473 return file_data_new(path_utf8, &st, TRUE);
1475 dir = remove_level_from_path(path_utf8);
1477 filelist_read_real(dir, &files, NULL, TRUE);
1479 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1480 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1486 filelist_free(files);
1492 void filelist_free(GList *list)
1499 file_data_unref((FileData *)work->data);
1507 GList *filelist_copy(GList *list)
1509 GList *new_list = NULL;
1520 new_list = g_list_prepend(new_list, file_data_ref(fd));
1523 return g_list_reverse(new_list);
1526 GList *filelist_from_path_list(GList *list)
1528 GList *new_list = NULL;
1539 new_list = g_list_prepend(new_list, file_data_new_group(path));
1542 return g_list_reverse(new_list);
1545 GList *filelist_to_path_list(GList *list)
1547 GList *new_list = NULL;
1558 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1561 return g_list_reverse(new_list);
1564 GList *filelist_filter(GList *list, gboolean is_dir_list)
1568 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1573 FileData *fd = (FileData *)(work->data);
1574 const gchar *name = fd->name;
1576 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1577 (!is_dir_list && !filter_name_exists(name)) ||
1578 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1579 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1583 list = g_list_remove_link(list, link);
1584 file_data_unref(fd);
1595 *-----------------------------------------------------------------------------
1596 * filelist recursive
1597 *-----------------------------------------------------------------------------
1600 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1602 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1605 GList *filelist_sort_path(GList *list)
1607 return g_list_sort(list, filelist_sort_path_cb);
1610 static void filelist_recursive_append(GList **list, GList *dirs)
1617 FileData *fd = (FileData *)(work->data);
1621 if (filelist_read(fd, &f, &d))
1623 f = filelist_filter(f, FALSE);
1624 f = filelist_sort_path(f);
1625 *list = g_list_concat(*list, f);
1627 d = filelist_filter(d, TRUE);
1628 d = filelist_sort_path(d);
1629 filelist_recursive_append(list, d);
1637 static void filelist_recursive_append_full(GList **list, GList *dirs, SortType method, gboolean ascend)
1644 FileData *fd = (FileData *)(work->data);
1648 if (filelist_read(fd, &f, &d))
1650 f = filelist_filter(f, FALSE);
1651 f = filelist_sort_full(f, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1652 *list = g_list_concat(*list, f);
1654 d = filelist_filter(d, TRUE);
1655 d = filelist_sort_path(d);
1656 filelist_recursive_append_full(list, d, method, ascend);
1664 GList *filelist_recursive(FileData *dir_fd)
1669 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1670 list = filelist_filter(list, FALSE);
1671 list = filelist_sort_path(list);
1673 d = filelist_filter(d, TRUE);
1674 d = filelist_sort_path(d);
1675 filelist_recursive_append(&list, d);
1681 GList *filelist_recursive_full(FileData *dir_fd, SortType method, gboolean ascend)
1686 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1687 list = filelist_filter(list, FALSE);
1688 list = filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1690 d = filelist_filter(d, TRUE);
1691 d = filelist_sort_path(d);
1692 filelist_recursive_append_full(&list, d, method, ascend);
1699 *-----------------------------------------------------------------------------
1700 * file modification support
1701 *-----------------------------------------------------------------------------
1705 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1707 if (!fdci && fd) fdci = fd->change;
1711 g_free(fdci->source);
1716 if (fd) fd->change = NULL;
1719 static gboolean file_data_can_write_directly(FileData *fd)
1721 return filter_name_is_writable(fd->extension);
1724 static gboolean file_data_can_write_sidecar(FileData *fd)
1726 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1729 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1731 gchar *sidecar_path = NULL;
1734 if (!file_data_can_write_sidecar(fd)) return NULL;
1736 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1737 gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1740 FileData *sfd = work->data;
1742 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1744 sidecar_path = g_strdup(sfd->path);
1748 g_free(extended_extension);
1750 if (!existing_only && !sidecar_path)
1752 if (options->metadata.sidecar_extended_name)
1753 sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1756 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1757 sidecar_path = g_strconcat(base, ".xmp", NULL);
1762 return sidecar_path;
1766 * marks and orientation
1769 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1770 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1771 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1772 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1774 gboolean file_data_get_mark(FileData *fd, gint n)
1776 gboolean valid = (fd->valid_marks & (1 << n));
1778 if (file_data_get_mark_func[n] && !valid)
1780 guint old = fd->marks;
1781 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1783 if (!value != !(fd->marks & (1 << n)))
1785 fd->marks = fd->marks ^ (1 << n);
1788 fd->valid_marks |= (1 << n);
1789 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1791 file_data_unref(fd);
1793 else if (!old && fd->marks)
1799 return !!(fd->marks & (1 << n));
1802 guint file_data_get_marks(FileData *fd)
1805 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1809 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1812 if (!value == !file_data_get_mark(fd, n)) return;
1814 if (file_data_set_mark_func[n])
1816 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1821 fd->marks = fd->marks ^ (1 << n);
1823 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1825 file_data_unref(fd);
1827 else if (!old && fd->marks)
1832 file_data_increment_version(fd);
1833 file_data_send_notification(fd, NOTIFY_MARKS);
1836 gboolean file_data_filter_marks(FileData *fd, guint filter)
1839 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1840 return ((fd->marks & filter) == filter);
1843 GList *file_data_filter_marks_list(GList *list, guint filter)
1850 FileData *fd = work->data;
1854 if (!file_data_filter_marks(fd, filter))
1856 list = g_list_remove_link(list, link);
1857 file_data_unref(fd);
1865 gboolean file_data_filter_file_filter(FileData *fd, GRegex *filter)
1867 return g_regex_match(filter, fd->name, 0, NULL);
1870 GList *file_data_filter_file_filter_list(GList *list, GRegex *filter)
1877 FileData *fd = work->data;
1881 if (!file_data_filter_file_filter(fd, filter))
1883 list = g_list_remove_link(list, link);
1884 file_data_unref(fd);
1892 static gboolean file_data_filter_class(FileData *fd, guint filter)
1896 for (i = 0; i < FILE_FORMAT_CLASSES; i++)
1898 if (filter & (1 << i))
1900 if ((FileFormatClass)i == filter_file_get_class(fd->path))
1910 GList *file_data_filter_class_list(GList *list, guint filter)
1917 FileData *fd = work->data;
1921 if (!file_data_filter_class(fd, filter))
1923 list = g_list_remove_link(list, link);
1924 file_data_unref(fd);
1932 static void file_data_notify_mark_func(gpointer UNUSED(key), gpointer value, gpointer UNUSED(user_data))
1934 FileData *fd = value;
1935 file_data_increment_version(fd);
1936 file_data_send_notification(fd, NOTIFY_MARKS);
1939 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1941 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1943 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1945 file_data_get_mark_func[n] = get_mark_func;
1946 file_data_set_mark_func[n] = set_mark_func;
1947 file_data_mark_func_data[n] = data;
1948 file_data_destroy_mark_func[n] = notify;
1950 if (get_mark_func && file_data_pool)
1952 /* this effectively changes all known files */
1953 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1959 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1961 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1962 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1963 if (data) *data = file_data_mark_func_data[n];
1966 //gint file_data_get_user_orientation(FileData *fd)
1968 //return fd->user_orientation;
1971 //void file_data_set_user_orientation(FileData *fd, gint value)
1973 //if (fd->user_orientation == value) return;
1975 //fd->user_orientation = value;
1976 //file_data_increment_version(fd);
1977 //file_data_send_notification(fd, NOTIFY_ORIENTATION);
1982 * file_data - operates on the given fd
1983 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1987 /* return list of sidecar file extensions in a string */
1988 gchar *file_data_sc_list_to_string(FileData *fd)
1991 GString *result = g_string_new("");
1993 work = fd->sidecar_files;
1996 FileData *sfd = work->data;
1998 result = g_string_append(result, "+ ");
1999 result = g_string_append(result, sfd->extension);
2001 if (work) result = g_string_append_c(result, ' ');
2004 return g_string_free(result, FALSE);
2010 * add FileDataChangeInfo (see typedefs.h) for the given operation
2011 * uses file_data_add_change_info
2013 * fails if the fd->change already exists - change operations can't run in parallel
2014 * fd->change_info works as a lock
2016 * dest can be NULL - in this case the current name is used for now, it will
2021 FileDataChangeInfo types:
2023 MOVE - path is changed, name may be changed too
2024 RENAME - path remains unchanged, name is changed
2025 extension should remain (FIXME should we allow editing extension? it will make problems with grouping)
2026 sidecar names are changed too, extensions are not changed
2028 UPDATE - file size, date or grouping has been changed
2031 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
2033 FileDataChangeInfo *fdci;
2035 if (fd->change) return FALSE;
2037 fdci = g_new0(FileDataChangeInfo, 1);
2042 fdci->source = g_strdup(src);
2044 fdci->source = g_strdup(fd->path);
2047 fdci->dest = g_strdup(dest);
2054 static void file_data_planned_change_remove(FileData *fd)
2056 if (file_data_planned_change_hash &&
2057 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
2059 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
2061 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
2062 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
2063 file_data_unref(fd);
2064 if (g_hash_table_size(file_data_planned_change_hash) == 0)
2066 g_hash_table_destroy(file_data_planned_change_hash);
2067 file_data_planned_change_hash = NULL;
2068 DEBUG_1("planned change: empty");
2075 void file_data_free_ci(FileData *fd)
2077 FileDataChangeInfo *fdci = fd->change;
2081 file_data_planned_change_remove(fd);
2083 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
2085 g_free(fdci->source);
2093 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
2095 FileDataChangeInfo *fdci = fd->change;
2097 fdci->regroup_when_finished = enable;
2100 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
2104 if (fd->parent) fd = fd->parent;
2106 if (fd->change) return FALSE;
2108 work = fd->sidecar_files;
2111 FileData *sfd = work->data;
2113 if (sfd->change) return FALSE;
2117 file_data_add_ci(fd, type, NULL, NULL);
2119 work = fd->sidecar_files;
2122 FileData *sfd = work->data;
2124 file_data_add_ci(sfd, type, NULL, NULL);
2131 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
2135 if (fd->parent) fd = fd->parent;
2137 if (!fd->change || fd->change->type != type) return FALSE;
2139 work = fd->sidecar_files;
2142 FileData *sfd = work->data;
2144 if (!sfd->change || sfd->change->type != type) return FALSE;
2152 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
2154 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
2155 file_data_sc_update_ci_copy(fd, dest_path);
2159 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
2161 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
2162 file_data_sc_update_ci_move(fd, dest_path);
2166 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
2168 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
2169 file_data_sc_update_ci_rename(fd, dest_path);
2173 gboolean file_data_sc_add_ci_delete(FileData *fd)
2175 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
2178 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
2180 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
2181 file_data_sc_update_ci_unspecified(fd, dest_path);
2185 gboolean file_data_add_ci_write_metadata(FileData *fd)
2187 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
2190 void file_data_sc_free_ci(FileData *fd)
2194 if (fd->parent) fd = fd->parent;
2196 file_data_free_ci(fd);
2198 work = fd->sidecar_files;
2201 FileData *sfd = work->data;
2203 file_data_free_ci(sfd);
2208 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2211 gboolean ret = TRUE;
2216 FileData *fd = work->data;
2218 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2225 static void file_data_sc_revert_ci_list(GList *fd_list)
2232 FileData *fd = work->data;
2234 file_data_sc_free_ci(fd);
2239 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2246 FileData *fd = work->data;
2248 if (!func(fd, dest))
2250 file_data_sc_revert_ci_list(work->prev);
2259 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2261 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2264 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2266 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2269 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2271 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2274 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2276 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2279 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2282 gboolean ret = TRUE;
2287 FileData *fd = work->data;
2289 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2296 void file_data_free_ci_list(GList *fd_list)
2303 FileData *fd = work->data;
2305 file_data_free_ci(fd);
2310 void file_data_sc_free_ci_list(GList *fd_list)
2317 FileData *fd = work->data;
2319 file_data_sc_free_ci(fd);
2325 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2326 * fails if fd->change does not exist or the change type does not match
2329 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2331 FileDataChangeType type = fd->change->type;
2333 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2337 if (!file_data_planned_change_hash)
2338 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2340 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2342 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2343 g_hash_table_remove(file_data_planned_change_hash, old_path);
2344 file_data_unref(fd);
2347 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
2352 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2353 g_hash_table_remove(file_data_planned_change_hash, new_path);
2354 file_data_unref(ofd);
2357 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2359 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2364 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2366 gchar *old_path = fd->change->dest;
2368 fd->change->dest = g_strdup(dest_path);
2369 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2373 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2375 const gchar *extension = registered_extension_from_path(fd->change->source);
2376 gchar *base = remove_extension_from_path(dest_path);
2377 gchar *old_path = fd->change->dest;
2379 fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2380 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2386 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2389 gchar *dest_path_full = NULL;
2391 if (fd->parent) fd = fd->parent;
2395 dest_path = fd->path;
2397 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2399 gchar *dir = remove_level_from_path(fd->path);
2401 dest_path_full = g_build_filename(dir, dest_path, NULL);
2403 dest_path = dest_path_full;
2405 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2407 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2408 dest_path = dest_path_full;
2411 file_data_update_ci_dest(fd, dest_path);
2413 work = fd->sidecar_files;
2416 FileData *sfd = work->data;
2418 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2422 g_free(dest_path_full);
2425 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2427 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2428 file_data_sc_update_ci(fd, dest_path);
2432 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2434 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2437 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2439 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2442 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2444 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2447 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2449 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2452 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2454 gboolean (*func)(FileData *, const gchar *))
2457 gboolean ret = TRUE;
2462 FileData *fd = work->data;
2464 if (!func(fd, dest)) ret = FALSE;
2471 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2473 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2476 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2478 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2481 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2483 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2488 * verify source and dest paths - dest image exists, etc.
2489 * it should detect all possible problems with the planned operation
2492 gint file_data_verify_ci(FileData *fd, GList *list)
2494 gint ret = CHANGE_OK;
2497 FileData *fd1 = NULL;
2501 DEBUG_1("Change checked: no change info: %s", fd->path);
2505 if (!isname(fd->path))
2507 /* this probably should not happen */
2508 ret |= CHANGE_NO_SRC;
2509 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2513 dir = remove_level_from_path(fd->path);
2515 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2516 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2517 fd->change->type != FILEDATA_CHANGE_RENAME &&
2518 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2521 ret |= CHANGE_WARN_UNSAVED_META;
2522 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2525 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2526 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2527 !access_file(fd->path, R_OK))
2529 ret |= CHANGE_NO_READ_PERM;
2530 DEBUG_1("Change checked: no read permission: %s", fd->path);
2532 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2533 !access_file(dir, W_OK))
2535 ret |= CHANGE_NO_WRITE_PERM_DIR;
2536 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2538 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2539 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2540 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2541 !access_file(fd->path, W_OK))
2543 ret |= CHANGE_WARN_NO_WRITE_PERM;
2544 DEBUG_1("Change checked: no write permission: %s", fd->path);
2546 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2547 - that means that there are no hard errors and warnings can be disabled
2548 - the destination is determined during the check
2550 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2552 /* determine destination file */
2553 gboolean have_dest = FALSE;
2554 gchar *dest_dir = NULL;
2556 if (options->metadata.save_in_image_file)
2558 if (file_data_can_write_directly(fd))
2560 /* we can write the file directly */
2561 if (access_file(fd->path, W_OK))
2567 if (options->metadata.warn_on_write_problems)
2569 ret |= CHANGE_WARN_NO_WRITE_PERM;
2570 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2574 else if (file_data_can_write_sidecar(fd))
2576 /* we can write sidecar */
2577 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2578 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2580 file_data_update_ci_dest(fd, sidecar);
2585 if (options->metadata.warn_on_write_problems)
2587 ret |= CHANGE_WARN_NO_WRITE_PERM;
2588 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2597 /* write private metadata file under ~/.geeqie */
2599 /* If an existing metadata file exists, we will try writing to
2600 * it's location regardless of the user's preference.
2602 gchar *metadata_path = NULL;
2604 /* but ignore XMP if we are not able to write it */
2605 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2607 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2609 if (metadata_path && !access_file(metadata_path, W_OK))
2611 g_free(metadata_path);
2612 metadata_path = NULL;
2619 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2620 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2622 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2624 metadata_path = g_build_filename(dest_dir, filename, NULL);
2628 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2630 file_data_update_ci_dest(fd, metadata_path);
2635 ret |= CHANGE_NO_WRITE_PERM_DEST;
2636 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2638 g_free(metadata_path);
2643 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2648 same = (strcmp(fd->path, fd->change->dest) == 0);
2652 const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2653 if (!dest_ext) dest_ext = "";
2654 if (!options->file_filter.disable_file_extension_checks)
2656 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2658 ret |= CHANGE_WARN_CHANGED_EXT;
2659 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2665 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /** @FIXME this is now needed for running editors */
2667 ret |= CHANGE_WARN_SAME;
2668 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2672 dest_dir = remove_level_from_path(fd->change->dest);
2674 if (!isdir(dest_dir))
2676 ret |= CHANGE_NO_DEST_DIR;
2677 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2679 else if (!access_file(dest_dir, W_OK))
2681 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2682 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2686 if (isfile(fd->change->dest))
2688 if (!access_file(fd->change->dest, W_OK))
2690 ret |= CHANGE_NO_WRITE_PERM_DEST;
2691 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2695 ret |= CHANGE_WARN_DEST_EXISTS;
2696 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2699 else if (isdir(fd->change->dest))
2701 ret |= CHANGE_DEST_EXISTS;
2702 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2709 /* During a rename operation, check if another planned destination file has
2712 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2713 fd->change->type == FILEDATA_CHANGE_COPY ||
2714 fd->change->type == FILEDATA_CHANGE_MOVE)
2721 if (fd1 != NULL && fd != fd1 )
2723 if (!strcmp(fd->change->dest, fd1->change->dest))
2725 ret |= CHANGE_DUPLICATE_DEST;
2731 fd->change->error = ret;
2732 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2739 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2744 ret = file_data_verify_ci(fd, list);
2746 work = fd->sidecar_files;
2749 FileData *sfd = work->data;
2751 ret |= file_data_verify_ci(sfd, list);
2758 gchar *file_data_get_error_string(gint error)
2760 GString *result = g_string_new("");
2762 if (error & CHANGE_NO_SRC)
2764 if (result->len > 0) g_string_append(result, ", ");
2765 g_string_append(result, _("file or directory does not exist"));
2768 if (error & CHANGE_DEST_EXISTS)
2770 if (result->len > 0) g_string_append(result, ", ");
2771 g_string_append(result, _("destination already exists"));
2774 if (error & CHANGE_NO_WRITE_PERM_DEST)
2776 if (result->len > 0) g_string_append(result, ", ");
2777 g_string_append(result, _("destination can't be overwritten"));
2780 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2782 if (result->len > 0) g_string_append(result, ", ");
2783 g_string_append(result, _("destination directory is not writable"));
2786 if (error & CHANGE_NO_DEST_DIR)
2788 if (result->len > 0) g_string_append(result, ", ");
2789 g_string_append(result, _("destination directory does not exist"));
2792 if (error & CHANGE_NO_WRITE_PERM_DIR)
2794 if (result->len > 0) g_string_append(result, ", ");
2795 g_string_append(result, _("source directory is not writable"));
2798 if (error & CHANGE_NO_READ_PERM)
2800 if (result->len > 0) g_string_append(result, ", ");
2801 g_string_append(result, _("no read permission"));
2804 if (error & CHANGE_WARN_NO_WRITE_PERM)
2806 if (result->len > 0) g_string_append(result, ", ");
2807 g_string_append(result, _("file is readonly"));
2810 if (error & CHANGE_WARN_DEST_EXISTS)
2812 if (result->len > 0) g_string_append(result, ", ");
2813 g_string_append(result, _("destination already exists and will be overwritten"));
2816 if (error & CHANGE_WARN_SAME)
2818 if (result->len > 0) g_string_append(result, ", ");
2819 g_string_append(result, _("source and destination are the same"));
2822 if (error & CHANGE_WARN_CHANGED_EXT)
2824 if (result->len > 0) g_string_append(result, ", ");
2825 g_string_append(result, _("source and destination have different extension"));
2828 if (error & CHANGE_WARN_UNSAVED_META)
2830 if (result->len > 0) g_string_append(result, ", ");
2831 g_string_append(result, _("there are unsaved metadata changes for the file"));
2834 if (error & CHANGE_DUPLICATE_DEST)
2836 if (result->len > 0) g_string_append(result, ", ");
2837 g_string_append(result, _("another destination file has the same filename"));
2840 return g_string_free(result, FALSE);
2843 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2846 gint all_errors = 0;
2847 gint common_errors = ~0;
2852 if (!list) return 0;
2854 num = g_list_length(list);
2855 errors = g_new(int, num);
2866 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2867 all_errors |= error;
2868 common_errors &= error;
2875 if (desc && all_errors)
2878 GString *result = g_string_new("");
2882 gchar *str = file_data_get_error_string(common_errors);
2883 g_string_append(result, str);
2884 g_string_append(result, "\n");
2898 error = errors[i] & ~common_errors;
2902 gchar *str = file_data_get_error_string(error);
2903 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2908 *desc = g_string_free(result, FALSE);
2917 * perform the change described by FileFataChangeInfo
2918 * it is used for internal operations,
2919 * this function actually operates with files on the filesystem
2920 * it should implement safe delete
2923 static gboolean file_data_perform_move(FileData *fd)
2925 g_assert(!strcmp(fd->change->source, fd->path));
2926 return move_file(fd->change->source, fd->change->dest);
2929 static gboolean file_data_perform_copy(FileData *fd)
2931 g_assert(!strcmp(fd->change->source, fd->path));
2932 return copy_file(fd->change->source, fd->change->dest);
2935 static gboolean file_data_perform_delete(FileData *fd)
2937 if (isdir(fd->path) && !islink(fd->path))
2938 return rmdir_utf8(fd->path);
2940 if (options->file_ops.safe_delete_enable)
2941 return file_util_safe_unlink(fd->path);
2943 return unlink_file(fd->path);
2946 gboolean file_data_perform_ci(FileData *fd)
2948 FileDataChangeType type = fd->change->type;
2952 case FILEDATA_CHANGE_MOVE:
2953 return file_data_perform_move(fd);
2954 case FILEDATA_CHANGE_COPY:
2955 return file_data_perform_copy(fd);
2956 case FILEDATA_CHANGE_RENAME:
2957 return file_data_perform_move(fd); /* the same as move */
2958 case FILEDATA_CHANGE_DELETE:
2959 return file_data_perform_delete(fd);
2960 case FILEDATA_CHANGE_WRITE_METADATA:
2961 return metadata_write_perform(fd);
2962 case FILEDATA_CHANGE_UNSPECIFIED:
2963 /* nothing to do here */
2971 gboolean file_data_sc_perform_ci(FileData *fd)
2974 gboolean ret = TRUE;
2975 FileDataChangeType type = fd->change->type;
2977 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2979 work = fd->sidecar_files;
2982 FileData *sfd = work->data;
2984 if (!file_data_perform_ci(sfd)) ret = FALSE;
2988 if (!file_data_perform_ci(fd)) ret = FALSE;
2994 * updates FileData structure according to FileDataChangeInfo
2997 gboolean file_data_apply_ci(FileData *fd)
2999 FileDataChangeType type = fd->change->type;
3001 /** @FIXME delete ?*/
3002 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
3004 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
3005 file_data_planned_change_remove(fd);
3007 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
3009 /* this change overwrites another file which is already known to other modules
3010 renaming fd would create duplicate FileData structure
3011 the best thing we can do is nothing
3013 /** @FIXME maybe we could copy stuff like marks
3015 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
3019 file_data_set_path(fd, fd->change->dest);
3022 file_data_increment_version(fd);
3023 file_data_send_notification(fd, NOTIFY_CHANGE);
3028 gboolean file_data_sc_apply_ci(FileData *fd)
3031 FileDataChangeType type = fd->change->type;
3033 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3035 work = fd->sidecar_files;
3038 FileData *sfd = work->data;
3040 file_data_apply_ci(sfd);
3044 file_data_apply_ci(fd);
3049 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
3052 if (fd->parent) fd = fd->parent;
3053 if (!g_list_find(list, fd)) return FALSE;
3055 work = fd->sidecar_files;
3058 if (!g_list_find(list, work->data)) return FALSE;
3064 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
3069 /* change partial groups to independent files */
3074 FileData *fd = work->data;
3077 if (!file_data_list_contains_whole_group(list, fd))
3079 file_data_disable_grouping(fd, TRUE);
3082 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
3088 /* remove sidecars from the list,
3089 they can be still accessed via main_fd->sidecar_files */
3093 FileData *fd = work->data;
3097 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
3099 out = g_list_prepend(out, file_data_ref(fd));
3103 filelist_free(list);
3104 out = g_list_reverse(out);
3114 * notify other modules about the change described by FileDataChangeInfo
3117 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks */
3118 /** @FIXME do we need the ignore_list? It looks like a workaround for ineffective
3119 implementation in view-file-list.cc */
3122 typedef struct _NotifyIdleData NotifyIdleData;
3124 struct _NotifyIdleData {
3130 typedef struct _NotifyData NotifyData;
3132 struct _NotifyData {
3133 FileDataNotifyFunc func;
3135 NotifyPriority priority;
3138 static GList *notify_func_list = NULL;
3140 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
3142 NotifyData *nda = (NotifyData *)a;
3143 NotifyData *ndb = (NotifyData *)b;
3145 if (nda->priority < ndb->priority) return -1;
3146 if (nda->priority > ndb->priority) return 1;
3150 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
3153 GList *work = notify_func_list;
3157 NotifyData *nd = (NotifyData *)work->data;
3159 if (nd->func == func && nd->data == data)
3161 g_warning("Notify func already registered");
3167 nd = g_new(NotifyData, 1);
3170 nd->priority = priority;
3172 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
3173 DEBUG_2("Notify func registered: %p", (void *)nd);
3178 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
3180 GList *work = notify_func_list;
3184 NotifyData *nd = (NotifyData *)work->data;
3186 if (nd->func == func && nd->data == data)
3188 notify_func_list = g_list_delete_link(notify_func_list, work);
3190 DEBUG_2("Notify func unregistered: %p", (void *)nd);
3196 g_warning("Notify func not found");
3201 //gboolean file_data_send_notification_idle_cb(gpointer data)
3203 //NotifyIdleData *nid = (NotifyIdleData *)data;
3204 //GList *work = notify_func_list;
3208 //NotifyData *nd = (NotifyData *)work->data;
3210 //nd->func(nid->fd, nid->type, nd->data);
3211 //work = work->next;
3213 //file_data_unref(nid->fd);
3218 void file_data_send_notification(FileData *fd, NotifyType type)
3220 GList *work = notify_func_list;
3224 NotifyData *nd = (NotifyData *)work->data;
3226 nd->func(fd, type, nd->data);
3230 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3231 nid->fd = file_data_ref(fd);
3233 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3237 static GHashTable *file_data_monitor_pool = NULL;
3238 static guint realtime_monitor_id = 0; /* event source id */
3240 static void realtime_monitor_check_cb(gpointer key, gpointer UNUSED(value), gpointer UNUSED(data))
3244 file_data_check_changed_files(fd);
3246 DEBUG_1("monitor %s", fd->path);
3249 static gboolean realtime_monitor_cb(gpointer UNUSED(data))
3251 if (!options->update_on_time_change) return TRUE;
3252 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
3256 gboolean file_data_register_real_time_monitor(FileData *fd)
3262 if (!file_data_monitor_pool)
3263 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3265 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3267 DEBUG_1("Register realtime %d %s", count, fd->path);
3270 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3272 if (!realtime_monitor_id)
3274 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
3280 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3284 g_assert(file_data_monitor_pool);
3286 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3288 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3290 g_assert(count > 0);
3295 g_hash_table_remove(file_data_monitor_pool, fd);
3297 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3299 file_data_unref(fd);
3301 if (g_hash_table_size(file_data_monitor_pool) == 0)
3303 g_source_remove(realtime_monitor_id);
3304 realtime_monitor_id = 0;
3312 *-----------------------------------------------------------------------------
3313 * Saving marks list, clearing marks
3314 * Uses file_data_pool
3315 *-----------------------------------------------------------------------------
3318 static void marks_get_files(gpointer key, gpointer value, gpointer userdata)
3320 gchar *file_name = key;
3321 GString *result = userdata;
3324 if (isfile(file_name))
3327 if (fd && fd->marks > 0)
3329 g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks);
3334 gboolean marks_list_load(const gchar *path)
3342 pathl = path_from_utf8(path);
3343 f = fopen(pathl, "r");
3345 if (!f) return FALSE;
3347 /* first line must start with Marks comment */
3348 if (!fgets(s_buf, sizeof(s_buf), f) ||
3349 strncmp(s_buf, "#Marks", 6) != 0)
3355 while (fgets(s_buf, sizeof(s_buf), f))
3357 if (s_buf[0]=='#') continue;
3358 file_path = strtok(s_buf, ",");
3359 marks_value = strtok(NULL, ",");
3360 if (isfile(file_path))
3362 FileData *fd = file_data_new_no_grouping(file_path);
3367 gint mark_no = 1 << n;
3368 if (atoi(marks_value) & mark_no)
3370 file_data_set_mark(fd, n , 1);
3381 gboolean marks_list_save(gchar *path, gboolean save)
3383 SecureSaveInfo *ssi;
3385 GString *marks = g_string_new("");
3387 pathl = path_from_utf8(path);
3388 ssi = secure_open(pathl);
3392 log_printf(_("Error: Unable to write marks lists to: %s\n"), path);
3396 secure_fprintf(ssi, "#Marks lists\n");
3400 g_hash_table_foreach(file_data_pool, marks_get_files, marks);
3402 secure_fprintf(ssi, "%s", marks->str);
3403 g_string_free(marks, FALSE);
3405 secure_fprintf(ssi, "#end\n");
3406 return (secure_close(ssi) == 0);
3409 static void marks_clear(gpointer key, gpointer value, gpointer UNUSED(userdata))
3411 gchar *file_name = key;
3416 if (isfile(file_name))
3419 if (fd && fd->marks > 0)
3425 if (fd->marks & mark_no)
3427 file_data_set_mark(fd, n , 0);
3435 void marks_clear_all()
3437 g_hash_table_foreach(file_data_pool, marks_clear, NULL);
3440 void file_data_set_page_num(FileData *fd, gint page_num)
3442 if (fd->page_total > 1 && page_num < 0)
3444 fd->page_num = fd->page_total - 1;
3446 else if (fd->page_total > 1 && page_num <= fd->page_total)
3448 fd->page_num = page_num - 1;
3454 file_data_send_notification(fd, NOTIFY_REREAD);
3457 void file_data_inc_page_num(FileData *fd)
3459 if (fd->page_total > 0 && fd->page_num < fd->page_total - 1)
3461 fd->page_num = fd->page_num + 1;
3463 else if (fd->page_total == 0)
3465 fd->page_num = fd->page_num + 1;
3467 file_data_send_notification(fd, NOTIFY_REREAD);
3470 void file_data_dec_page_num(FileData *fd)
3472 if (fd->page_num > 0)
3474 fd->page_num = fd->page_num - 1;
3476 file_data_send_notification(fd, NOTIFY_REREAD);
3479 void file_data_set_page_total(FileData *fd, gint page_total)
3481 fd->page_total = page_total;
3484 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */