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"
41 gint global_file_data_count = 0;
44 static GHashTable *file_data_pool = NULL;
45 static GHashTable *file_data_planned_change_hash = NULL;
47 static gint sidecar_file_priority(const gchar *extension);
48 static void file_data_check_sidecars(const GList *basename_list);
49 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
52 static SortType filelist_sort_method = SORT_NONE;
53 static gboolean filelist_sort_ascend = TRUE;
56 *-----------------------------------------------------------------------------
57 * text conversion utils
58 *-----------------------------------------------------------------------------
61 gchar *text_from_size(gint64 size)
67 /* what I would like to use is printf("%'d", size)
68 * BUT: not supported on every libc :(
72 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
73 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
77 a = g_strdup_printf("%d", (guint)size);
83 b = g_new(gchar, l + n + 1);
108 gchar *text_from_size_abrev(gint64 size)
110 if (size < (gint64)1024)
112 return g_strdup_printf(_("%d bytes"), (gint)size);
114 if (size < (gint64)1048576)
116 return g_strdup_printf(_("%.1f KiB"), (gdouble)size / 1024.0);
118 if (size < (gint64)1073741824)
120 return g_strdup_printf(_("%.1f MiB"), (gdouble)size / 1048576.0);
123 /* to avoid overflowing the gdouble, do division in two steps */
125 return g_strdup_printf(_("%.1f GiB"), (gdouble)size / 1024.0);
128 /* note: returned string is valid until next call to text_from_time() */
129 const gchar *text_from_time(time_t t)
131 static gchar *ret = NULL;
135 GError *error = NULL;
137 btime = localtime(&t);
139 /* the %x warning about 2 digit years is not an error */
140 buflen = strftime(buf, sizeof(buf), "%x %X", btime);
141 if (buflen < 1) return "";
144 ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
147 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
156 *-----------------------------------------------------------------------------
157 * changed files detection and notification
158 *-----------------------------------------------------------------------------
161 void file_data_increment_version(FileData *fd)
167 fd->parent->version++;
168 fd->parent->valid_marks = 0;
172 static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st)
174 if (fd->size != st->st_size ||
175 fd->date != st->st_mtime)
177 fd->size = st->st_size;
178 fd->date = st->st_mtime;
179 fd->cdate = st->st_ctime;
180 fd->mode = st->st_mode;
181 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
182 fd->thumb_pixbuf = NULL;
183 file_data_increment_version(fd);
184 file_data_send_notification(fd, NOTIFY_REREAD);
190 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
192 gboolean ret = FALSE;
195 ret = file_data_check_changed_single_file(fd, st);
197 work = fd->sidecar_files;
200 FileData *sfd = work->data;
204 if (!stat_utf8(sfd->path, &st))
209 file_data_disconnect_sidecar_file(fd, sfd);
211 file_data_increment_version(sfd);
212 file_data_send_notification(sfd, NOTIFY_REREAD);
213 file_data_unref(sfd);
217 ret |= file_data_check_changed_files_recursive(sfd, &st);
223 gboolean file_data_check_changed_files(FileData *fd)
225 gboolean ret = FALSE;
228 if (fd->parent) fd = fd->parent;
230 if (!stat_utf8(fd->path, &st))
234 FileData *sfd = NULL;
236 /* parent is missing, we have to rebuild whole group */
241 /* file_data_disconnect_sidecar_file might delete the file,
242 we have to keep the reference to prevent this */
243 sidecars = filelist_copy(fd->sidecar_files);
251 file_data_disconnect_sidecar_file(fd, sfd);
253 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
254 /* now we can release the sidecars */
255 filelist_free(sidecars);
256 file_data_increment_version(fd);
257 file_data_send_notification(fd, NOTIFY_REREAD);
262 ret |= file_data_check_changed_files_recursive(fd, &st);
269 *-----------------------------------------------------------------------------
270 * file name, extension, sorting, ...
271 *-----------------------------------------------------------------------------
274 static void file_data_set_collate_keys(FileData *fd)
276 gchar *caseless_name;
279 valid_name = g_filename_display_name(fd->name);
280 caseless_name = g_utf8_casefold(valid_name, -1);
282 g_free(fd->collate_key_name);
283 g_free(fd->collate_key_name_nocase);
285 #if GTK_CHECK_VERSION(2, 8, 0)
286 if (options->file_sort.natural)
288 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
289 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
293 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
294 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
297 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
298 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
302 g_free(caseless_name);
305 static void file_data_set_path(FileData *fd, const gchar *path)
307 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
308 g_assert(file_data_pool);
312 if (fd->original_path)
314 g_hash_table_remove(file_data_pool, fd->original_path);
315 g_free(fd->original_path);
318 g_assert(!g_hash_table_lookup(file_data_pool, path));
320 fd->original_path = g_strdup(path);
321 g_hash_table_insert(file_data_pool, fd->original_path, fd);
323 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
325 fd->path = g_strdup(path);
327 fd->extension = fd->name + 1;
328 file_data_set_collate_keys(fd);
332 fd->path = g_strdup(path);
333 fd->name = filename_from_path(fd->path);
335 if (strcmp(fd->name, "..") == 0)
337 gchar *dir = remove_level_from_path(path);
339 fd->path = remove_level_from_path(dir);
342 fd->extension = fd->name + 2;
343 file_data_set_collate_keys(fd);
346 else if (strcmp(fd->name, ".") == 0)
349 fd->path = remove_level_from_path(path);
351 fd->extension = fd->name + 1;
352 file_data_set_collate_keys(fd);
356 fd->extension = registered_extension_from_path(fd->path);
357 if (fd->extension == NULL)
359 fd->extension = fd->name + strlen(fd->name);
362 fd->sidecar_priority = sidecar_file_priority(fd->extension);
363 file_data_set_collate_keys(fd);
367 *-----------------------------------------------------------------------------
368 * create or reuse Filedata
369 *-----------------------------------------------------------------------------
372 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
378 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
380 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
383 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
385 fd = g_hash_table_lookup(file_data_pool, path_utf8);
391 if (!fd && file_data_planned_change_hash)
393 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
396 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
397 if (!isfile(fd->path))
400 file_data_apply_ci(fd);
413 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
416 changed = file_data_check_changed_single_file(fd, st);
418 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
423 fd = g_new0(FileData, 1);
424 #ifdef DEBUG_FILEDATA
425 global_file_data_count++;
426 DEBUG_2("file data count++: %d", global_file_data_count);
429 fd->size = st->st_size;
430 fd->date = st->st_mtime;
431 fd->cdate = st->st_ctime;
432 fd->mode = st->st_mode;
434 fd->magick = FD_MAGICK;
436 fd->rating = STAR_RATING_NOT_READ;
437 fd->format_class = filter_file_get_class(path_utf8);
441 user = getpwuid(st->st_uid);
444 fd->owner = g_strdup_printf("%u", st->st_uid);
448 fd->owner = g_strdup(user->pw_name);
451 group = getgrgid(st->st_gid);
454 fd->group = g_strdup_printf("%u", st->st_gid);
458 fd->group = g_strdup(group->gr_name);
461 fd->sym_link = get_symbolic_link(path_utf8);
463 if (disable_sidecars) fd->disable_grouping = TRUE;
465 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
470 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
472 gchar *path_utf8 = path_to_utf8(path);
473 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
479 FileData *file_data_new_simple(const gchar *path_utf8)
484 if (!stat_utf8(path_utf8, &st))
490 fd = g_hash_table_lookup(file_data_pool, path_utf8);
491 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
500 void read_exif_time_data(FileData *file)
502 if (file->exifdate > 0)
504 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
515 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
516 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), file, file->path);
521 uint year, month, day, hour, min, sec;
523 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
524 time_str.tm_year = year - 1900;
525 time_str.tm_mon = month - 1;
526 time_str.tm_mday = day;
527 time_str.tm_hour = hour;
528 time_str.tm_min = min;
529 time_str.tm_sec = sec;
530 time_str.tm_isdst = 0;
532 file->exifdate = mktime(&time_str);
538 void read_exif_time_digitized_data(FileData *file)
540 if (file->exifdate_digitized > 0)
542 DEBUG_1("%s set_exif_time_digitized_data: Already exists for %s", get_exec_time(), file->path);
553 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeDigitized");
554 DEBUG_2("%s set_exif_time_digitized_data: reading %p %s", get_exec_time(), file, file->path);
559 uint year, month, day, hour, min, sec;
561 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
562 time_str.tm_year = year - 1900;
563 time_str.tm_mon = month - 1;
564 time_str.tm_mday = day;
565 time_str.tm_hour = hour;
566 time_str.tm_min = min;
567 time_str.tm_sec = sec;
568 time_str.tm_isdst = 0;
570 file->exifdate_digitized = mktime(&time_str);
576 void read_rating_data(FileData *file)
580 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
583 file->rating = atoi(rating_str);
592 void set_exif_time_data(GList *files)
594 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
598 FileData *file = files->data;
600 read_exif_time_data(file);
605 void set_exif_time_digitized_data(GList *files)
607 DEBUG_1("%s set_exif_time_digitized_data: ...", get_exec_time());
611 FileData *file = files->data;
613 read_exif_time_digitized_data(file);
618 void set_rating_data(GList *files)
621 DEBUG_1("%s set_rating_data: ...", get_exec_time());
625 FileData *file = files->data;
626 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
629 file->rating = atoi(rating_str);
636 FileData *file_data_new_no_grouping(const gchar *path_utf8)
640 if (!stat_utf8(path_utf8, &st))
646 return file_data_new(path_utf8, &st, TRUE);
649 FileData *file_data_new_dir(const gchar *path_utf8)
653 if (!stat_utf8(path_utf8, &st))
659 /* dir or non-existing yet */
660 g_assert(S_ISDIR(st.st_mode));
662 return file_data_new(path_utf8, &st, TRUE);
666 *-----------------------------------------------------------------------------
668 *-----------------------------------------------------------------------------
671 #ifdef DEBUG_FILEDATA
672 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
674 FileData *file_data_ref(FileData *fd)
677 if (fd == NULL) return NULL;
678 if (fd->magick != FD_MAGICK)
679 #ifdef DEBUG_FILEDATA
680 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, fd);
682 log_printf("Error: fd magick mismatch fd=%p", fd);
684 g_assert(fd->magick == FD_MAGICK);
687 #ifdef DEBUG_FILEDATA
688 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", fd, fd->ref, fd->path, file, line);
690 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
695 static void file_data_free(FileData *fd)
697 g_assert(fd->magick == FD_MAGICK);
698 g_assert(fd->ref == 0);
699 g_assert(!fd->locked);
701 #ifdef DEBUG_FILEDATA
702 global_file_data_count--;
703 DEBUG_2("file data count--: %d", global_file_data_count);
706 metadata_cache_free(fd);
707 g_hash_table_remove(file_data_pool, fd->original_path);
710 g_free(fd->original_path);
711 g_free(fd->collate_key_name);
712 g_free(fd->collate_key_name_nocase);
713 g_free(fd->extended_extension);
714 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
715 histmap_free(fd->histmap);
718 g_free(fd->sym_link);
719 g_free(fd->format_name);
720 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
722 file_data_change_info_free(NULL, fd);
727 * @brief Checks if the FileData is referenced
729 * Checks the refcount and whether the FileData is locked.
731 static gboolean file_data_check_has_ref(FileData *fd)
733 return fd->ref > 0 || fd->locked;
737 * @brief Consider freeing a FileData.
739 * This function will free a FileData and its children provided that neither its parent nor it has
740 * a positive refcount, and provided that neither is locked.
742 static void file_data_consider_free(FileData *fd)
745 FileData *parent = fd->parent ? fd->parent : fd;
747 g_assert(fd->magick == FD_MAGICK);
748 if (file_data_check_has_ref(fd)) return;
749 if (file_data_check_has_ref(parent)) return;
751 work = parent->sidecar_files;
754 FileData *sfd = work->data;
755 if (file_data_check_has_ref(sfd)) return;
759 /* Neither the parent nor the siblings are referenced, so we can free everything */
760 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
761 fd->path, fd->parent ? parent->path : "-");
763 work = parent->sidecar_files;
766 FileData *sfd = work->data;
771 g_list_free(parent->sidecar_files);
772 parent->sidecar_files = NULL;
774 file_data_free(parent);
777 #ifdef DEBUG_FILEDATA
778 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
780 void file_data_unref(FileData *fd)
783 if (fd == NULL) return;
784 if (fd->magick != FD_MAGICK)
785 #ifdef DEBUG_FILEDATA
786 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, fd);
788 log_printf("Error: fd magick mismatch fd=%p", fd);
790 g_assert(fd->magick == FD_MAGICK);
793 #ifdef DEBUG_FILEDATA
794 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", fd, fd->ref, fd->locked, fd->path,
797 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
800 // Free FileData if it's no longer ref'd
801 file_data_consider_free(fd);
805 * @brief Lock the FileData in memory.
807 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
808 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
809 * even if the code would continue to function properly even if the FileData were freed. Code that
810 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
812 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
813 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
815 void file_data_lock(FileData *fd)
817 if (fd == NULL) return;
818 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", fd);
820 g_assert(fd->magick == FD_MAGICK);
823 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
827 * @brief Reset the maintain-FileData-in-memory lock
829 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
830 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
831 * keeping it from being freed.
833 void file_data_unlock(FileData *fd)
835 if (fd == NULL) return;
836 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", fd);
838 g_assert(fd->magick == FD_MAGICK);
841 // Free FileData if it's no longer ref'd
842 file_data_consider_free(fd);
846 * @brief Lock all of the FileDatas in the provided list
848 * @see file_data_lock(#FileData)
850 void file_data_lock_list(GList *list)
857 FileData *fd = work->data;
864 * @brief Unlock all of the FileDatas in the provided list
866 * @see #file_data_unlock(#FileData)
868 void file_data_unlock_list(GList *list)
875 FileData *fd = work->data;
877 file_data_unlock(fd);
882 *-----------------------------------------------------------------------------
883 * sidecar file info struct
884 *-----------------------------------------------------------------------------
887 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
889 const FileData *fda = a;
890 const FileData *fdb = b;
892 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
893 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
895 return strcmp(fdb->extension, fda->extension);
899 static gint sidecar_file_priority(const gchar *extension)
904 if (extension == NULL)
907 work = sidecar_ext_get_list();
910 gchar *ext = work->data;
913 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
919 static void file_data_check_sidecars(const GList *basename_list)
921 /* basename_list contains the new group - first is the parent, then sorted sidecars */
922 /* all files in the list have ref count > 0 */
925 GList *s_work, *new_sidecars;
928 if (!basename_list) return;
931 DEBUG_2("basename start");
932 work = basename_list;
935 FileData *fd = work->data;
937 g_assert(fd->magick == FD_MAGICK);
938 DEBUG_2("basename: %p %s", fd, fd->name);
941 g_assert(fd->parent->magick == FD_MAGICK);
942 DEBUG_2(" parent: %p", fd->parent);
944 s_work = fd->sidecar_files;
947 FileData *sfd = s_work->data;
948 s_work = s_work->next;
949 g_assert(sfd->magick == FD_MAGICK);
950 DEBUG_2(" sidecar: %p %s", sfd, sfd->name);
953 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
956 parent_fd = basename_list->data;
958 /* check if the second and next entries of basename_list are already connected
959 as sidecars of the first entry (parent_fd) */
960 work = basename_list->next;
961 s_work = parent_fd->sidecar_files;
963 while (work && s_work)
965 if (work->data != s_work->data) break;
967 s_work = s_work->next;
970 if (!work && !s_work)
972 DEBUG_2("basename no change");
973 return; /* no change in grouping */
976 /* we have to regroup it */
978 /* first, disconnect everything and send notification*/
980 work = basename_list;
983 FileData *fd = work->data;
985 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
989 FileData *old_parent = fd->parent;
990 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
991 file_data_ref(old_parent);
992 file_data_disconnect_sidecar_file(old_parent, fd);
993 file_data_send_notification(old_parent, NOTIFY_REREAD);
994 file_data_unref(old_parent);
997 while (fd->sidecar_files)
999 FileData *sfd = fd->sidecar_files->data;
1000 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
1002 file_data_disconnect_sidecar_file(fd, sfd);
1003 file_data_send_notification(sfd, NOTIFY_REREAD);
1004 file_data_unref(sfd);
1006 file_data_send_notification(fd, NOTIFY_GROUPING);
1008 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
1011 /* now we can form the new group */
1012 work = basename_list->next;
1013 new_sidecars = NULL;
1016 FileData *sfd = work->data;
1017 g_assert(sfd->magick == FD_MAGICK);
1018 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
1019 sfd->parent = parent_fd;
1020 new_sidecars = g_list_prepend(new_sidecars, sfd);
1023 g_assert(parent_fd->sidecar_files == NULL);
1024 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
1025 DEBUG_1("basename group changed for %s", parent_fd->path);
1029 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
1031 g_assert(target->magick == FD_MAGICK);
1032 g_assert(sfd->magick == FD_MAGICK);
1033 g_assert(g_list_find(target->sidecar_files, sfd));
1035 file_data_ref(target);
1038 g_assert(sfd->parent == target);
1040 file_data_increment_version(sfd); /* increments both sfd and target */
1042 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
1044 g_free(sfd->extended_extension);
1045 sfd->extended_extension = NULL;
1047 file_data_unref(target);
1048 file_data_unref(sfd);
1051 /* disables / enables grouping for particular file, sends UPDATE notification */
1052 void file_data_disable_grouping(FileData *fd, gboolean disable)
1054 if (!fd->disable_grouping == !disable) return;
1056 fd->disable_grouping = !!disable;
1062 FileData *parent = file_data_ref(fd->parent);
1063 file_data_disconnect_sidecar_file(parent, fd);
1064 file_data_send_notification(parent, NOTIFY_GROUPING);
1065 file_data_unref(parent);
1067 else if (fd->sidecar_files)
1069 GList *sidecar_files = filelist_copy(fd->sidecar_files);
1070 GList *work = sidecar_files;
1073 FileData *sfd = work->data;
1075 file_data_disconnect_sidecar_file(fd, sfd);
1076 file_data_send_notification(sfd, NOTIFY_GROUPING);
1078 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
1079 filelist_free(sidecar_files);
1083 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
1088 file_data_increment_version(fd);
1089 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
1091 file_data_send_notification(fd, NOTIFY_GROUPING);
1094 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
1101 FileData *fd = work->data;
1103 file_data_disable_grouping(fd, disable);
1111 *-----------------------------------------------------------------------------
1113 *-----------------------------------------------------------------------------
1117 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
1120 if (!filelist_sort_ascend)
1127 switch (filelist_sort_method)
1132 if (fa->size < fb->size) return -1;
1133 if (fa->size > fb->size) return 1;
1134 /* fall back to name */
1137 if (fa->date < fb->date) return -1;
1138 if (fa->date > fb->date) return 1;
1139 /* fall back to name */
1142 if (fa->cdate < fb->cdate) return -1;
1143 if (fa->cdate > fb->cdate) return 1;
1144 /* fall back to name */
1147 if (fa->exifdate < fb->exifdate) return -1;
1148 if (fa->exifdate > fb->exifdate) return 1;
1149 /* fall back to name */
1151 case SORT_EXIFTIMEDIGITIZED:
1152 if (fa->exifdate_digitized < fb->exifdate_digitized) return -1;
1153 if (fa->exifdate_digitized > fb->exifdate_digitized) return 1;
1154 /* fall back to name */
1157 if (fa->rating < fb->rating) return -1;
1158 if (fa->rating > fb->rating) return 1;
1159 /* fall back to name */
1162 if (fa->format_class < fb->format_class) return -1;
1163 if (fa->format_class > fb->format_class) return 1;
1164 /* fall back to name */
1166 #ifdef HAVE_STRVERSCMP
1168 ret = strverscmp(fa->name, fb->name);
1169 if (ret != 0) return ret;
1176 if (options->file_sort.case_sensitive)
1177 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1179 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1181 if (ret != 0) return ret;
1183 /* do not return 0 unless the files are really the same
1184 file_data_pool ensures that original_path is unique
1186 return strcmp(fa->original_path, fb->original_path);
1189 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1191 filelist_sort_method = method;
1192 filelist_sort_ascend = ascend;
1193 return filelist_sort_compare_filedata(fa, fb);
1196 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1198 return filelist_sort_compare_filedata(a, b);
1201 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1203 filelist_sort_method = method;
1204 filelist_sort_ascend = ascend;
1205 return g_list_sort(list, cb);
1208 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1210 filelist_sort_method = method;
1211 filelist_sort_ascend = ascend;
1212 return g_list_insert_sorted(list, data, cb);
1215 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1217 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1220 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
1222 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1226 *-----------------------------------------------------------------------------
1227 * basename hash - grouping of sidecars in filelist
1228 *-----------------------------------------------------------------------------
1232 static GHashTable *file_data_basename_hash_new(void)
1234 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1237 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1240 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1242 list = g_hash_table_lookup(basename_hash, basename);
1246 DEBUG_1("TG: basename_hash not found for %s",fd->path);
1247 const gchar *parent_extension = registered_extension_from_path(basename);
1249 if (parent_extension)
1251 DEBUG_1("TG: parent extension %s",parent_extension);
1252 gchar *parent_basename = g_strndup(basename, parent_extension - basename);
1253 DEBUG_1("TG: parent basename %s",parent_basename);
1254 FileData *parent_fd = g_hash_table_lookup(file_data_pool, basename);
1257 DEBUG_1("TG: parent fd found");
1258 list = g_hash_table_lookup(basename_hash, parent_basename);
1259 if (!g_list_find(list, parent_fd))
1261 DEBUG_1("TG: parent fd doesn't fit");
1262 g_free(parent_basename);
1268 basename = parent_basename;
1269 fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
1275 if (!g_list_find(list, fd))
1277 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1278 g_hash_table_insert(basename_hash, basename, list);
1287 static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
1289 file_data_basename_hash_insert((GHashTable *)basename_hash, (FileData *)fd);
1292 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
1294 filelist_free((GList *)value);
1297 static void file_data_basename_hash_free(GHashTable *basename_hash)
1299 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1300 g_hash_table_destroy(basename_hash);
1304 *-----------------------------------------------------------------------------
1305 * handling sidecars in filelist
1306 *-----------------------------------------------------------------------------
1309 static GList *filelist_filter_out_sidecars(GList *flist)
1311 GList *work = flist;
1312 GList *flist_filtered = NULL;
1316 FileData *fd = work->data;
1319 if (fd->parent) /* remove fd's that are children */
1320 file_data_unref(fd);
1322 flist_filtered = g_list_prepend(flist_filtered, fd);
1326 return flist_filtered;
1329 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
1331 GList *basename_list = (GList *)value;
1332 file_data_check_sidecars(basename_list);
1336 static gboolean is_hidden_file(const gchar *name)
1338 if (name[0] != '.') return FALSE;
1339 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1344 *-----------------------------------------------------------------------------
1345 * the main filelist function
1346 *-----------------------------------------------------------------------------
1349 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1354 GList *dlist = NULL;
1355 GList *flist = NULL;
1356 GList *xmp_files = NULL;
1357 gint (*stat_func)(const gchar *path, struct stat *buf);
1358 GHashTable *basename_hash = NULL;
1360 g_assert(files || dirs);
1362 if (files) *files = NULL;
1363 if (dirs) *dirs = NULL;
1365 pathl = path_from_utf8(dir_path);
1366 if (!pathl) return FALSE;
1368 dp = opendir(pathl);
1375 if (files) basename_hash = file_data_basename_hash_new();
1377 if (follow_symlinks)
1382 while ((dir = readdir(dp)) != NULL)
1384 struct stat ent_sbuf;
1385 const gchar *name = dir->d_name;
1388 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1391 filepath = g_build_filename(pathl, name, NULL);
1392 if (stat_func(filepath, &ent_sbuf) >= 0)
1394 if (S_ISDIR(ent_sbuf.st_mode))
1396 /* we ignore the .thumbnails dir for cleanliness */
1398 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1399 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1400 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1401 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1403 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1408 if (files && filter_name_exists(name))
1410 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1411 flist = g_list_prepend(flist, fd);
1412 if (fd->sidecar_priority && !fd->disable_grouping)
1414 if (strcmp(fd->extension, ".xmp") != 0)
1415 file_data_basename_hash_insert(basename_hash, fd);
1417 xmp_files = g_list_append(xmp_files, fd);
1424 if (errno == EOVERFLOW)
1426 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1438 g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
1439 g_list_free(xmp_files);
1442 if (dirs) *dirs = dlist;
1446 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1448 *files = filelist_filter_out_sidecars(flist);
1450 if (basename_hash) file_data_basename_hash_free(basename_hash);
1455 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1457 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1460 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1462 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1465 FileData *file_data_new_group(const gchar *path_utf8)
1472 if (!stat_utf8(path_utf8, &st))
1478 if (S_ISDIR(st.st_mode))
1479 return file_data_new(path_utf8, &st, TRUE);
1481 dir = remove_level_from_path(path_utf8);
1483 filelist_read_real(dir, &files, NULL, TRUE);
1485 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1486 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1492 filelist_free(files);
1498 void filelist_free(GList *list)
1505 file_data_unref((FileData *)work->data);
1513 GList *filelist_copy(GList *list)
1515 GList *new_list = NULL;
1526 new_list = g_list_prepend(new_list, file_data_ref(fd));
1529 return g_list_reverse(new_list);
1532 GList *filelist_from_path_list(GList *list)
1534 GList *new_list = NULL;
1545 new_list = g_list_prepend(new_list, file_data_new_group(path));
1548 return g_list_reverse(new_list);
1551 GList *filelist_to_path_list(GList *list)
1553 GList *new_list = NULL;
1564 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1567 return g_list_reverse(new_list);
1570 GList *filelist_filter(GList *list, gboolean is_dir_list)
1574 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1579 FileData *fd = (FileData *)(work->data);
1580 const gchar *name = fd->name;
1582 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1583 (!is_dir_list && !filter_name_exists(name)) ||
1584 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1585 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1589 list = g_list_remove_link(list, link);
1590 file_data_unref(fd);
1601 *-----------------------------------------------------------------------------
1602 * filelist recursive
1603 *-----------------------------------------------------------------------------
1606 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1608 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1611 GList *filelist_sort_path(GList *list)
1613 return g_list_sort(list, filelist_sort_path_cb);
1616 static void filelist_recursive_append(GList **list, GList *dirs)
1623 FileData *fd = (FileData *)(work->data);
1627 if (filelist_read(fd, &f, &d))
1629 f = filelist_filter(f, FALSE);
1630 f = filelist_sort_path(f);
1631 *list = g_list_concat(*list, f);
1633 d = filelist_filter(d, TRUE);
1634 d = filelist_sort_path(d);
1635 filelist_recursive_append(list, d);
1643 static void filelist_recursive_append_full(GList **list, GList *dirs, SortType method, gboolean ascend)
1650 FileData *fd = (FileData *)(work->data);
1654 if (filelist_read(fd, &f, &d))
1656 f = filelist_filter(f, FALSE);
1657 f = filelist_sort_full(f, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1658 *list = g_list_concat(*list, f);
1660 d = filelist_filter(d, TRUE);
1661 d = filelist_sort_path(d);
1662 filelist_recursive_append_full(list, d, method, ascend);
1670 GList *filelist_recursive(FileData *dir_fd)
1675 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1676 list = filelist_filter(list, FALSE);
1677 list = filelist_sort_path(list);
1679 d = filelist_filter(d, TRUE);
1680 d = filelist_sort_path(d);
1681 filelist_recursive_append(&list, d);
1687 GList *filelist_recursive_full(FileData *dir_fd, SortType method, gboolean ascend)
1692 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1693 list = filelist_filter(list, FALSE);
1694 list = filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1696 d = filelist_filter(d, TRUE);
1697 d = filelist_sort_path(d);
1698 filelist_recursive_append_full(&list, d, method, ascend);
1705 *-----------------------------------------------------------------------------
1706 * file modification support
1707 *-----------------------------------------------------------------------------
1711 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1713 if (!fdci && fd) fdci = fd->change;
1717 g_free(fdci->source);
1722 if (fd) fd->change = NULL;
1725 static gboolean file_data_can_write_directly(FileData *fd)
1727 return filter_name_is_writable(fd->extension);
1730 static gboolean file_data_can_write_sidecar(FileData *fd)
1732 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1735 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1737 gchar *sidecar_path = NULL;
1740 if (!file_data_can_write_sidecar(fd)) return NULL;
1742 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1743 gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1746 FileData *sfd = work->data;
1748 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1750 sidecar_path = g_strdup(sfd->path);
1754 g_free(extended_extension);
1756 if (!existing_only && !sidecar_path)
1758 if (options->metadata.sidecar_extended_name)
1759 sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1762 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1763 sidecar_path = g_strconcat(base, ".xmp", NULL);
1768 return sidecar_path;
1772 * marks and orientation
1775 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1776 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1777 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1778 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1780 gboolean file_data_get_mark(FileData *fd, gint n)
1782 gboolean valid = (fd->valid_marks & (1 << n));
1784 if (file_data_get_mark_func[n] && !valid)
1786 guint old = fd->marks;
1787 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1789 if (!value != !(fd->marks & (1 << n)))
1791 fd->marks = fd->marks ^ (1 << n);
1794 fd->valid_marks |= (1 << n);
1795 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1797 file_data_unref(fd);
1799 else if (!old && fd->marks)
1805 return !!(fd->marks & (1 << n));
1808 guint file_data_get_marks(FileData *fd)
1811 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1815 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1818 if (!value == !file_data_get_mark(fd, n)) return;
1820 if (file_data_set_mark_func[n])
1822 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1827 fd->marks = fd->marks ^ (1 << n);
1829 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1831 file_data_unref(fd);
1833 else if (!old && fd->marks)
1838 file_data_increment_version(fd);
1839 file_data_send_notification(fd, NOTIFY_MARKS);
1842 gboolean file_data_filter_marks(FileData *fd, guint filter)
1845 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1846 return ((fd->marks & filter) == filter);
1849 GList *file_data_filter_marks_list(GList *list, guint filter)
1856 FileData *fd = work->data;
1860 if (!file_data_filter_marks(fd, filter))
1862 list = g_list_remove_link(list, link);
1863 file_data_unref(fd);
1871 gboolean file_data_filter_file_filter(FileData *fd, GRegex *filter)
1873 return g_regex_match(filter, fd->name, 0, NULL);
1876 GList *file_data_filter_file_filter_list(GList *list, GRegex *filter)
1883 FileData *fd = work->data;
1887 if (!file_data_filter_file_filter(fd, filter))
1889 list = g_list_remove_link(list, link);
1890 file_data_unref(fd);
1898 static gboolean file_data_filter_class(FileData *fd, guint filter)
1902 for (i = 0; i < FILE_FORMAT_CLASSES; i++)
1904 if (filter & (1 << i))
1906 if ((FileFormatClass)i == filter_file_get_class(fd->path))
1916 GList *file_data_filter_class_list(GList *list, guint filter)
1923 FileData *fd = work->data;
1927 if (!file_data_filter_class(fd, filter))
1929 list = g_list_remove_link(list, link);
1930 file_data_unref(fd);
1938 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1940 FileData *fd = value;
1941 file_data_increment_version(fd);
1942 file_data_send_notification(fd, NOTIFY_MARKS);
1945 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1947 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1949 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1951 file_data_get_mark_func[n] = get_mark_func;
1952 file_data_set_mark_func[n] = set_mark_func;
1953 file_data_mark_func_data[n] = data;
1954 file_data_destroy_mark_func[n] = notify;
1956 if (get_mark_func && file_data_pool)
1958 /* this effectively changes all known files */
1959 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1965 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1967 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1968 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1969 if (data) *data = file_data_mark_func_data[n];
1972 gint file_data_get_user_orientation(FileData *fd)
1974 return fd->user_orientation;
1977 void file_data_set_user_orientation(FileData *fd, gint value)
1979 if (fd->user_orientation == value) return;
1981 fd->user_orientation = value;
1982 file_data_increment_version(fd);
1983 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1988 * file_data - operates on the given fd
1989 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1993 /* return list of sidecar file extensions in a string */
1994 gchar *file_data_sc_list_to_string(FileData *fd)
1997 GString *result = g_string_new("");
1999 work = fd->sidecar_files;
2002 FileData *sfd = work->data;
2004 result = g_string_append(result, "+ ");
2005 result = g_string_append(result, sfd->extension);
2007 if (work) result = g_string_append_c(result, ' ');
2010 return g_string_free(result, FALSE);
2016 * add FileDataChangeInfo (see typedefs.h) for the given operation
2017 * uses file_data_add_change_info
2019 * fails if the fd->change already exists - change operations can't run in parallel
2020 * fd->change_info works as a lock
2022 * dest can be NULL - in this case the current name is used for now, it will
2027 FileDataChangeInfo types:
2029 MOVE - path is changed, name may be changed too
2030 RENAME - path remains unchanged, name is changed
2031 extension should remain (FIXME should we allow editing extension? it will make problems with grouping)
2032 sidecar names are changed too, extensions are not changed
2034 UPDATE - file size, date or grouping has been changed
2037 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
2039 FileDataChangeInfo *fdci;
2041 if (fd->change) return FALSE;
2043 fdci = g_new0(FileDataChangeInfo, 1);
2048 fdci->source = g_strdup(src);
2050 fdci->source = g_strdup(fd->path);
2053 fdci->dest = g_strdup(dest);
2060 static void file_data_planned_change_remove(FileData *fd)
2062 if (file_data_planned_change_hash &&
2063 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
2065 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
2067 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
2068 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
2069 file_data_unref(fd);
2070 if (g_hash_table_size(file_data_planned_change_hash) == 0)
2072 g_hash_table_destroy(file_data_planned_change_hash);
2073 file_data_planned_change_hash = NULL;
2074 DEBUG_1("planned change: empty");
2081 void file_data_free_ci(FileData *fd)
2083 FileDataChangeInfo *fdci = fd->change;
2087 file_data_planned_change_remove(fd);
2089 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
2091 g_free(fdci->source);
2099 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
2101 FileDataChangeInfo *fdci = fd->change;
2103 fdci->regroup_when_finished = enable;
2106 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
2110 if (fd->parent) fd = fd->parent;
2112 if (fd->change) return FALSE;
2114 work = fd->sidecar_files;
2117 FileData *sfd = work->data;
2119 if (sfd->change) return FALSE;
2123 file_data_add_ci(fd, type, NULL, NULL);
2125 work = fd->sidecar_files;
2128 FileData *sfd = work->data;
2130 file_data_add_ci(sfd, type, NULL, NULL);
2137 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
2141 if (fd->parent) fd = fd->parent;
2143 if (!fd->change || fd->change->type != type) return FALSE;
2145 work = fd->sidecar_files;
2148 FileData *sfd = work->data;
2150 if (!sfd->change || sfd->change->type != type) return FALSE;
2158 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
2160 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
2161 file_data_sc_update_ci_copy(fd, dest_path);
2165 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
2167 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
2168 file_data_sc_update_ci_move(fd, dest_path);
2172 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
2174 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
2175 file_data_sc_update_ci_rename(fd, dest_path);
2179 gboolean file_data_sc_add_ci_delete(FileData *fd)
2181 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
2184 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
2186 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
2187 file_data_sc_update_ci_unspecified(fd, dest_path);
2191 gboolean file_data_add_ci_write_metadata(FileData *fd)
2193 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
2196 void file_data_sc_free_ci(FileData *fd)
2200 if (fd->parent) fd = fd->parent;
2202 file_data_free_ci(fd);
2204 work = fd->sidecar_files;
2207 FileData *sfd = work->data;
2209 file_data_free_ci(sfd);
2214 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2217 gboolean ret = TRUE;
2222 FileData *fd = work->data;
2224 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2231 static void file_data_sc_revert_ci_list(GList *fd_list)
2238 FileData *fd = work->data;
2240 file_data_sc_free_ci(fd);
2245 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2252 FileData *fd = work->data;
2254 if (!func(fd, dest))
2256 file_data_sc_revert_ci_list(work->prev);
2265 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2267 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2270 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2272 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2275 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2277 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2280 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2282 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2285 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2288 gboolean ret = TRUE;
2293 FileData *fd = work->data;
2295 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2302 void file_data_free_ci_list(GList *fd_list)
2309 FileData *fd = work->data;
2311 file_data_free_ci(fd);
2316 void file_data_sc_free_ci_list(GList *fd_list)
2323 FileData *fd = work->data;
2325 file_data_sc_free_ci(fd);
2331 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2332 * fails if fd->change does not exist or the change type does not match
2335 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2337 FileDataChangeType type = fd->change->type;
2339 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2343 if (!file_data_planned_change_hash)
2344 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2346 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2348 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2349 g_hash_table_remove(file_data_planned_change_hash, old_path);
2350 file_data_unref(fd);
2353 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
2358 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2359 g_hash_table_remove(file_data_planned_change_hash, new_path);
2360 file_data_unref(ofd);
2363 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2365 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2370 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2372 gchar *old_path = fd->change->dest;
2374 fd->change->dest = g_strdup(dest_path);
2375 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2379 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2381 const gchar *extension = registered_extension_from_path(fd->change->source);
2382 gchar *base = remove_extension_from_path(dest_path);
2383 gchar *old_path = fd->change->dest;
2385 fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2386 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2392 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2395 gchar *dest_path_full = NULL;
2397 if (fd->parent) fd = fd->parent;
2401 dest_path = fd->path;
2403 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2405 gchar *dir = remove_level_from_path(fd->path);
2407 dest_path_full = g_build_filename(dir, dest_path, NULL);
2409 dest_path = dest_path_full;
2411 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2413 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2414 dest_path = dest_path_full;
2417 file_data_update_ci_dest(fd, dest_path);
2419 work = fd->sidecar_files;
2422 FileData *sfd = work->data;
2424 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2428 g_free(dest_path_full);
2431 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2433 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2434 file_data_sc_update_ci(fd, dest_path);
2438 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2440 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2443 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2445 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2448 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2450 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2453 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2455 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2458 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2460 gboolean (*func)(FileData *, const gchar *))
2463 gboolean ret = TRUE;
2468 FileData *fd = work->data;
2470 if (!func(fd, dest)) ret = FALSE;
2477 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2479 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2482 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2484 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2487 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2489 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2494 * verify source and dest paths - dest image exists, etc.
2495 * it should detect all possible problems with the planned operation
2498 gint file_data_verify_ci(FileData *fd, GList *list)
2500 gint ret = CHANGE_OK;
2503 FileData *fd1 = NULL;
2507 DEBUG_1("Change checked: no change info: %s", fd->path);
2511 if (!isname(fd->path))
2513 /* this probably should not happen */
2514 ret |= CHANGE_NO_SRC;
2515 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2519 dir = remove_level_from_path(fd->path);
2521 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2522 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2523 fd->change->type != FILEDATA_CHANGE_RENAME &&
2524 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2527 ret |= CHANGE_WARN_UNSAVED_META;
2528 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2531 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2532 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2533 !access_file(fd->path, R_OK))
2535 ret |= CHANGE_NO_READ_PERM;
2536 DEBUG_1("Change checked: no read permission: %s", fd->path);
2538 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2539 !access_file(dir, W_OK))
2541 ret |= CHANGE_NO_WRITE_PERM_DIR;
2542 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2544 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2545 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2546 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2547 !access_file(fd->path, W_OK))
2549 ret |= CHANGE_WARN_NO_WRITE_PERM;
2550 DEBUG_1("Change checked: no write permission: %s", fd->path);
2552 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2553 - that means that there are no hard errors and warnings can be disabled
2554 - the destination is determined during the check
2556 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2558 /* determine destination file */
2559 gboolean have_dest = FALSE;
2560 gchar *dest_dir = NULL;
2562 if (options->metadata.save_in_image_file)
2564 if (file_data_can_write_directly(fd))
2566 /* we can write the file directly */
2567 if (access_file(fd->path, W_OK))
2573 if (options->metadata.warn_on_write_problems)
2575 ret |= CHANGE_WARN_NO_WRITE_PERM;
2576 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2580 else if (file_data_can_write_sidecar(fd))
2582 /* we can write sidecar */
2583 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2584 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2586 file_data_update_ci_dest(fd, sidecar);
2591 if (options->metadata.warn_on_write_problems)
2593 ret |= CHANGE_WARN_NO_WRITE_PERM;
2594 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2603 /* write private metadata file under ~/.geeqie */
2605 /* If an existing metadata file exists, we will try writing to
2606 * it's location regardless of the user's preference.
2608 gchar *metadata_path = NULL;
2610 /* but ignore XMP if we are not able to write it */
2611 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2613 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2615 if (metadata_path && !access_file(metadata_path, W_OK))
2617 g_free(metadata_path);
2618 metadata_path = NULL;
2625 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2626 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2628 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2630 metadata_path = g_build_filename(dest_dir, filename, NULL);
2634 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2636 file_data_update_ci_dest(fd, metadata_path);
2641 ret |= CHANGE_NO_WRITE_PERM_DEST;
2642 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2644 g_free(metadata_path);
2649 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2654 same = (strcmp(fd->path, fd->change->dest) == 0);
2658 const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2659 if (!dest_ext) dest_ext = "";
2660 if (!options->file_filter.disable_file_extension_checks)
2662 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2664 ret |= CHANGE_WARN_CHANGED_EXT;
2665 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2671 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /** @FIXME this is now needed for running editors */
2673 ret |= CHANGE_WARN_SAME;
2674 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2678 dest_dir = remove_level_from_path(fd->change->dest);
2680 if (!isdir(dest_dir))
2682 ret |= CHANGE_NO_DEST_DIR;
2683 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2685 else if (!access_file(dest_dir, W_OK))
2687 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2688 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2692 if (isfile(fd->change->dest))
2694 if (!access_file(fd->change->dest, W_OK))
2696 ret |= CHANGE_NO_WRITE_PERM_DEST;
2697 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2701 ret |= CHANGE_WARN_DEST_EXISTS;
2702 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2705 else if (isdir(fd->change->dest))
2707 ret |= CHANGE_DEST_EXISTS;
2708 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2715 /* During a rename operation, check if another planned destination file has
2718 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2719 fd->change->type == FILEDATA_CHANGE_COPY ||
2720 fd->change->type == FILEDATA_CHANGE_MOVE)
2727 if (fd1 != NULL && fd != fd1 )
2729 if (!strcmp(fd->change->dest, fd1->change->dest))
2731 ret |= CHANGE_DUPLICATE_DEST;
2737 fd->change->error = ret;
2738 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2745 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2750 ret = file_data_verify_ci(fd, list);
2752 work = fd->sidecar_files;
2755 FileData *sfd = work->data;
2757 ret |= file_data_verify_ci(sfd, list);
2764 gchar *file_data_get_error_string(gint error)
2766 GString *result = g_string_new("");
2768 if (error & CHANGE_NO_SRC)
2770 if (result->len > 0) g_string_append(result, ", ");
2771 g_string_append(result, _("file or directory does not exist"));
2774 if (error & CHANGE_DEST_EXISTS)
2776 if (result->len > 0) g_string_append(result, ", ");
2777 g_string_append(result, _("destination already exists"));
2780 if (error & CHANGE_NO_WRITE_PERM_DEST)
2782 if (result->len > 0) g_string_append(result, ", ");
2783 g_string_append(result, _("destination can't be overwritten"));
2786 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2788 if (result->len > 0) g_string_append(result, ", ");
2789 g_string_append(result, _("destination directory is not writable"));
2792 if (error & CHANGE_NO_DEST_DIR)
2794 if (result->len > 0) g_string_append(result, ", ");
2795 g_string_append(result, _("destination directory does not exist"));
2798 if (error & CHANGE_NO_WRITE_PERM_DIR)
2800 if (result->len > 0) g_string_append(result, ", ");
2801 g_string_append(result, _("source directory is not writable"));
2804 if (error & CHANGE_NO_READ_PERM)
2806 if (result->len > 0) g_string_append(result, ", ");
2807 g_string_append(result, _("no read permission"));
2810 if (error & CHANGE_WARN_NO_WRITE_PERM)
2812 if (result->len > 0) g_string_append(result, ", ");
2813 g_string_append(result, _("file is readonly"));
2816 if (error & CHANGE_WARN_DEST_EXISTS)
2818 if (result->len > 0) g_string_append(result, ", ");
2819 g_string_append(result, _("destination already exists and will be overwritten"));
2822 if (error & CHANGE_WARN_SAME)
2824 if (result->len > 0) g_string_append(result, ", ");
2825 g_string_append(result, _("source and destination are the same"));
2828 if (error & CHANGE_WARN_CHANGED_EXT)
2830 if (result->len > 0) g_string_append(result, ", ");
2831 g_string_append(result, _("source and destination have different extension"));
2834 if (error & CHANGE_WARN_UNSAVED_META)
2836 if (result->len > 0) g_string_append(result, ", ");
2837 g_string_append(result, _("there are unsaved metadata changes for the file"));
2840 if (error & CHANGE_DUPLICATE_DEST)
2842 if (result->len > 0) g_string_append(result, ", ");
2843 g_string_append(result, _("another destination file has the same filename"));
2846 return g_string_free(result, FALSE);
2849 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2852 gint all_errors = 0;
2853 gint common_errors = ~0;
2858 if (!list) return 0;
2860 num = g_list_length(list);
2861 errors = g_new(int, num);
2872 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2873 all_errors |= error;
2874 common_errors &= error;
2881 if (desc && all_errors)
2884 GString *result = g_string_new("");
2888 gchar *str = file_data_get_error_string(common_errors);
2889 g_string_append(result, str);
2890 g_string_append(result, "\n");
2904 error = errors[i] & ~common_errors;
2908 gchar *str = file_data_get_error_string(error);
2909 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2914 *desc = g_string_free(result, FALSE);
2923 * perform the change described by FileFataChangeInfo
2924 * it is used for internal operations,
2925 * this function actually operates with files on the filesystem
2926 * it should implement safe delete
2929 static gboolean file_data_perform_move(FileData *fd)
2931 g_assert(!strcmp(fd->change->source, fd->path));
2932 return move_file(fd->change->source, fd->change->dest);
2935 static gboolean file_data_perform_copy(FileData *fd)
2937 g_assert(!strcmp(fd->change->source, fd->path));
2938 return copy_file(fd->change->source, fd->change->dest);
2941 static gboolean file_data_perform_delete(FileData *fd)
2943 if (isdir(fd->path) && !islink(fd->path))
2944 return rmdir_utf8(fd->path);
2946 if (options->file_ops.safe_delete_enable)
2947 return file_util_safe_unlink(fd->path);
2949 return unlink_file(fd->path);
2952 gboolean file_data_perform_ci(FileData *fd)
2954 FileDataChangeType type = fd->change->type;
2958 case FILEDATA_CHANGE_MOVE:
2959 return file_data_perform_move(fd);
2960 case FILEDATA_CHANGE_COPY:
2961 return file_data_perform_copy(fd);
2962 case FILEDATA_CHANGE_RENAME:
2963 return file_data_perform_move(fd); /* the same as move */
2964 case FILEDATA_CHANGE_DELETE:
2965 return file_data_perform_delete(fd);
2966 case FILEDATA_CHANGE_WRITE_METADATA:
2967 return metadata_write_perform(fd);
2968 case FILEDATA_CHANGE_UNSPECIFIED:
2969 /* nothing to do here */
2977 gboolean file_data_sc_perform_ci(FileData *fd)
2980 gboolean ret = TRUE;
2981 FileDataChangeType type = fd->change->type;
2983 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2985 work = fd->sidecar_files;
2988 FileData *sfd = work->data;
2990 if (!file_data_perform_ci(sfd)) ret = FALSE;
2994 if (!file_data_perform_ci(fd)) ret = FALSE;
3000 * updates FileData structure according to FileDataChangeInfo
3003 gboolean file_data_apply_ci(FileData *fd)
3005 FileDataChangeType type = fd->change->type;
3007 /** @FIXME delete ?*/
3008 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
3010 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
3011 file_data_planned_change_remove(fd);
3013 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
3015 /* this change overwrites another file which is already known to other modules
3016 renaming fd would create duplicate FileData structure
3017 the best thing we can do is nothing
3019 /** @FIXME maybe we could copy stuff like marks
3021 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
3025 file_data_set_path(fd, fd->change->dest);
3028 file_data_increment_version(fd);
3029 file_data_send_notification(fd, NOTIFY_CHANGE);
3034 gboolean file_data_sc_apply_ci(FileData *fd)
3037 FileDataChangeType type = fd->change->type;
3039 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3041 work = fd->sidecar_files;
3044 FileData *sfd = work->data;
3046 file_data_apply_ci(sfd);
3050 file_data_apply_ci(fd);
3055 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
3058 if (fd->parent) fd = fd->parent;
3059 if (!g_list_find(list, fd)) return FALSE;
3061 work = fd->sidecar_files;
3064 if (!g_list_find(list, work->data)) return FALSE;
3070 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
3075 /* change partial groups to independent files */
3080 FileData *fd = work->data;
3083 if (!file_data_list_contains_whole_group(list, fd))
3085 file_data_disable_grouping(fd, TRUE);
3088 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
3094 /* remove sidecars from the list,
3095 they can be still accessed via main_fd->sidecar_files */
3099 FileData *fd = work->data;
3103 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
3105 out = g_list_prepend(out, file_data_ref(fd));
3109 filelist_free(list);
3110 out = g_list_reverse(out);
3120 * notify other modules about the change described by FileDataChangeInfo
3123 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks */
3124 /** @FIXME do we need the ignore_list? It looks like a workaround for ineffective
3125 implementation in view_file_list.c */
3128 typedef struct _NotifyIdleData NotifyIdleData;
3130 struct _NotifyIdleData {
3136 typedef struct _NotifyData NotifyData;
3138 struct _NotifyData {
3139 FileDataNotifyFunc func;
3141 NotifyPriority priority;
3144 static GList *notify_func_list = NULL;
3146 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
3148 NotifyData *nda = (NotifyData *)a;
3149 NotifyData *ndb = (NotifyData *)b;
3151 if (nda->priority < ndb->priority) return -1;
3152 if (nda->priority > ndb->priority) return 1;
3156 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
3159 GList *work = notify_func_list;
3163 NotifyData *nd = (NotifyData *)work->data;
3165 if (nd->func == func && nd->data == data)
3167 g_warning("Notify func already registered");
3173 nd = g_new(NotifyData, 1);
3176 nd->priority = priority;
3178 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
3179 DEBUG_2("Notify func registered: %p", nd);
3184 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
3186 GList *work = notify_func_list;
3190 NotifyData *nd = (NotifyData *)work->data;
3192 if (nd->func == func && nd->data == data)
3194 notify_func_list = g_list_delete_link(notify_func_list, work);
3196 DEBUG_2("Notify func unregistered: %p", nd);
3202 g_warning("Notify func not found");
3207 gboolean file_data_send_notification_idle_cb(gpointer data)
3209 NotifyIdleData *nid = (NotifyIdleData *)data;
3210 GList *work = notify_func_list;
3214 NotifyData *nd = (NotifyData *)work->data;
3216 nd->func(nid->fd, nid->type, nd->data);
3219 file_data_unref(nid->fd);
3224 void file_data_send_notification(FileData *fd, NotifyType type)
3226 GList *work = notify_func_list;
3230 NotifyData *nd = (NotifyData *)work->data;
3232 nd->func(fd, type, nd->data);
3236 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3237 nid->fd = file_data_ref(fd);
3239 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3243 static GHashTable *file_data_monitor_pool = NULL;
3244 static guint realtime_monitor_id = 0; /* event source id */
3246 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
3250 file_data_check_changed_files(fd);
3252 DEBUG_1("monitor %s", fd->path);
3255 static gboolean realtime_monitor_cb(gpointer data)
3257 if (!options->update_on_time_change) return TRUE;
3258 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
3262 gboolean file_data_register_real_time_monitor(FileData *fd)
3268 if (!file_data_monitor_pool)
3269 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3271 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3273 DEBUG_1("Register realtime %d %s", count, fd->path);
3276 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3278 if (!realtime_monitor_id)
3280 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
3286 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3290 g_assert(file_data_monitor_pool);
3292 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3294 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3296 g_assert(count > 0);
3301 g_hash_table_remove(file_data_monitor_pool, fd);
3303 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3305 file_data_unref(fd);
3307 if (g_hash_table_size(file_data_monitor_pool) == 0)
3309 g_source_remove(realtime_monitor_id);
3310 realtime_monitor_id = 0;
3318 *-----------------------------------------------------------------------------
3319 * Saving marks list, clearing marks
3320 * Uses file_data_pool
3321 *-----------------------------------------------------------------------------
3324 static void marks_get_files(gpointer key, gpointer value, gpointer userdata)
3326 gchar *file_name = key;
3327 GString *result = userdata;
3330 if (isfile(file_name))
3333 if (fd && fd->marks > 0)
3335 g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks);
3340 gboolean marks_list_load(const gchar *path)
3348 pathl = path_from_utf8(path);
3349 f = fopen(pathl, "r");
3351 if (!f) return FALSE;
3353 /* first line must start with Marks comment */
3354 if (!fgets(s_buf, sizeof(s_buf), f) ||
3355 strncmp(s_buf, "#Marks", 6) != 0)
3361 while (fgets(s_buf, sizeof(s_buf), f))
3363 if (s_buf[0]=='#') continue;
3364 file_path = strtok(s_buf, ",");
3365 marks_value = strtok(NULL, ",");
3366 if (isfile(file_path))
3368 FileData *fd = file_data_new_no_grouping(file_path);
3373 gint mark_no = 1 << n;
3374 if (atoi(marks_value) & mark_no)
3376 file_data_set_mark(fd, n , 1);
3387 gboolean marks_list_save(gchar *path, gboolean save)
3389 SecureSaveInfo *ssi;
3391 GString *marks = g_string_new("");
3393 pathl = path_from_utf8(path);
3394 ssi = secure_open(pathl);
3398 log_printf(_("Error: Unable to write marks lists to: %s\n"), path);
3402 secure_fprintf(ssi, "#Marks lists\n");
3406 g_hash_table_foreach(file_data_pool, marks_get_files, marks);
3408 secure_fprintf(ssi, "%s", marks->str);
3409 g_string_free(marks, FALSE);
3411 secure_fprintf(ssi, "#end\n");
3412 return (secure_close(ssi) == 0);
3415 static void marks_clear(gpointer key, gpointer value, gpointer userdata)
3417 gchar *file_name = key;
3422 if (isfile(file_name))
3425 if (fd && fd->marks > 0)
3431 if (fd->marks & mark_no)
3433 file_data_set_mark(fd, n , 0);
3441 void marks_clear_all()
3443 g_hash_table_foreach(file_data_pool, marks_clear, NULL);
3446 void file_data_set_page_num(FileData *fd, gint page_num)
3448 if (fd->page_total > 1 && page_num < 0)
3450 fd->page_num = fd->page_total - 1;
3452 else if (fd->page_total > 1 && page_num <= fd->page_total)
3454 fd->page_num = page_num - 1;
3460 file_data_send_notification(fd, NOTIFY_REREAD);
3463 void file_data_inc_page_num(FileData *fd)
3465 if (fd->page_total > 0 && fd->page_num < fd->page_total - 1)
3467 fd->page_num = fd->page_num + 1;
3469 else if (fd->page_total == 0)
3471 fd->page_num = fd->page_num + 1;
3473 file_data_send_notification(fd, NOTIFY_REREAD);
3476 void file_data_dec_page_num(FileData *fd)
3478 if (fd->page_num > 0)
3480 fd->page_num = fd->page_num - 1;
3482 file_data_send_notification(fd, NOTIFY_REREAD);
3485 void file_data_set_page_total(FileData *fd, gint page_total)
3487 fd->page_total = page_total;
3490 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */