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 */
1164 if (options->file_sort.case_sensitive)
1165 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1167 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1169 if (ret != 0) return ret;
1171 /* do not return 0 unless the files are really the same
1172 file_data_pool ensures that original_path is unique
1174 return strcmp(fa->original_path, fb->original_path);
1177 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1179 filelist_sort_method = method;
1180 filelist_sort_ascend = ascend;
1181 return filelist_sort_compare_filedata(fa, fb);
1184 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1186 return filelist_sort_compare_filedata(a, b);
1189 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1191 filelist_sort_method = method;
1192 filelist_sort_ascend = ascend;
1193 return g_list_sort(list, cb);
1196 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1198 filelist_sort_method = method;
1199 filelist_sort_ascend = ascend;
1200 return g_list_insert_sorted(list, data, cb);
1203 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1205 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1208 //GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
1210 //return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1214 *-----------------------------------------------------------------------------
1215 * basename hash - grouping of sidecars in filelist
1216 *-----------------------------------------------------------------------------
1220 static GHashTable *file_data_basename_hash_new(void)
1222 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1225 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1228 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1230 list = g_hash_table_lookup(basename_hash, basename);
1234 DEBUG_1("TG: basename_hash not found for %s",fd->path);
1235 const gchar *parent_extension = registered_extension_from_path(basename);
1237 if (parent_extension)
1239 DEBUG_1("TG: parent extension %s",parent_extension);
1240 gchar *parent_basename = g_strndup(basename, parent_extension - basename);
1241 DEBUG_1("TG: parent basename %s",parent_basename);
1242 FileData *parent_fd = g_hash_table_lookup(file_data_pool, basename);
1245 DEBUG_1("TG: parent fd found");
1246 list = g_hash_table_lookup(basename_hash, parent_basename);
1247 if (!g_list_find(list, parent_fd))
1249 DEBUG_1("TG: parent fd doesn't fit");
1250 g_free(parent_basename);
1256 basename = parent_basename;
1257 fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
1263 if (!g_list_find(list, fd))
1265 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1266 g_hash_table_insert(basename_hash, basename, list);
1275 static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
1277 file_data_basename_hash_insert((GHashTable *)basename_hash, (FileData *)fd);
1280 static void file_data_basename_hash_remove_list(gpointer UNUSED(key), gpointer value, gpointer UNUSED(data))
1282 filelist_free((GList *)value);
1285 static void file_data_basename_hash_free(GHashTable *basename_hash)
1287 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1288 g_hash_table_destroy(basename_hash);
1292 *-----------------------------------------------------------------------------
1293 * handling sidecars in filelist
1294 *-----------------------------------------------------------------------------
1297 static GList *filelist_filter_out_sidecars(GList *flist)
1299 GList *work = flist;
1300 GList *flist_filtered = NULL;
1304 FileData *fd = work->data;
1307 if (fd->parent) /* remove fd's that are children */
1308 file_data_unref(fd);
1310 flist_filtered = g_list_prepend(flist_filtered, fd);
1314 return flist_filtered;
1317 static void file_data_basename_hash_to_sidecars(gpointer UNUSED(key), gpointer value, gpointer UNUSED(data))
1319 GList *basename_list = (GList *)value;
1320 file_data_check_sidecars(basename_list);
1324 static gboolean is_hidden_file(const gchar *name)
1326 if (name[0] != '.') return FALSE;
1327 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1332 *-----------------------------------------------------------------------------
1333 * the main filelist function
1334 *-----------------------------------------------------------------------------
1337 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1342 GList *dlist = NULL;
1343 GList *flist = NULL;
1344 GList *xmp_files = NULL;
1345 gint (*stat_func)(const gchar *path, struct stat *buf);
1346 GHashTable *basename_hash = NULL;
1348 g_assert(files || dirs);
1350 if (files) *files = NULL;
1351 if (dirs) *dirs = NULL;
1353 pathl = path_from_utf8(dir_path);
1354 if (!pathl) return FALSE;
1356 dp = opendir(pathl);
1363 if (files) basename_hash = file_data_basename_hash_new();
1365 if (follow_symlinks)
1370 while ((dir = readdir(dp)) != NULL)
1372 struct stat ent_sbuf;
1373 const gchar *name = dir->d_name;
1376 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1379 filepath = g_build_filename(pathl, name, NULL);
1380 if (stat_func(filepath, &ent_sbuf) >= 0)
1382 if (S_ISDIR(ent_sbuf.st_mode))
1384 /* we ignore the .thumbnails dir for cleanliness */
1386 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1387 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1388 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1389 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1391 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1396 if (files && filter_name_exists(name))
1398 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1399 flist = g_list_prepend(flist, fd);
1400 if (fd->sidecar_priority && !fd->disable_grouping)
1402 if (strcmp(fd->extension, ".xmp") != 0)
1403 file_data_basename_hash_insert(basename_hash, fd);
1405 xmp_files = g_list_append(xmp_files, fd);
1412 if (errno == EOVERFLOW)
1414 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1426 g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
1427 g_list_free(xmp_files);
1430 if (dirs) *dirs = dlist;
1434 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1436 *files = filelist_filter_out_sidecars(flist);
1438 if (basename_hash) file_data_basename_hash_free(basename_hash);
1443 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1445 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1448 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1450 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1453 FileData *file_data_new_group(const gchar *path_utf8)
1460 if (!stat_utf8(path_utf8, &st))
1466 if (S_ISDIR(st.st_mode))
1467 return file_data_new(path_utf8, &st, TRUE);
1469 dir = remove_level_from_path(path_utf8);
1471 filelist_read_real(dir, &files, NULL, TRUE);
1473 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1474 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1480 filelist_free(files);
1486 void filelist_free(GList *list)
1493 file_data_unref((FileData *)work->data);
1501 GList *filelist_copy(GList *list)
1503 GList *new_list = NULL;
1514 new_list = g_list_prepend(new_list, file_data_ref(fd));
1517 return g_list_reverse(new_list);
1520 GList *filelist_from_path_list(GList *list)
1522 GList *new_list = NULL;
1533 new_list = g_list_prepend(new_list, file_data_new_group(path));
1536 return g_list_reverse(new_list);
1539 GList *filelist_to_path_list(GList *list)
1541 GList *new_list = NULL;
1552 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1555 return g_list_reverse(new_list);
1558 GList *filelist_filter(GList *list, gboolean is_dir_list)
1562 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1567 FileData *fd = (FileData *)(work->data);
1568 const gchar *name = fd->name;
1570 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1571 (!is_dir_list && !filter_name_exists(name)) ||
1572 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1573 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1577 list = g_list_remove_link(list, link);
1578 file_data_unref(fd);
1589 *-----------------------------------------------------------------------------
1590 * filelist recursive
1591 *-----------------------------------------------------------------------------
1594 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1596 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1599 GList *filelist_sort_path(GList *list)
1601 return g_list_sort(list, filelist_sort_path_cb);
1604 static void filelist_recursive_append(GList **list, GList *dirs)
1611 FileData *fd = (FileData *)(work->data);
1615 if (filelist_read(fd, &f, &d))
1617 f = filelist_filter(f, FALSE);
1618 f = filelist_sort_path(f);
1619 *list = g_list_concat(*list, f);
1621 d = filelist_filter(d, TRUE);
1622 d = filelist_sort_path(d);
1623 filelist_recursive_append(list, d);
1631 static void filelist_recursive_append_full(GList **list, GList *dirs, SortType method, gboolean ascend)
1638 FileData *fd = (FileData *)(work->data);
1642 if (filelist_read(fd, &f, &d))
1644 f = filelist_filter(f, FALSE);
1645 f = filelist_sort_full(f, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1646 *list = g_list_concat(*list, f);
1648 d = filelist_filter(d, TRUE);
1649 d = filelist_sort_path(d);
1650 filelist_recursive_append_full(list, d, method, ascend);
1658 GList *filelist_recursive(FileData *dir_fd)
1663 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1664 list = filelist_filter(list, FALSE);
1665 list = filelist_sort_path(list);
1667 d = filelist_filter(d, TRUE);
1668 d = filelist_sort_path(d);
1669 filelist_recursive_append(&list, d);
1675 GList *filelist_recursive_full(FileData *dir_fd, SortType method, gboolean ascend)
1680 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1681 list = filelist_filter(list, FALSE);
1682 list = filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1684 d = filelist_filter(d, TRUE);
1685 d = filelist_sort_path(d);
1686 filelist_recursive_append_full(&list, d, method, ascend);
1693 *-----------------------------------------------------------------------------
1694 * file modification support
1695 *-----------------------------------------------------------------------------
1699 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1701 if (!fdci && fd) fdci = fd->change;
1705 g_free(fdci->source);
1710 if (fd) fd->change = NULL;
1713 static gboolean file_data_can_write_directly(FileData *fd)
1715 return filter_name_is_writable(fd->extension);
1718 static gboolean file_data_can_write_sidecar(FileData *fd)
1720 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1723 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1725 gchar *sidecar_path = NULL;
1728 if (!file_data_can_write_sidecar(fd)) return NULL;
1730 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1731 gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1734 FileData *sfd = work->data;
1736 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1738 sidecar_path = g_strdup(sfd->path);
1742 g_free(extended_extension);
1744 if (!existing_only && !sidecar_path)
1746 if (options->metadata.sidecar_extended_name)
1747 sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1750 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1751 sidecar_path = g_strconcat(base, ".xmp", NULL);
1756 return sidecar_path;
1760 * marks and orientation
1763 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1764 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1765 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1766 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1768 gboolean file_data_get_mark(FileData *fd, gint n)
1770 gboolean valid = (fd->valid_marks & (1 << n));
1772 if (file_data_get_mark_func[n] && !valid)
1774 guint old = fd->marks;
1775 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1777 if (!value != !(fd->marks & (1 << n)))
1779 fd->marks = fd->marks ^ (1 << n);
1782 fd->valid_marks |= (1 << n);
1783 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1785 file_data_unref(fd);
1787 else if (!old && fd->marks)
1793 return !!(fd->marks & (1 << n));
1796 guint file_data_get_marks(FileData *fd)
1799 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1803 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1806 if (!value == !file_data_get_mark(fd, n)) return;
1808 if (file_data_set_mark_func[n])
1810 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1815 fd->marks = fd->marks ^ (1 << n);
1817 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1819 file_data_unref(fd);
1821 else if (!old && fd->marks)
1826 file_data_increment_version(fd);
1827 file_data_send_notification(fd, NOTIFY_MARKS);
1830 gboolean file_data_filter_marks(FileData *fd, guint filter)
1833 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1834 return ((fd->marks & filter) == filter);
1837 GList *file_data_filter_marks_list(GList *list, guint filter)
1844 FileData *fd = work->data;
1848 if (!file_data_filter_marks(fd, filter))
1850 list = g_list_remove_link(list, link);
1851 file_data_unref(fd);
1859 gboolean file_data_filter_file_filter(FileData *fd, GRegex *filter)
1861 return g_regex_match(filter, fd->name, 0, NULL);
1864 GList *file_data_filter_file_filter_list(GList *list, GRegex *filter)
1871 FileData *fd = work->data;
1875 if (!file_data_filter_file_filter(fd, filter))
1877 list = g_list_remove_link(list, link);
1878 file_data_unref(fd);
1886 static gboolean file_data_filter_class(FileData *fd, guint filter)
1890 for (i = 0; i < FILE_FORMAT_CLASSES; i++)
1892 if (filter & (1 << i))
1894 if ((FileFormatClass)i == filter_file_get_class(fd->path))
1904 GList *file_data_filter_class_list(GList *list, guint filter)
1911 FileData *fd = work->data;
1915 if (!file_data_filter_class(fd, filter))
1917 list = g_list_remove_link(list, link);
1918 file_data_unref(fd);
1926 static void file_data_notify_mark_func(gpointer UNUSED(key), gpointer value, gpointer UNUSED(user_data))
1928 FileData *fd = value;
1929 file_data_increment_version(fd);
1930 file_data_send_notification(fd, NOTIFY_MARKS);
1933 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1935 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1937 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1939 file_data_get_mark_func[n] = get_mark_func;
1940 file_data_set_mark_func[n] = set_mark_func;
1941 file_data_mark_func_data[n] = data;
1942 file_data_destroy_mark_func[n] = notify;
1944 if (get_mark_func && file_data_pool)
1946 /* this effectively changes all known files */
1947 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1953 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1955 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1956 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1957 if (data) *data = file_data_mark_func_data[n];
1960 //gint file_data_get_user_orientation(FileData *fd)
1962 //return fd->user_orientation;
1965 //void file_data_set_user_orientation(FileData *fd, gint value)
1967 //if (fd->user_orientation == value) return;
1969 //fd->user_orientation = value;
1970 //file_data_increment_version(fd);
1971 //file_data_send_notification(fd, NOTIFY_ORIENTATION);
1976 * file_data - operates on the given fd
1977 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1981 /* return list of sidecar file extensions in a string */
1982 gchar *file_data_sc_list_to_string(FileData *fd)
1985 GString *result = g_string_new("");
1987 work = fd->sidecar_files;
1990 FileData *sfd = work->data;
1992 result = g_string_append(result, "+ ");
1993 result = g_string_append(result, sfd->extension);
1995 if (work) result = g_string_append_c(result, ' ');
1998 return g_string_free(result, FALSE);
2004 * add FileDataChangeInfo (see typedefs.h) for the given operation
2005 * uses file_data_add_change_info
2007 * fails if the fd->change already exists - change operations can't run in parallel
2008 * fd->change_info works as a lock
2010 * dest can be NULL - in this case the current name is used for now, it will
2015 FileDataChangeInfo types:
2017 MOVE - path is changed, name may be changed too
2018 RENAME - path remains unchanged, name is changed
2019 extension should remain (FIXME should we allow editing extension? it will make problems with grouping)
2020 sidecar names are changed too, extensions are not changed
2022 UPDATE - file size, date or grouping has been changed
2025 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
2027 FileDataChangeInfo *fdci;
2029 if (fd->change) return FALSE;
2031 fdci = g_new0(FileDataChangeInfo, 1);
2036 fdci->source = g_strdup(src);
2038 fdci->source = g_strdup(fd->path);
2041 fdci->dest = g_strdup(dest);
2048 static void file_data_planned_change_remove(FileData *fd)
2050 if (file_data_planned_change_hash &&
2051 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
2053 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
2055 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
2056 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
2057 file_data_unref(fd);
2058 if (g_hash_table_size(file_data_planned_change_hash) == 0)
2060 g_hash_table_destroy(file_data_planned_change_hash);
2061 file_data_planned_change_hash = NULL;
2062 DEBUG_1("planned change: empty");
2069 void file_data_free_ci(FileData *fd)
2071 FileDataChangeInfo *fdci = fd->change;
2075 file_data_planned_change_remove(fd);
2077 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
2079 g_free(fdci->source);
2087 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
2089 FileDataChangeInfo *fdci = fd->change;
2091 fdci->regroup_when_finished = enable;
2094 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
2098 if (fd->parent) fd = fd->parent;
2100 if (fd->change) return FALSE;
2102 work = fd->sidecar_files;
2105 FileData *sfd = work->data;
2107 if (sfd->change) return FALSE;
2111 file_data_add_ci(fd, type, NULL, NULL);
2113 work = fd->sidecar_files;
2116 FileData *sfd = work->data;
2118 file_data_add_ci(sfd, type, NULL, NULL);
2125 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
2129 if (fd->parent) fd = fd->parent;
2131 if (!fd->change || fd->change->type != type) return FALSE;
2133 work = fd->sidecar_files;
2136 FileData *sfd = work->data;
2138 if (!sfd->change || sfd->change->type != type) return FALSE;
2146 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
2148 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
2149 file_data_sc_update_ci_copy(fd, dest_path);
2153 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
2155 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
2156 file_data_sc_update_ci_move(fd, dest_path);
2160 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
2162 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
2163 file_data_sc_update_ci_rename(fd, dest_path);
2167 gboolean file_data_sc_add_ci_delete(FileData *fd)
2169 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
2172 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
2174 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
2175 file_data_sc_update_ci_unspecified(fd, dest_path);
2179 gboolean file_data_add_ci_write_metadata(FileData *fd)
2181 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
2184 void file_data_sc_free_ci(FileData *fd)
2188 if (fd->parent) fd = fd->parent;
2190 file_data_free_ci(fd);
2192 work = fd->sidecar_files;
2195 FileData *sfd = work->data;
2197 file_data_free_ci(sfd);
2202 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2205 gboolean ret = TRUE;
2210 FileData *fd = work->data;
2212 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2219 static void file_data_sc_revert_ci_list(GList *fd_list)
2226 FileData *fd = work->data;
2228 file_data_sc_free_ci(fd);
2233 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2240 FileData *fd = work->data;
2242 if (!func(fd, dest))
2244 file_data_sc_revert_ci_list(work->prev);
2253 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2255 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2258 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2260 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2263 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2265 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2268 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2270 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2273 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2276 gboolean ret = TRUE;
2281 FileData *fd = work->data;
2283 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2290 void file_data_free_ci_list(GList *fd_list)
2297 FileData *fd = work->data;
2299 file_data_free_ci(fd);
2304 void file_data_sc_free_ci_list(GList *fd_list)
2311 FileData *fd = work->data;
2313 file_data_sc_free_ci(fd);
2319 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2320 * fails if fd->change does not exist or the change type does not match
2323 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2325 FileDataChangeType type = fd->change->type;
2327 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2331 if (!file_data_planned_change_hash)
2332 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2334 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2336 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2337 g_hash_table_remove(file_data_planned_change_hash, old_path);
2338 file_data_unref(fd);
2341 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
2346 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2347 g_hash_table_remove(file_data_planned_change_hash, new_path);
2348 file_data_unref(ofd);
2351 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2353 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2358 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2360 gchar *old_path = fd->change->dest;
2362 fd->change->dest = g_strdup(dest_path);
2363 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2367 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2369 const gchar *extension = registered_extension_from_path(fd->change->source);
2370 gchar *base = remove_extension_from_path(dest_path);
2371 gchar *old_path = fd->change->dest;
2373 fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2374 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2380 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2383 gchar *dest_path_full = NULL;
2385 if (fd->parent) fd = fd->parent;
2389 dest_path = fd->path;
2391 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2393 gchar *dir = remove_level_from_path(fd->path);
2395 dest_path_full = g_build_filename(dir, dest_path, NULL);
2397 dest_path = dest_path_full;
2399 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2401 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2402 dest_path = dest_path_full;
2405 file_data_update_ci_dest(fd, dest_path);
2407 work = fd->sidecar_files;
2410 FileData *sfd = work->data;
2412 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2416 g_free(dest_path_full);
2419 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2421 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2422 file_data_sc_update_ci(fd, dest_path);
2426 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2428 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2431 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2433 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2436 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2438 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2441 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2443 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2446 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2448 gboolean (*func)(FileData *, const gchar *))
2451 gboolean ret = TRUE;
2456 FileData *fd = work->data;
2458 if (!func(fd, dest)) ret = FALSE;
2465 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2467 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2470 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2472 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2475 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2477 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2482 * verify source and dest paths - dest image exists, etc.
2483 * it should detect all possible problems with the planned operation
2486 gint file_data_verify_ci(FileData *fd, GList *list)
2488 gint ret = CHANGE_OK;
2491 FileData *fd1 = NULL;
2495 DEBUG_1("Change checked: no change info: %s", fd->path);
2499 if (!isname(fd->path))
2501 /* this probably should not happen */
2502 ret |= CHANGE_NO_SRC;
2503 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2507 dir = remove_level_from_path(fd->path);
2509 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2510 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2511 fd->change->type != FILEDATA_CHANGE_RENAME &&
2512 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2515 ret |= CHANGE_WARN_UNSAVED_META;
2516 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2519 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2520 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2521 !access_file(fd->path, R_OK))
2523 ret |= CHANGE_NO_READ_PERM;
2524 DEBUG_1("Change checked: no read permission: %s", fd->path);
2526 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2527 !access_file(dir, W_OK))
2529 ret |= CHANGE_NO_WRITE_PERM_DIR;
2530 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2532 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2533 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2534 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2535 !access_file(fd->path, W_OK))
2537 ret |= CHANGE_WARN_NO_WRITE_PERM;
2538 DEBUG_1("Change checked: no write permission: %s", fd->path);
2540 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2541 - that means that there are no hard errors and warnings can be disabled
2542 - the destination is determined during the check
2544 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2546 /* determine destination file */
2547 gboolean have_dest = FALSE;
2548 gchar *dest_dir = NULL;
2550 if (options->metadata.save_in_image_file)
2552 if (file_data_can_write_directly(fd))
2554 /* we can write the file directly */
2555 if (access_file(fd->path, W_OK))
2561 if (options->metadata.warn_on_write_problems)
2563 ret |= CHANGE_WARN_NO_WRITE_PERM;
2564 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2568 else if (file_data_can_write_sidecar(fd))
2570 /* we can write sidecar */
2571 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2572 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2574 file_data_update_ci_dest(fd, sidecar);
2579 if (options->metadata.warn_on_write_problems)
2581 ret |= CHANGE_WARN_NO_WRITE_PERM;
2582 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2591 /* write private metadata file under ~/.geeqie */
2593 /* If an existing metadata file exists, we will try writing to
2594 * it's location regardless of the user's preference.
2596 gchar *metadata_path = NULL;
2598 /* but ignore XMP if we are not able to write it */
2599 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2601 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2603 if (metadata_path && !access_file(metadata_path, W_OK))
2605 g_free(metadata_path);
2606 metadata_path = NULL;
2613 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2614 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2616 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2618 metadata_path = g_build_filename(dest_dir, filename, NULL);
2622 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2624 file_data_update_ci_dest(fd, metadata_path);
2629 ret |= CHANGE_NO_WRITE_PERM_DEST;
2630 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2632 g_free(metadata_path);
2637 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2642 same = (strcmp(fd->path, fd->change->dest) == 0);
2646 const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2647 if (!dest_ext) dest_ext = "";
2648 if (!options->file_filter.disable_file_extension_checks)
2650 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2652 ret |= CHANGE_WARN_CHANGED_EXT;
2653 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2659 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /** @FIXME this is now needed for running editors */
2661 ret |= CHANGE_WARN_SAME;
2662 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2666 dest_dir = remove_level_from_path(fd->change->dest);
2668 if (!isdir(dest_dir))
2670 ret |= CHANGE_NO_DEST_DIR;
2671 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2673 else if (!access_file(dest_dir, W_OK))
2675 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2676 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2680 if (isfile(fd->change->dest))
2682 if (!access_file(fd->change->dest, W_OK))
2684 ret |= CHANGE_NO_WRITE_PERM_DEST;
2685 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2689 ret |= CHANGE_WARN_DEST_EXISTS;
2690 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2693 else if (isdir(fd->change->dest))
2695 ret |= CHANGE_DEST_EXISTS;
2696 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2703 /* During a rename operation, check if another planned destination file has
2706 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2707 fd->change->type == FILEDATA_CHANGE_COPY ||
2708 fd->change->type == FILEDATA_CHANGE_MOVE)
2715 if (fd1 != NULL && fd != fd1 )
2717 if (!strcmp(fd->change->dest, fd1->change->dest))
2719 ret |= CHANGE_DUPLICATE_DEST;
2725 fd->change->error = ret;
2726 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2733 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2738 ret = file_data_verify_ci(fd, list);
2740 work = fd->sidecar_files;
2743 FileData *sfd = work->data;
2745 ret |= file_data_verify_ci(sfd, list);
2752 gchar *file_data_get_error_string(gint error)
2754 GString *result = g_string_new("");
2756 if (error & CHANGE_NO_SRC)
2758 if (result->len > 0) g_string_append(result, ", ");
2759 g_string_append(result, _("file or directory does not exist"));
2762 if (error & CHANGE_DEST_EXISTS)
2764 if (result->len > 0) g_string_append(result, ", ");
2765 g_string_append(result, _("destination already exists"));
2768 if (error & CHANGE_NO_WRITE_PERM_DEST)
2770 if (result->len > 0) g_string_append(result, ", ");
2771 g_string_append(result, _("destination can't be overwritten"));
2774 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2776 if (result->len > 0) g_string_append(result, ", ");
2777 g_string_append(result, _("destination directory is not writable"));
2780 if (error & CHANGE_NO_DEST_DIR)
2782 if (result->len > 0) g_string_append(result, ", ");
2783 g_string_append(result, _("destination directory does not exist"));
2786 if (error & CHANGE_NO_WRITE_PERM_DIR)
2788 if (result->len > 0) g_string_append(result, ", ");
2789 g_string_append(result, _("source directory is not writable"));
2792 if (error & CHANGE_NO_READ_PERM)
2794 if (result->len > 0) g_string_append(result, ", ");
2795 g_string_append(result, _("no read permission"));
2798 if (error & CHANGE_WARN_NO_WRITE_PERM)
2800 if (result->len > 0) g_string_append(result, ", ");
2801 g_string_append(result, _("file is readonly"));
2804 if (error & CHANGE_WARN_DEST_EXISTS)
2806 if (result->len > 0) g_string_append(result, ", ");
2807 g_string_append(result, _("destination already exists and will be overwritten"));
2810 if (error & CHANGE_WARN_SAME)
2812 if (result->len > 0) g_string_append(result, ", ");
2813 g_string_append(result, _("source and destination are the same"));
2816 if (error & CHANGE_WARN_CHANGED_EXT)
2818 if (result->len > 0) g_string_append(result, ", ");
2819 g_string_append(result, _("source and destination have different extension"));
2822 if (error & CHANGE_WARN_UNSAVED_META)
2824 if (result->len > 0) g_string_append(result, ", ");
2825 g_string_append(result, _("there are unsaved metadata changes for the file"));
2828 if (error & CHANGE_DUPLICATE_DEST)
2830 if (result->len > 0) g_string_append(result, ", ");
2831 g_string_append(result, _("another destination file has the same filename"));
2834 return g_string_free(result, FALSE);
2837 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2840 gint all_errors = 0;
2841 gint common_errors = ~0;
2846 if (!list) return 0;
2848 num = g_list_length(list);
2849 errors = g_new(int, num);
2860 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2861 all_errors |= error;
2862 common_errors &= error;
2869 if (desc && all_errors)
2872 GString *result = g_string_new("");
2876 gchar *str = file_data_get_error_string(common_errors);
2877 g_string_append(result, str);
2878 g_string_append(result, "\n");
2892 error = errors[i] & ~common_errors;
2896 gchar *str = file_data_get_error_string(error);
2897 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2902 *desc = g_string_free(result, FALSE);
2911 * perform the change described by FileFataChangeInfo
2912 * it is used for internal operations,
2913 * this function actually operates with files on the filesystem
2914 * it should implement safe delete
2917 static gboolean file_data_perform_move(FileData *fd)
2919 g_assert(!strcmp(fd->change->source, fd->path));
2920 return move_file(fd->change->source, fd->change->dest);
2923 static gboolean file_data_perform_copy(FileData *fd)
2925 g_assert(!strcmp(fd->change->source, fd->path));
2926 return copy_file(fd->change->source, fd->change->dest);
2929 static gboolean file_data_perform_delete(FileData *fd)
2931 if (isdir(fd->path) && !islink(fd->path))
2932 return rmdir_utf8(fd->path);
2934 if (options->file_ops.safe_delete_enable)
2935 return file_util_safe_unlink(fd->path);
2937 return unlink_file(fd->path);
2940 gboolean file_data_perform_ci(FileData *fd)
2942 FileDataChangeType type = fd->change->type;
2946 case FILEDATA_CHANGE_MOVE:
2947 return file_data_perform_move(fd);
2948 case FILEDATA_CHANGE_COPY:
2949 return file_data_perform_copy(fd);
2950 case FILEDATA_CHANGE_RENAME:
2951 return file_data_perform_move(fd); /* the same as move */
2952 case FILEDATA_CHANGE_DELETE:
2953 return file_data_perform_delete(fd);
2954 case FILEDATA_CHANGE_WRITE_METADATA:
2955 return metadata_write_perform(fd);
2956 case FILEDATA_CHANGE_UNSPECIFIED:
2957 /* nothing to do here */
2965 gboolean file_data_sc_perform_ci(FileData *fd)
2968 gboolean ret = TRUE;
2969 FileDataChangeType type = fd->change->type;
2971 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2973 work = fd->sidecar_files;
2976 FileData *sfd = work->data;
2978 if (!file_data_perform_ci(sfd)) ret = FALSE;
2982 if (!file_data_perform_ci(fd)) ret = FALSE;
2988 * updates FileData structure according to FileDataChangeInfo
2991 gboolean file_data_apply_ci(FileData *fd)
2993 FileDataChangeType type = fd->change->type;
2995 /** @FIXME delete ?*/
2996 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2998 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2999 file_data_planned_change_remove(fd);
3001 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
3003 /* this change overwrites another file which is already known to other modules
3004 renaming fd would create duplicate FileData structure
3005 the best thing we can do is nothing
3007 /** @FIXME maybe we could copy stuff like marks
3009 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
3013 file_data_set_path(fd, fd->change->dest);
3016 file_data_increment_version(fd);
3017 file_data_send_notification(fd, NOTIFY_CHANGE);
3022 gboolean file_data_sc_apply_ci(FileData *fd)
3025 FileDataChangeType type = fd->change->type;
3027 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3029 work = fd->sidecar_files;
3032 FileData *sfd = work->data;
3034 file_data_apply_ci(sfd);
3038 file_data_apply_ci(fd);
3043 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
3046 if (fd->parent) fd = fd->parent;
3047 if (!g_list_find(list, fd)) return FALSE;
3049 work = fd->sidecar_files;
3052 if (!g_list_find(list, work->data)) return FALSE;
3058 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
3063 /* change partial groups to independent files */
3068 FileData *fd = work->data;
3071 if (!file_data_list_contains_whole_group(list, fd))
3073 file_data_disable_grouping(fd, TRUE);
3076 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
3082 /* remove sidecars from the list,
3083 they can be still accessed via main_fd->sidecar_files */
3087 FileData *fd = work->data;
3091 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
3093 out = g_list_prepend(out, file_data_ref(fd));
3097 filelist_free(list);
3098 out = g_list_reverse(out);
3108 * notify other modules about the change described by FileDataChangeInfo
3111 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks */
3112 /** @FIXME do we need the ignore_list? It looks like a workaround for ineffective
3113 implementation in view-file-list.cc */
3116 typedef struct _NotifyIdleData NotifyIdleData;
3118 struct _NotifyIdleData {
3124 typedef struct _NotifyData NotifyData;
3126 struct _NotifyData {
3127 FileDataNotifyFunc func;
3129 NotifyPriority priority;
3132 static GList *notify_func_list = NULL;
3134 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
3136 NotifyData *nda = (NotifyData *)a;
3137 NotifyData *ndb = (NotifyData *)b;
3139 if (nda->priority < ndb->priority) return -1;
3140 if (nda->priority > ndb->priority) return 1;
3144 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
3147 GList *work = notify_func_list;
3151 NotifyData *nd = (NotifyData *)work->data;
3153 if (nd->func == func && nd->data == data)
3155 g_warning("Notify func already registered");
3161 nd = g_new(NotifyData, 1);
3164 nd->priority = priority;
3166 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
3167 DEBUG_2("Notify func registered: %p", (void *)nd);
3172 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
3174 GList *work = notify_func_list;
3178 NotifyData *nd = (NotifyData *)work->data;
3180 if (nd->func == func && nd->data == data)
3182 notify_func_list = g_list_delete_link(notify_func_list, work);
3184 DEBUG_2("Notify func unregistered: %p", (void *)nd);
3190 g_warning("Notify func not found");
3195 //gboolean file_data_send_notification_idle_cb(gpointer data)
3197 //NotifyIdleData *nid = (NotifyIdleData *)data;
3198 //GList *work = notify_func_list;
3202 //NotifyData *nd = (NotifyData *)work->data;
3204 //nd->func(nid->fd, nid->type, nd->data);
3205 //work = work->next;
3207 //file_data_unref(nid->fd);
3212 void file_data_send_notification(FileData *fd, NotifyType type)
3214 GList *work = notify_func_list;
3218 NotifyData *nd = (NotifyData *)work->data;
3220 nd->func(fd, type, nd->data);
3224 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3225 nid->fd = file_data_ref(fd);
3227 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3231 static GHashTable *file_data_monitor_pool = NULL;
3232 static guint realtime_monitor_id = 0; /* event source id */
3234 static void realtime_monitor_check_cb(gpointer key, gpointer UNUSED(value), gpointer UNUSED(data))
3238 file_data_check_changed_files(fd);
3240 DEBUG_1("monitor %s", fd->path);
3243 static gboolean realtime_monitor_cb(gpointer UNUSED(data))
3245 if (!options->update_on_time_change) return TRUE;
3246 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
3250 gboolean file_data_register_real_time_monitor(FileData *fd)
3256 if (!file_data_monitor_pool)
3257 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3259 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3261 DEBUG_1("Register realtime %d %s", count, fd->path);
3264 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3266 if (!realtime_monitor_id)
3268 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
3274 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3278 g_assert(file_data_monitor_pool);
3280 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3282 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3284 g_assert(count > 0);
3289 g_hash_table_remove(file_data_monitor_pool, fd);
3291 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3293 file_data_unref(fd);
3295 if (g_hash_table_size(file_data_monitor_pool) == 0)
3297 g_source_remove(realtime_monitor_id);
3298 realtime_monitor_id = 0;
3306 *-----------------------------------------------------------------------------
3307 * Saving marks list, clearing marks
3308 * Uses file_data_pool
3309 *-----------------------------------------------------------------------------
3312 static void marks_get_files(gpointer key, gpointer value, gpointer userdata)
3314 gchar *file_name = key;
3315 GString *result = userdata;
3318 if (isfile(file_name))
3321 if (fd && fd->marks > 0)
3323 g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks);
3328 gboolean marks_list_load(const gchar *path)
3336 pathl = path_from_utf8(path);
3337 f = fopen(pathl, "r");
3339 if (!f) return FALSE;
3341 /* first line must start with Marks comment */
3342 if (!fgets(s_buf, sizeof(s_buf), f) ||
3343 strncmp(s_buf, "#Marks", 6) != 0)
3349 while (fgets(s_buf, sizeof(s_buf), f))
3351 if (s_buf[0]=='#') continue;
3352 file_path = strtok(s_buf, ",");
3353 marks_value = strtok(NULL, ",");
3354 if (isfile(file_path))
3356 FileData *fd = file_data_new_no_grouping(file_path);
3361 gint mark_no = 1 << n;
3362 if (atoi(marks_value) & mark_no)
3364 file_data_set_mark(fd, n , 1);
3375 gboolean marks_list_save(gchar *path, gboolean save)
3377 SecureSaveInfo *ssi;
3379 GString *marks = g_string_new("");
3381 pathl = path_from_utf8(path);
3382 ssi = secure_open(pathl);
3386 log_printf(_("Error: Unable to write marks lists to: %s\n"), path);
3390 secure_fprintf(ssi, "#Marks lists\n");
3394 g_hash_table_foreach(file_data_pool, marks_get_files, marks);
3396 secure_fprintf(ssi, "%s", marks->str);
3397 g_string_free(marks, FALSE);
3399 secure_fprintf(ssi, "#end\n");
3400 return (secure_close(ssi) == 0);
3403 static void marks_clear(gpointer key, gpointer value, gpointer UNUSED(userdata))
3405 gchar *file_name = key;
3410 if (isfile(file_name))
3413 if (fd && fd->marks > 0)
3419 if (fd->marks & mark_no)
3421 file_data_set_mark(fd, n , 0);
3429 void marks_clear_all()
3431 g_hash_table_foreach(file_data_pool, marks_clear, NULL);
3434 void file_data_set_page_num(FileData *fd, gint page_num)
3436 if (fd->page_total > 1 && page_num < 0)
3438 fd->page_num = fd->page_total - 1;
3440 else if (fd->page_total > 1 && page_num <= fd->page_total)
3442 fd->page_num = page_num - 1;
3448 file_data_send_notification(fd, NOTIFY_REREAD);
3451 void file_data_inc_page_num(FileData *fd)
3453 if (fd->page_total > 0 && fd->page_num < fd->page_total - 1)
3455 fd->page_num = fd->page_num + 1;
3457 else if (fd->page_total == 0)
3459 fd->page_num = fd->page_num + 1;
3461 file_data_send_notification(fd, NOTIFY_REREAD);
3464 void file_data_dec_page_num(FileData *fd)
3466 if (fd->page_num > 0)
3468 fd->page_num = fd->page_num - 1;
3470 file_data_send_notification(fd, NOTIFY_REREAD);
3473 void file_data_set_page_total(FileData *fd, gint page_total)
3475 fd->page_total = page_total;
3478 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */