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 (options->file_sort.natural)
287 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
288 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
292 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
293 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
297 g_free(caseless_name);
300 static void file_data_set_path(FileData *fd, const gchar *path)
302 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
303 g_assert(file_data_pool);
307 if (fd->original_path)
309 g_hash_table_remove(file_data_pool, fd->original_path);
310 g_free(fd->original_path);
313 g_assert(!g_hash_table_lookup(file_data_pool, path));
315 fd->original_path = g_strdup(path);
316 g_hash_table_insert(file_data_pool, fd->original_path, fd);
318 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
320 fd->path = g_strdup(path);
322 fd->extension = fd->name + 1;
323 file_data_set_collate_keys(fd);
327 fd->path = g_strdup(path);
328 fd->name = filename_from_path(fd->path);
330 if (strcmp(fd->name, "..") == 0)
332 gchar *dir = remove_level_from_path(path);
334 fd->path = remove_level_from_path(dir);
337 fd->extension = fd->name + 2;
338 file_data_set_collate_keys(fd);
341 else if (strcmp(fd->name, ".") == 0)
344 fd->path = remove_level_from_path(path);
346 fd->extension = fd->name + 1;
347 file_data_set_collate_keys(fd);
351 fd->extension = registered_extension_from_path(fd->path);
352 if (fd->extension == NULL)
354 fd->extension = fd->name + strlen(fd->name);
357 fd->sidecar_priority = sidecar_file_priority(fd->extension);
358 file_data_set_collate_keys(fd);
362 *-----------------------------------------------------------------------------
363 * create or reuse Filedata
364 *-----------------------------------------------------------------------------
367 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
373 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
375 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
378 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
380 fd = g_hash_table_lookup(file_data_pool, path_utf8);
386 if (!fd && file_data_planned_change_hash)
388 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
391 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
392 if (!isfile(fd->path))
395 file_data_apply_ci(fd);
408 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
411 changed = file_data_check_changed_single_file(fd, st);
413 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
418 fd = g_new0(FileData, 1);
419 #ifdef DEBUG_FILEDATA
420 global_file_data_count++;
421 DEBUG_2("file data count++: %d", global_file_data_count);
424 fd->size = st->st_size;
425 fd->date = st->st_mtime;
426 fd->cdate = st->st_ctime;
427 fd->mode = st->st_mode;
429 fd->magick = FD_MAGICK;
431 fd->rating = STAR_RATING_NOT_READ;
432 fd->format_class = filter_file_get_class(path_utf8);
436 user = getpwuid(st->st_uid);
439 fd->owner = g_strdup_printf("%u", st->st_uid);
443 fd->owner = g_strdup(user->pw_name);
446 group = getgrgid(st->st_gid);
449 fd->group = g_strdup_printf("%u", st->st_gid);
453 fd->group = g_strdup(group->gr_name);
456 fd->sym_link = get_symbolic_link(path_utf8);
458 if (disable_sidecars) fd->disable_grouping = TRUE;
460 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
465 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
467 gchar *path_utf8 = path_to_utf8(path);
468 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
474 FileData *file_data_new_simple(const gchar *path_utf8)
479 if (!stat_utf8(path_utf8, &st))
485 fd = g_hash_table_lookup(file_data_pool, path_utf8);
486 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
495 void read_exif_time_data(FileData *file)
497 if (file->exifdate > 0)
499 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
510 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
511 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), (void *)file, file->path);
516 uint year, month, day, hour, min, sec;
518 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
519 time_str.tm_year = year - 1900;
520 time_str.tm_mon = month - 1;
521 time_str.tm_mday = day;
522 time_str.tm_hour = hour;
523 time_str.tm_min = min;
524 time_str.tm_sec = sec;
525 time_str.tm_isdst = 0;
527 file->exifdate = mktime(&time_str);
533 void read_exif_time_digitized_data(FileData *file)
535 if (file->exifdate_digitized > 0)
537 DEBUG_1("%s set_exif_time_digitized_data: Already exists for %s", get_exec_time(), file->path);
548 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeDigitized");
549 DEBUG_2("%s set_exif_time_digitized_data: reading %p %s", get_exec_time(), (void *)file, file->path);
554 uint year, month, day, hour, min, sec;
556 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
557 time_str.tm_year = year - 1900;
558 time_str.tm_mon = month - 1;
559 time_str.tm_mday = day;
560 time_str.tm_hour = hour;
561 time_str.tm_min = min;
562 time_str.tm_sec = sec;
563 time_str.tm_isdst = 0;
565 file->exifdate_digitized = mktime(&time_str);
571 void read_rating_data(FileData *file)
575 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
578 file->rating = atoi(rating_str);
587 void set_exif_time_data(GList *files)
589 DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
593 FileData *file = files->data;
595 read_exif_time_data(file);
600 void set_exif_time_digitized_data(GList *files)
602 DEBUG_1("%s set_exif_time_digitized_data: ...", get_exec_time());
606 FileData *file = files->data;
608 read_exif_time_digitized_data(file);
613 void set_rating_data(GList *files)
616 DEBUG_1("%s set_rating_data: ...", get_exec_time());
620 FileData *file = files->data;
621 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
624 file->rating = atoi(rating_str);
631 FileData *file_data_new_no_grouping(const gchar *path_utf8)
635 if (!stat_utf8(path_utf8, &st))
641 return file_data_new(path_utf8, &st, TRUE);
644 FileData *file_data_new_dir(const gchar *path_utf8)
648 if (!stat_utf8(path_utf8, &st))
654 /* dir or non-existing yet */
655 g_assert(S_ISDIR(st.st_mode));
657 return file_data_new(path_utf8, &st, TRUE);
661 *-----------------------------------------------------------------------------
663 *-----------------------------------------------------------------------------
666 #ifdef DEBUG_FILEDATA
667 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
669 FileData *file_data_ref(FileData *fd)
672 if (fd == NULL) return NULL;
673 if (fd->magick != FD_MAGICK)
674 #ifdef DEBUG_FILEDATA
675 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, (void *)fd);
677 log_printf("Error: fd magick mismatch fd=%p", fd);
679 g_assert(fd->magick == FD_MAGICK);
682 #ifdef DEBUG_FILEDATA
683 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->path, file, line);
685 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
690 static void file_data_free(FileData *fd)
692 g_assert(fd->magick == FD_MAGICK);
693 g_assert(fd->ref == 0);
694 g_assert(!fd->locked);
696 #ifdef DEBUG_FILEDATA
697 global_file_data_count--;
698 DEBUG_2("file data count--: %d", global_file_data_count);
701 metadata_cache_free(fd);
702 g_hash_table_remove(file_data_pool, fd->original_path);
705 g_free(fd->original_path);
706 g_free(fd->collate_key_name);
707 g_free(fd->collate_key_name_nocase);
708 g_free(fd->extended_extension);
709 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
710 histmap_free(fd->histmap);
713 g_free(fd->sym_link);
714 g_free(fd->format_name);
715 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
717 file_data_change_info_free(NULL, fd);
722 * @brief Checks if the FileData is referenced
724 * Checks the refcount and whether the FileData is locked.
726 static gboolean file_data_check_has_ref(FileData *fd)
728 return fd->ref > 0 || fd->locked;
732 * @brief Consider freeing a FileData.
734 * This function will free a FileData and its children provided that neither its parent nor it has
735 * a positive refcount, and provided that neither is locked.
737 static void file_data_consider_free(FileData *fd)
740 FileData *parent = fd->parent ? fd->parent : fd;
742 g_assert(fd->magick == FD_MAGICK);
743 if (file_data_check_has_ref(fd)) return;
744 if (file_data_check_has_ref(parent)) return;
746 work = parent->sidecar_files;
749 FileData *sfd = work->data;
750 if (file_data_check_has_ref(sfd)) return;
754 /* Neither the parent nor the siblings are referenced, so we can free everything */
755 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
756 fd->path, fd->parent ? parent->path : "-");
758 work = parent->sidecar_files;
761 FileData *sfd = work->data;
766 g_list_free(parent->sidecar_files);
767 parent->sidecar_files = NULL;
769 file_data_free(parent);
772 #ifdef DEBUG_FILEDATA
773 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
775 void file_data_unref(FileData *fd)
778 if (fd == NULL) return;
779 if (fd->magick != FD_MAGICK)
780 #ifdef DEBUG_FILEDATA
781 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, (void *)fd);
783 log_printf("Error: fd magick mismatch fd=%p", fd);
785 g_assert(fd->magick == FD_MAGICK);
788 #ifdef DEBUG_FILEDATA
789 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->locked, fd->path,
792 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
795 // Free FileData if it's no longer ref'd
796 file_data_consider_free(fd);
800 * @brief Lock the FileData in memory.
802 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
803 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
804 * even if the code would continue to function properly even if the FileData were freed. Code that
805 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
807 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
808 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
810 void file_data_lock(FileData *fd)
812 if (fd == NULL) return;
813 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
815 g_assert(fd->magick == FD_MAGICK);
818 DEBUG_2("file_data_ref fd=%p (%d): '%s'", (void *)fd, fd->ref, fd->path);
822 * @brief Reset the maintain-FileData-in-memory lock
824 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
825 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
826 * keeping it from being freed.
828 void file_data_unlock(FileData *fd)
830 if (fd == NULL) return;
831 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
833 g_assert(fd->magick == FD_MAGICK);
836 // Free FileData if it's no longer ref'd
837 file_data_consider_free(fd);
841 * @brief Lock all of the FileDatas in the provided list
843 * @see file_data_lock(#FileData)
845 void file_data_lock_list(GList *list)
852 FileData *fd = work->data;
859 * @brief Unlock all of the FileDatas in the provided list
861 * @see #file_data_unlock(#FileData)
863 void file_data_unlock_list(GList *list)
870 FileData *fd = work->data;
872 file_data_unlock(fd);
877 *-----------------------------------------------------------------------------
878 * sidecar file info struct
879 *-----------------------------------------------------------------------------
882 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
884 const FileData *fda = a;
885 const FileData *fdb = b;
887 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
888 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
890 return strcmp(fdb->extension, fda->extension);
894 static gint sidecar_file_priority(const gchar *extension)
899 if (extension == NULL)
902 work = sidecar_ext_get_list();
905 gchar *ext = work->data;
908 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
914 static void file_data_check_sidecars(const GList *basename_list)
916 /* basename_list contains the new group - first is the parent, then sorted sidecars */
917 /* all files in the list have ref count > 0 */
920 GList *s_work, *new_sidecars;
923 if (!basename_list) return;
926 DEBUG_2("basename start");
927 work = basename_list;
930 FileData *fd = work->data;
932 g_assert(fd->magick == FD_MAGICK);
933 DEBUG_2("basename: %p %s", (void *)fd, fd->name);
936 g_assert(fd->parent->magick == FD_MAGICK);
937 DEBUG_2(" parent: %p", (void *)fd->parent);
939 s_work = fd->sidecar_files;
942 FileData *sfd = s_work->data;
943 s_work = s_work->next;
944 g_assert(sfd->magick == FD_MAGICK);
945 DEBUG_2(" sidecar: %p %s", (void *)sfd, sfd->name);
948 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
951 parent_fd = basename_list->data;
953 /* check if the second and next entries of basename_list are already connected
954 as sidecars of the first entry (parent_fd) */
955 work = basename_list->next;
956 s_work = parent_fd->sidecar_files;
958 while (work && s_work)
960 if (work->data != s_work->data) break;
962 s_work = s_work->next;
965 if (!work && !s_work)
967 DEBUG_2("basename no change");
968 return; /* no change in grouping */
971 /* we have to regroup it */
973 /* first, disconnect everything and send notification*/
975 work = basename_list;
978 FileData *fd = work->data;
980 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
984 FileData *old_parent = fd->parent;
985 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
986 file_data_ref(old_parent);
987 file_data_disconnect_sidecar_file(old_parent, fd);
988 file_data_send_notification(old_parent, NOTIFY_REREAD);
989 file_data_unref(old_parent);
992 while (fd->sidecar_files)
994 FileData *sfd = fd->sidecar_files->data;
995 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
997 file_data_disconnect_sidecar_file(fd, sfd);
998 file_data_send_notification(sfd, NOTIFY_REREAD);
999 file_data_unref(sfd);
1001 file_data_send_notification(fd, NOTIFY_GROUPING);
1003 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
1006 /* now we can form the new group */
1007 work = basename_list->next;
1008 new_sidecars = NULL;
1011 FileData *sfd = work->data;
1012 g_assert(sfd->magick == FD_MAGICK);
1013 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
1014 sfd->parent = parent_fd;
1015 new_sidecars = g_list_prepend(new_sidecars, sfd);
1018 g_assert(parent_fd->sidecar_files == NULL);
1019 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
1020 DEBUG_1("basename group changed for %s", parent_fd->path);
1024 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
1026 g_assert(target->magick == FD_MAGICK);
1027 g_assert(sfd->magick == FD_MAGICK);
1028 g_assert(g_list_find(target->sidecar_files, sfd));
1030 file_data_ref(target);
1033 g_assert(sfd->parent == target);
1035 file_data_increment_version(sfd); /* increments both sfd and target */
1037 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
1039 g_free(sfd->extended_extension);
1040 sfd->extended_extension = NULL;
1042 file_data_unref(target);
1043 file_data_unref(sfd);
1046 /* disables / enables grouping for particular file, sends UPDATE notification */
1047 void file_data_disable_grouping(FileData *fd, gboolean disable)
1049 if (!fd->disable_grouping == !disable) return;
1051 fd->disable_grouping = !!disable;
1057 FileData *parent = file_data_ref(fd->parent);
1058 file_data_disconnect_sidecar_file(parent, fd);
1059 file_data_send_notification(parent, NOTIFY_GROUPING);
1060 file_data_unref(parent);
1062 else if (fd->sidecar_files)
1064 GList *sidecar_files = filelist_copy(fd->sidecar_files);
1065 GList *work = sidecar_files;
1068 FileData *sfd = work->data;
1070 file_data_disconnect_sidecar_file(fd, sfd);
1071 file_data_send_notification(sfd, NOTIFY_GROUPING);
1073 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
1074 filelist_free(sidecar_files);
1078 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
1083 file_data_increment_version(fd);
1084 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
1086 file_data_send_notification(fd, NOTIFY_GROUPING);
1089 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
1096 FileData *fd = work->data;
1098 file_data_disable_grouping(fd, disable);
1106 *-----------------------------------------------------------------------------
1108 *-----------------------------------------------------------------------------
1112 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
1115 if (!filelist_sort_ascend)
1122 switch (filelist_sort_method)
1127 if (fa->size < fb->size) return -1;
1128 if (fa->size > fb->size) return 1;
1129 /* fall back to name */
1132 if (fa->date < fb->date) return -1;
1133 if (fa->date > fb->date) return 1;
1134 /* fall back to name */
1137 if (fa->cdate < fb->cdate) return -1;
1138 if (fa->cdate > fb->cdate) return 1;
1139 /* fall back to name */
1142 if (fa->exifdate < fb->exifdate) return -1;
1143 if (fa->exifdate > fb->exifdate) return 1;
1144 /* fall back to name */
1146 case SORT_EXIFTIMEDIGITIZED:
1147 if (fa->exifdate_digitized < fb->exifdate_digitized) return -1;
1148 if (fa->exifdate_digitized > fb->exifdate_digitized) return 1;
1149 /* fall back to name */
1152 if (fa->rating < fb->rating) return -1;
1153 if (fa->rating > fb->rating) return 1;
1154 /* fall back to name */
1157 if (fa->format_class < fb->format_class) return -1;
1158 if (fa->format_class > fb->format_class) return 1;
1159 /* fall back to name */
1161 #ifdef HAVE_STRVERSCMP
1163 ret = strverscmp(fa->name, fb->name);
1164 if (ret != 0) return ret;
1171 if (options->file_sort.case_sensitive)
1172 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1174 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1176 if (ret != 0) return ret;
1178 /* do not return 0 unless the files are really the same
1179 file_data_pool ensures that original_path is unique
1181 return strcmp(fa->original_path, fb->original_path);
1184 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1186 filelist_sort_method = method;
1187 filelist_sort_ascend = ascend;
1188 return filelist_sort_compare_filedata(fa, fb);
1191 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1193 return filelist_sort_compare_filedata(a, b);
1196 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1198 filelist_sort_method = method;
1199 filelist_sort_ascend = ascend;
1200 return g_list_sort(list, cb);
1203 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1205 filelist_sort_method = method;
1206 filelist_sort_ascend = ascend;
1207 return g_list_insert_sorted(list, data, cb);
1210 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1212 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1215 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
1217 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1221 *-----------------------------------------------------------------------------
1222 * basename hash - grouping of sidecars in filelist
1223 *-----------------------------------------------------------------------------
1227 static GHashTable *file_data_basename_hash_new(void)
1229 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1232 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1235 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1237 list = g_hash_table_lookup(basename_hash, basename);
1241 DEBUG_1("TG: basename_hash not found for %s",fd->path);
1242 const gchar *parent_extension = registered_extension_from_path(basename);
1244 if (parent_extension)
1246 DEBUG_1("TG: parent extension %s",parent_extension);
1247 gchar *parent_basename = g_strndup(basename, parent_extension - basename);
1248 DEBUG_1("TG: parent basename %s",parent_basename);
1249 FileData *parent_fd = g_hash_table_lookup(file_data_pool, basename);
1252 DEBUG_1("TG: parent fd found");
1253 list = g_hash_table_lookup(basename_hash, parent_basename);
1254 if (!g_list_find(list, parent_fd))
1256 DEBUG_1("TG: parent fd doesn't fit");
1257 g_free(parent_basename);
1263 basename = parent_basename;
1264 fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
1270 if (!g_list_find(list, fd))
1272 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1273 g_hash_table_insert(basename_hash, basename, list);
1282 static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
1284 file_data_basename_hash_insert((GHashTable *)basename_hash, (FileData *)fd);
1287 static void file_data_basename_hash_remove_list(gpointer UNUSED(key), gpointer value, gpointer UNUSED(data))
1289 filelist_free((GList *)value);
1292 static void file_data_basename_hash_free(GHashTable *basename_hash)
1294 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1295 g_hash_table_destroy(basename_hash);
1299 *-----------------------------------------------------------------------------
1300 * handling sidecars in filelist
1301 *-----------------------------------------------------------------------------
1304 static GList *filelist_filter_out_sidecars(GList *flist)
1306 GList *work = flist;
1307 GList *flist_filtered = NULL;
1311 FileData *fd = work->data;
1314 if (fd->parent) /* remove fd's that are children */
1315 file_data_unref(fd);
1317 flist_filtered = g_list_prepend(flist_filtered, fd);
1321 return flist_filtered;
1324 static void file_data_basename_hash_to_sidecars(gpointer UNUSED(key), gpointer value, gpointer UNUSED(data))
1326 GList *basename_list = (GList *)value;
1327 file_data_check_sidecars(basename_list);
1331 static gboolean is_hidden_file(const gchar *name)
1333 if (name[0] != '.') return FALSE;
1334 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1339 *-----------------------------------------------------------------------------
1340 * the main filelist function
1341 *-----------------------------------------------------------------------------
1344 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1349 GList *dlist = NULL;
1350 GList *flist = NULL;
1351 GList *xmp_files = NULL;
1352 gint (*stat_func)(const gchar *path, struct stat *buf);
1353 GHashTable *basename_hash = NULL;
1355 g_assert(files || dirs);
1357 if (files) *files = NULL;
1358 if (dirs) *dirs = NULL;
1360 pathl = path_from_utf8(dir_path);
1361 if (!pathl) return FALSE;
1363 dp = opendir(pathl);
1370 if (files) basename_hash = file_data_basename_hash_new();
1372 if (follow_symlinks)
1377 while ((dir = readdir(dp)) != NULL)
1379 struct stat ent_sbuf;
1380 const gchar *name = dir->d_name;
1383 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1386 filepath = g_build_filename(pathl, name, NULL);
1387 if (stat_func(filepath, &ent_sbuf) >= 0)
1389 if (S_ISDIR(ent_sbuf.st_mode))
1391 /* we ignore the .thumbnails dir for cleanliness */
1393 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1394 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1395 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1396 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1398 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1403 if (files && filter_name_exists(name))
1405 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1406 flist = g_list_prepend(flist, fd);
1407 if (fd->sidecar_priority && !fd->disable_grouping)
1409 if (strcmp(fd->extension, ".xmp") != 0)
1410 file_data_basename_hash_insert(basename_hash, fd);
1412 xmp_files = g_list_append(xmp_files, fd);
1419 if (errno == EOVERFLOW)
1421 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1433 g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
1434 g_list_free(xmp_files);
1437 if (dirs) *dirs = dlist;
1441 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1443 *files = filelist_filter_out_sidecars(flist);
1445 if (basename_hash) file_data_basename_hash_free(basename_hash);
1450 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1452 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1455 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1457 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1460 FileData *file_data_new_group(const gchar *path_utf8)
1467 if (!stat_utf8(path_utf8, &st))
1473 if (S_ISDIR(st.st_mode))
1474 return file_data_new(path_utf8, &st, TRUE);
1476 dir = remove_level_from_path(path_utf8);
1478 filelist_read_real(dir, &files, NULL, TRUE);
1480 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1481 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1487 filelist_free(files);
1493 void filelist_free(GList *list)
1500 file_data_unref((FileData *)work->data);
1508 GList *filelist_copy(GList *list)
1510 GList *new_list = NULL;
1521 new_list = g_list_prepend(new_list, file_data_ref(fd));
1524 return g_list_reverse(new_list);
1527 GList *filelist_from_path_list(GList *list)
1529 GList *new_list = NULL;
1540 new_list = g_list_prepend(new_list, file_data_new_group(path));
1543 return g_list_reverse(new_list);
1546 GList *filelist_to_path_list(GList *list)
1548 GList *new_list = NULL;
1559 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1562 return g_list_reverse(new_list);
1565 GList *filelist_filter(GList *list, gboolean is_dir_list)
1569 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1574 FileData *fd = (FileData *)(work->data);
1575 const gchar *name = fd->name;
1577 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1578 (!is_dir_list && !filter_name_exists(name)) ||
1579 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1580 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1584 list = g_list_remove_link(list, link);
1585 file_data_unref(fd);
1596 *-----------------------------------------------------------------------------
1597 * filelist recursive
1598 *-----------------------------------------------------------------------------
1601 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1603 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1606 GList *filelist_sort_path(GList *list)
1608 return g_list_sort(list, filelist_sort_path_cb);
1611 static void filelist_recursive_append(GList **list, GList *dirs)
1618 FileData *fd = (FileData *)(work->data);
1622 if (filelist_read(fd, &f, &d))
1624 f = filelist_filter(f, FALSE);
1625 f = filelist_sort_path(f);
1626 *list = g_list_concat(*list, f);
1628 d = filelist_filter(d, TRUE);
1629 d = filelist_sort_path(d);
1630 filelist_recursive_append(list, d);
1638 static void filelist_recursive_append_full(GList **list, GList *dirs, SortType method, gboolean ascend)
1645 FileData *fd = (FileData *)(work->data);
1649 if (filelist_read(fd, &f, &d))
1651 f = filelist_filter(f, FALSE);
1652 f = filelist_sort_full(f, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1653 *list = g_list_concat(*list, f);
1655 d = filelist_filter(d, TRUE);
1656 d = filelist_sort_path(d);
1657 filelist_recursive_append_full(list, d, method, ascend);
1665 GList *filelist_recursive(FileData *dir_fd)
1670 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1671 list = filelist_filter(list, FALSE);
1672 list = filelist_sort_path(list);
1674 d = filelist_filter(d, TRUE);
1675 d = filelist_sort_path(d);
1676 filelist_recursive_append(&list, d);
1682 GList *filelist_recursive_full(FileData *dir_fd, SortType method, gboolean ascend)
1687 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1688 list = filelist_filter(list, FALSE);
1689 list = filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1691 d = filelist_filter(d, TRUE);
1692 d = filelist_sort_path(d);
1693 filelist_recursive_append_full(&list, d, method, ascend);
1700 *-----------------------------------------------------------------------------
1701 * file modification support
1702 *-----------------------------------------------------------------------------
1706 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1708 if (!fdci && fd) fdci = fd->change;
1712 g_free(fdci->source);
1717 if (fd) fd->change = NULL;
1720 static gboolean file_data_can_write_directly(FileData *fd)
1722 return filter_name_is_writable(fd->extension);
1725 static gboolean file_data_can_write_sidecar(FileData *fd)
1727 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1730 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1732 gchar *sidecar_path = NULL;
1735 if (!file_data_can_write_sidecar(fd)) return NULL;
1737 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1738 gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1741 FileData *sfd = work->data;
1743 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1745 sidecar_path = g_strdup(sfd->path);
1749 g_free(extended_extension);
1751 if (!existing_only && !sidecar_path)
1753 if (options->metadata.sidecar_extended_name)
1754 sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1757 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1758 sidecar_path = g_strconcat(base, ".xmp", NULL);
1763 return sidecar_path;
1767 * marks and orientation
1770 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1771 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1772 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1773 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1775 gboolean file_data_get_mark(FileData *fd, gint n)
1777 gboolean valid = (fd->valid_marks & (1 << n));
1779 if (file_data_get_mark_func[n] && !valid)
1781 guint old = fd->marks;
1782 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1784 if (!value != !(fd->marks & (1 << n)))
1786 fd->marks = fd->marks ^ (1 << n);
1789 fd->valid_marks |= (1 << n);
1790 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1792 file_data_unref(fd);
1794 else if (!old && fd->marks)
1800 return !!(fd->marks & (1 << n));
1803 guint file_data_get_marks(FileData *fd)
1806 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1810 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1813 if (!value == !file_data_get_mark(fd, n)) return;
1815 if (file_data_set_mark_func[n])
1817 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1822 fd->marks = fd->marks ^ (1 << n);
1824 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1826 file_data_unref(fd);
1828 else if (!old && fd->marks)
1833 file_data_increment_version(fd);
1834 file_data_send_notification(fd, NOTIFY_MARKS);
1837 gboolean file_data_filter_marks(FileData *fd, guint filter)
1840 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1841 return ((fd->marks & filter) == filter);
1844 GList *file_data_filter_marks_list(GList *list, guint filter)
1851 FileData *fd = work->data;
1855 if (!file_data_filter_marks(fd, filter))
1857 list = g_list_remove_link(list, link);
1858 file_data_unref(fd);
1866 gboolean file_data_filter_file_filter(FileData *fd, GRegex *filter)
1868 return g_regex_match(filter, fd->name, 0, NULL);
1871 GList *file_data_filter_file_filter_list(GList *list, GRegex *filter)
1878 FileData *fd = work->data;
1882 if (!file_data_filter_file_filter(fd, filter))
1884 list = g_list_remove_link(list, link);
1885 file_data_unref(fd);
1893 static gboolean file_data_filter_class(FileData *fd, guint filter)
1897 for (i = 0; i < FILE_FORMAT_CLASSES; i++)
1899 if (filter & (1 << i))
1901 if ((FileFormatClass)i == filter_file_get_class(fd->path))
1911 GList *file_data_filter_class_list(GList *list, guint filter)
1918 FileData *fd = work->data;
1922 if (!file_data_filter_class(fd, filter))
1924 list = g_list_remove_link(list, link);
1925 file_data_unref(fd);
1933 static void file_data_notify_mark_func(gpointer UNUSED(key), gpointer value, gpointer UNUSED(user_data))
1935 FileData *fd = value;
1936 file_data_increment_version(fd);
1937 file_data_send_notification(fd, NOTIFY_MARKS);
1940 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1942 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1944 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1946 file_data_get_mark_func[n] = get_mark_func;
1947 file_data_set_mark_func[n] = set_mark_func;
1948 file_data_mark_func_data[n] = data;
1949 file_data_destroy_mark_func[n] = notify;
1951 if (get_mark_func && file_data_pool)
1953 /* this effectively changes all known files */
1954 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1960 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1962 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1963 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1964 if (data) *data = file_data_mark_func_data[n];
1967 gint file_data_get_user_orientation(FileData *fd)
1969 return fd->user_orientation;
1972 void file_data_set_user_orientation(FileData *fd, gint value)
1974 if (fd->user_orientation == value) return;
1976 fd->user_orientation = value;
1977 file_data_increment_version(fd);
1978 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1983 * file_data - operates on the given fd
1984 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1988 /* return list of sidecar file extensions in a string */
1989 gchar *file_data_sc_list_to_string(FileData *fd)
1992 GString *result = g_string_new("");
1994 work = fd->sidecar_files;
1997 FileData *sfd = work->data;
1999 result = g_string_append(result, "+ ");
2000 result = g_string_append(result, sfd->extension);
2002 if (work) result = g_string_append_c(result, ' ');
2005 return g_string_free(result, FALSE);
2011 * add FileDataChangeInfo (see typedefs.h) for the given operation
2012 * uses file_data_add_change_info
2014 * fails if the fd->change already exists - change operations can't run in parallel
2015 * fd->change_info works as a lock
2017 * dest can be NULL - in this case the current name is used for now, it will
2022 FileDataChangeInfo types:
2024 MOVE - path is changed, name may be changed too
2025 RENAME - path remains unchanged, name is changed
2026 extension should remain (FIXME should we allow editing extension? it will make problems with grouping)
2027 sidecar names are changed too, extensions are not changed
2029 UPDATE - file size, date or grouping has been changed
2032 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
2034 FileDataChangeInfo *fdci;
2036 if (fd->change) return FALSE;
2038 fdci = g_new0(FileDataChangeInfo, 1);
2043 fdci->source = g_strdup(src);
2045 fdci->source = g_strdup(fd->path);
2048 fdci->dest = g_strdup(dest);
2055 static void file_data_planned_change_remove(FileData *fd)
2057 if (file_data_planned_change_hash &&
2058 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
2060 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
2062 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
2063 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
2064 file_data_unref(fd);
2065 if (g_hash_table_size(file_data_planned_change_hash) == 0)
2067 g_hash_table_destroy(file_data_planned_change_hash);
2068 file_data_planned_change_hash = NULL;
2069 DEBUG_1("planned change: empty");
2076 void file_data_free_ci(FileData *fd)
2078 FileDataChangeInfo *fdci = fd->change;
2082 file_data_planned_change_remove(fd);
2084 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
2086 g_free(fdci->source);
2094 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
2096 FileDataChangeInfo *fdci = fd->change;
2098 fdci->regroup_when_finished = enable;
2101 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
2105 if (fd->parent) fd = fd->parent;
2107 if (fd->change) return FALSE;
2109 work = fd->sidecar_files;
2112 FileData *sfd = work->data;
2114 if (sfd->change) return FALSE;
2118 file_data_add_ci(fd, type, NULL, NULL);
2120 work = fd->sidecar_files;
2123 FileData *sfd = work->data;
2125 file_data_add_ci(sfd, type, NULL, NULL);
2132 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
2136 if (fd->parent) fd = fd->parent;
2138 if (!fd->change || fd->change->type != type) return FALSE;
2140 work = fd->sidecar_files;
2143 FileData *sfd = work->data;
2145 if (!sfd->change || sfd->change->type != type) return FALSE;
2153 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
2155 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
2156 file_data_sc_update_ci_copy(fd, dest_path);
2160 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
2162 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
2163 file_data_sc_update_ci_move(fd, dest_path);
2167 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
2169 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
2170 file_data_sc_update_ci_rename(fd, dest_path);
2174 gboolean file_data_sc_add_ci_delete(FileData *fd)
2176 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
2179 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
2181 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
2182 file_data_sc_update_ci_unspecified(fd, dest_path);
2186 gboolean file_data_add_ci_write_metadata(FileData *fd)
2188 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
2191 void file_data_sc_free_ci(FileData *fd)
2195 if (fd->parent) fd = fd->parent;
2197 file_data_free_ci(fd);
2199 work = fd->sidecar_files;
2202 FileData *sfd = work->data;
2204 file_data_free_ci(sfd);
2209 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2212 gboolean ret = TRUE;
2217 FileData *fd = work->data;
2219 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2226 static void file_data_sc_revert_ci_list(GList *fd_list)
2233 FileData *fd = work->data;
2235 file_data_sc_free_ci(fd);
2240 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2247 FileData *fd = work->data;
2249 if (!func(fd, dest))
2251 file_data_sc_revert_ci_list(work->prev);
2260 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2262 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2265 gboolean file_data_sc_add_ci_move_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_move);
2270 gboolean file_data_sc_add_ci_rename_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_rename);
2275 gboolean file_data_sc_add_ci_unspecified_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_unspecified);
2280 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2283 gboolean ret = TRUE;
2288 FileData *fd = work->data;
2290 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2297 void file_data_free_ci_list(GList *fd_list)
2304 FileData *fd = work->data;
2306 file_data_free_ci(fd);
2311 void file_data_sc_free_ci_list(GList *fd_list)
2318 FileData *fd = work->data;
2320 file_data_sc_free_ci(fd);
2326 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2327 * fails if fd->change does not exist or the change type does not match
2330 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2332 FileDataChangeType type = fd->change->type;
2334 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2338 if (!file_data_planned_change_hash)
2339 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2341 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2343 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2344 g_hash_table_remove(file_data_planned_change_hash, old_path);
2345 file_data_unref(fd);
2348 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
2353 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2354 g_hash_table_remove(file_data_planned_change_hash, new_path);
2355 file_data_unref(ofd);
2358 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2360 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2365 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2367 gchar *old_path = fd->change->dest;
2369 fd->change->dest = g_strdup(dest_path);
2370 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2374 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2376 const gchar *extension = registered_extension_from_path(fd->change->source);
2377 gchar *base = remove_extension_from_path(dest_path);
2378 gchar *old_path = fd->change->dest;
2380 fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2381 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2387 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2390 gchar *dest_path_full = NULL;
2392 if (fd->parent) fd = fd->parent;
2396 dest_path = fd->path;
2398 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2400 gchar *dir = remove_level_from_path(fd->path);
2402 dest_path_full = g_build_filename(dir, dest_path, NULL);
2404 dest_path = dest_path_full;
2406 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2408 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2409 dest_path = dest_path_full;
2412 file_data_update_ci_dest(fd, dest_path);
2414 work = fd->sidecar_files;
2417 FileData *sfd = work->data;
2419 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2423 g_free(dest_path_full);
2426 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2428 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2429 file_data_sc_update_ci(fd, dest_path);
2433 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2435 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2438 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2440 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2443 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2445 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2448 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2450 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2453 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2455 gboolean (*func)(FileData *, const gchar *))
2458 gboolean ret = TRUE;
2463 FileData *fd = work->data;
2465 if (!func(fd, dest)) ret = FALSE;
2472 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2474 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2477 gboolean file_data_sc_update_ci_copy_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_copy);
2482 gboolean file_data_sc_update_ci_unspecified_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_unspecified);
2489 * verify source and dest paths - dest image exists, etc.
2490 * it should detect all possible problems with the planned operation
2493 gint file_data_verify_ci(FileData *fd, GList *list)
2495 gint ret = CHANGE_OK;
2498 FileData *fd1 = NULL;
2502 DEBUG_1("Change checked: no change info: %s", fd->path);
2506 if (!isname(fd->path))
2508 /* this probably should not happen */
2509 ret |= CHANGE_NO_SRC;
2510 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2514 dir = remove_level_from_path(fd->path);
2516 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2517 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2518 fd->change->type != FILEDATA_CHANGE_RENAME &&
2519 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2522 ret |= CHANGE_WARN_UNSAVED_META;
2523 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2526 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2527 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2528 !access_file(fd->path, R_OK))
2530 ret |= CHANGE_NO_READ_PERM;
2531 DEBUG_1("Change checked: no read permission: %s", fd->path);
2533 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2534 !access_file(dir, W_OK))
2536 ret |= CHANGE_NO_WRITE_PERM_DIR;
2537 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2539 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2540 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2541 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2542 !access_file(fd->path, W_OK))
2544 ret |= CHANGE_WARN_NO_WRITE_PERM;
2545 DEBUG_1("Change checked: no write permission: %s", fd->path);
2547 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2548 - that means that there are no hard errors and warnings can be disabled
2549 - the destination is determined during the check
2551 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2553 /* determine destination file */
2554 gboolean have_dest = FALSE;
2555 gchar *dest_dir = NULL;
2557 if (options->metadata.save_in_image_file)
2559 if (file_data_can_write_directly(fd))
2561 /* we can write the file directly */
2562 if (access_file(fd->path, W_OK))
2568 if (options->metadata.warn_on_write_problems)
2570 ret |= CHANGE_WARN_NO_WRITE_PERM;
2571 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2575 else if (file_data_can_write_sidecar(fd))
2577 /* we can write sidecar */
2578 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2579 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2581 file_data_update_ci_dest(fd, sidecar);
2586 if (options->metadata.warn_on_write_problems)
2588 ret |= CHANGE_WARN_NO_WRITE_PERM;
2589 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2598 /* write private metadata file under ~/.geeqie */
2600 /* If an existing metadata file exists, we will try writing to
2601 * it's location regardless of the user's preference.
2603 gchar *metadata_path = NULL;
2605 /* but ignore XMP if we are not able to write it */
2606 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2608 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2610 if (metadata_path && !access_file(metadata_path, W_OK))
2612 g_free(metadata_path);
2613 metadata_path = NULL;
2620 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2621 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2623 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2625 metadata_path = g_build_filename(dest_dir, filename, NULL);
2629 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2631 file_data_update_ci_dest(fd, metadata_path);
2636 ret |= CHANGE_NO_WRITE_PERM_DEST;
2637 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2639 g_free(metadata_path);
2644 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2649 same = (strcmp(fd->path, fd->change->dest) == 0);
2653 const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2654 if (!dest_ext) dest_ext = "";
2655 if (!options->file_filter.disable_file_extension_checks)
2657 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2659 ret |= CHANGE_WARN_CHANGED_EXT;
2660 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2666 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /** @FIXME this is now needed for running editors */
2668 ret |= CHANGE_WARN_SAME;
2669 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2673 dest_dir = remove_level_from_path(fd->change->dest);
2675 if (!isdir(dest_dir))
2677 ret |= CHANGE_NO_DEST_DIR;
2678 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2680 else if (!access_file(dest_dir, W_OK))
2682 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2683 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2687 if (isfile(fd->change->dest))
2689 if (!access_file(fd->change->dest, W_OK))
2691 ret |= CHANGE_NO_WRITE_PERM_DEST;
2692 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2696 ret |= CHANGE_WARN_DEST_EXISTS;
2697 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2700 else if (isdir(fd->change->dest))
2702 ret |= CHANGE_DEST_EXISTS;
2703 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2710 /* During a rename operation, check if another planned destination file has
2713 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2714 fd->change->type == FILEDATA_CHANGE_COPY ||
2715 fd->change->type == FILEDATA_CHANGE_MOVE)
2722 if (fd1 != NULL && fd != fd1 )
2724 if (!strcmp(fd->change->dest, fd1->change->dest))
2726 ret |= CHANGE_DUPLICATE_DEST;
2732 fd->change->error = ret;
2733 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2740 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2745 ret = file_data_verify_ci(fd, list);
2747 work = fd->sidecar_files;
2750 FileData *sfd = work->data;
2752 ret |= file_data_verify_ci(sfd, list);
2759 gchar *file_data_get_error_string(gint error)
2761 GString *result = g_string_new("");
2763 if (error & CHANGE_NO_SRC)
2765 if (result->len > 0) g_string_append(result, ", ");
2766 g_string_append(result, _("file or directory does not exist"));
2769 if (error & CHANGE_DEST_EXISTS)
2771 if (result->len > 0) g_string_append(result, ", ");
2772 g_string_append(result, _("destination already exists"));
2775 if (error & CHANGE_NO_WRITE_PERM_DEST)
2777 if (result->len > 0) g_string_append(result, ", ");
2778 g_string_append(result, _("destination can't be overwritten"));
2781 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2783 if (result->len > 0) g_string_append(result, ", ");
2784 g_string_append(result, _("destination directory is not writable"));
2787 if (error & CHANGE_NO_DEST_DIR)
2789 if (result->len > 0) g_string_append(result, ", ");
2790 g_string_append(result, _("destination directory does not exist"));
2793 if (error & CHANGE_NO_WRITE_PERM_DIR)
2795 if (result->len > 0) g_string_append(result, ", ");
2796 g_string_append(result, _("source directory is not writable"));
2799 if (error & CHANGE_NO_READ_PERM)
2801 if (result->len > 0) g_string_append(result, ", ");
2802 g_string_append(result, _("no read permission"));
2805 if (error & CHANGE_WARN_NO_WRITE_PERM)
2807 if (result->len > 0) g_string_append(result, ", ");
2808 g_string_append(result, _("file is readonly"));
2811 if (error & CHANGE_WARN_DEST_EXISTS)
2813 if (result->len > 0) g_string_append(result, ", ");
2814 g_string_append(result, _("destination already exists and will be overwritten"));
2817 if (error & CHANGE_WARN_SAME)
2819 if (result->len > 0) g_string_append(result, ", ");
2820 g_string_append(result, _("source and destination are the same"));
2823 if (error & CHANGE_WARN_CHANGED_EXT)
2825 if (result->len > 0) g_string_append(result, ", ");
2826 g_string_append(result, _("source and destination have different extension"));
2829 if (error & CHANGE_WARN_UNSAVED_META)
2831 if (result->len > 0) g_string_append(result, ", ");
2832 g_string_append(result, _("there are unsaved metadata changes for the file"));
2835 if (error & CHANGE_DUPLICATE_DEST)
2837 if (result->len > 0) g_string_append(result, ", ");
2838 g_string_append(result, _("another destination file has the same filename"));
2841 return g_string_free(result, FALSE);
2844 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2847 gint all_errors = 0;
2848 gint common_errors = ~0;
2853 if (!list) return 0;
2855 num = g_list_length(list);
2856 errors = g_new(int, num);
2867 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2868 all_errors |= error;
2869 common_errors &= error;
2876 if (desc && all_errors)
2879 GString *result = g_string_new("");
2883 gchar *str = file_data_get_error_string(common_errors);
2884 g_string_append(result, str);
2885 g_string_append(result, "\n");
2899 error = errors[i] & ~common_errors;
2903 gchar *str = file_data_get_error_string(error);
2904 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2909 *desc = g_string_free(result, FALSE);
2918 * perform the change described by FileFataChangeInfo
2919 * it is used for internal operations,
2920 * this function actually operates with files on the filesystem
2921 * it should implement safe delete
2924 static gboolean file_data_perform_move(FileData *fd)
2926 g_assert(!strcmp(fd->change->source, fd->path));
2927 return move_file(fd->change->source, fd->change->dest);
2930 static gboolean file_data_perform_copy(FileData *fd)
2932 g_assert(!strcmp(fd->change->source, fd->path));
2933 return copy_file(fd->change->source, fd->change->dest);
2936 static gboolean file_data_perform_delete(FileData *fd)
2938 if (isdir(fd->path) && !islink(fd->path))
2939 return rmdir_utf8(fd->path);
2941 if (options->file_ops.safe_delete_enable)
2942 return file_util_safe_unlink(fd->path);
2944 return unlink_file(fd->path);
2947 gboolean file_data_perform_ci(FileData *fd)
2949 FileDataChangeType type = fd->change->type;
2953 case FILEDATA_CHANGE_MOVE:
2954 return file_data_perform_move(fd);
2955 case FILEDATA_CHANGE_COPY:
2956 return file_data_perform_copy(fd);
2957 case FILEDATA_CHANGE_RENAME:
2958 return file_data_perform_move(fd); /* the same as move */
2959 case FILEDATA_CHANGE_DELETE:
2960 return file_data_perform_delete(fd);
2961 case FILEDATA_CHANGE_WRITE_METADATA:
2962 return metadata_write_perform(fd);
2963 case FILEDATA_CHANGE_UNSPECIFIED:
2964 /* nothing to do here */
2972 gboolean file_data_sc_perform_ci(FileData *fd)
2975 gboolean ret = TRUE;
2976 FileDataChangeType type = fd->change->type;
2978 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2980 work = fd->sidecar_files;
2983 FileData *sfd = work->data;
2985 if (!file_data_perform_ci(sfd)) ret = FALSE;
2989 if (!file_data_perform_ci(fd)) ret = FALSE;
2995 * updates FileData structure according to FileDataChangeInfo
2998 gboolean file_data_apply_ci(FileData *fd)
3000 FileDataChangeType type = fd->change->type;
3002 /** @FIXME delete ?*/
3003 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
3005 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
3006 file_data_planned_change_remove(fd);
3008 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
3010 /* this change overwrites another file which is already known to other modules
3011 renaming fd would create duplicate FileData structure
3012 the best thing we can do is nothing
3014 /** @FIXME maybe we could copy stuff like marks
3016 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
3020 file_data_set_path(fd, fd->change->dest);
3023 file_data_increment_version(fd);
3024 file_data_send_notification(fd, NOTIFY_CHANGE);
3029 gboolean file_data_sc_apply_ci(FileData *fd)
3032 FileDataChangeType type = fd->change->type;
3034 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3036 work = fd->sidecar_files;
3039 FileData *sfd = work->data;
3041 file_data_apply_ci(sfd);
3045 file_data_apply_ci(fd);
3050 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
3053 if (fd->parent) fd = fd->parent;
3054 if (!g_list_find(list, fd)) return FALSE;
3056 work = fd->sidecar_files;
3059 if (!g_list_find(list, work->data)) return FALSE;
3065 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
3070 /* change partial groups to independent files */
3075 FileData *fd = work->data;
3078 if (!file_data_list_contains_whole_group(list, fd))
3080 file_data_disable_grouping(fd, TRUE);
3083 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
3089 /* remove sidecars from the list,
3090 they can be still accessed via main_fd->sidecar_files */
3094 FileData *fd = work->data;
3098 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
3100 out = g_list_prepend(out, file_data_ref(fd));
3104 filelist_free(list);
3105 out = g_list_reverse(out);
3115 * notify other modules about the change described by FileDataChangeInfo
3118 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks */
3119 /** @FIXME do we need the ignore_list? It looks like a workaround for ineffective
3120 implementation in view-file-list.cc */
3123 typedef struct _NotifyIdleData NotifyIdleData;
3125 struct _NotifyIdleData {
3131 typedef struct _NotifyData NotifyData;
3133 struct _NotifyData {
3134 FileDataNotifyFunc func;
3136 NotifyPriority priority;
3139 static GList *notify_func_list = NULL;
3141 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
3143 NotifyData *nda = (NotifyData *)a;
3144 NotifyData *ndb = (NotifyData *)b;
3146 if (nda->priority < ndb->priority) return -1;
3147 if (nda->priority > ndb->priority) return 1;
3151 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
3154 GList *work = notify_func_list;
3158 NotifyData *nd = (NotifyData *)work->data;
3160 if (nd->func == func && nd->data == data)
3162 g_warning("Notify func already registered");
3168 nd = g_new(NotifyData, 1);
3171 nd->priority = priority;
3173 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
3174 DEBUG_2("Notify func registered: %p", (void *)nd);
3179 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
3181 GList *work = notify_func_list;
3185 NotifyData *nd = (NotifyData *)work->data;
3187 if (nd->func == func && nd->data == data)
3189 notify_func_list = g_list_delete_link(notify_func_list, work);
3191 DEBUG_2("Notify func unregistered: %p", (void *)nd);
3197 g_warning("Notify func not found");
3202 gboolean file_data_send_notification_idle_cb(gpointer data)
3204 NotifyIdleData *nid = (NotifyIdleData *)data;
3205 GList *work = notify_func_list;
3209 NotifyData *nd = (NotifyData *)work->data;
3211 nd->func(nid->fd, nid->type, nd->data);
3214 file_data_unref(nid->fd);
3219 void file_data_send_notification(FileData *fd, NotifyType type)
3221 GList *work = notify_func_list;
3225 NotifyData *nd = (NotifyData *)work->data;
3227 nd->func(fd, type, nd->data);
3231 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3232 nid->fd = file_data_ref(fd);
3234 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3238 static GHashTable *file_data_monitor_pool = NULL;
3239 static guint realtime_monitor_id = 0; /* event source id */
3241 static void realtime_monitor_check_cb(gpointer key, gpointer UNUSED(value), gpointer UNUSED(data))
3245 file_data_check_changed_files(fd);
3247 DEBUG_1("monitor %s", fd->path);
3250 static gboolean realtime_monitor_cb(gpointer UNUSED(data))
3252 if (!options->update_on_time_change) return TRUE;
3253 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
3257 gboolean file_data_register_real_time_monitor(FileData *fd)
3263 if (!file_data_monitor_pool)
3264 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3266 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3268 DEBUG_1("Register realtime %d %s", count, fd->path);
3271 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3273 if (!realtime_monitor_id)
3275 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
3281 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3285 g_assert(file_data_monitor_pool);
3287 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3289 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3291 g_assert(count > 0);
3296 g_hash_table_remove(file_data_monitor_pool, fd);
3298 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3300 file_data_unref(fd);
3302 if (g_hash_table_size(file_data_monitor_pool) == 0)
3304 g_source_remove(realtime_monitor_id);
3305 realtime_monitor_id = 0;
3313 *-----------------------------------------------------------------------------
3314 * Saving marks list, clearing marks
3315 * Uses file_data_pool
3316 *-----------------------------------------------------------------------------
3319 static void marks_get_files(gpointer key, gpointer value, gpointer userdata)
3321 gchar *file_name = key;
3322 GString *result = userdata;
3325 if (isfile(file_name))
3328 if (fd && fd->marks > 0)
3330 g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks);
3335 gboolean marks_list_load(const gchar *path)
3343 pathl = path_from_utf8(path);
3344 f = fopen(pathl, "r");
3346 if (!f) return FALSE;
3348 /* first line must start with Marks comment */
3349 if (!fgets(s_buf, sizeof(s_buf), f) ||
3350 strncmp(s_buf, "#Marks", 6) != 0)
3356 while (fgets(s_buf, sizeof(s_buf), f))
3358 if (s_buf[0]=='#') continue;
3359 file_path = strtok(s_buf, ",");
3360 marks_value = strtok(NULL, ",");
3361 if (isfile(file_path))
3363 FileData *fd = file_data_new_no_grouping(file_path);
3368 gint mark_no = 1 << n;
3369 if (atoi(marks_value) & mark_no)
3371 file_data_set_mark(fd, n , 1);
3382 gboolean marks_list_save(gchar *path, gboolean save)
3384 SecureSaveInfo *ssi;
3386 GString *marks = g_string_new("");
3388 pathl = path_from_utf8(path);
3389 ssi = secure_open(pathl);
3393 log_printf(_("Error: Unable to write marks lists to: %s\n"), path);
3397 secure_fprintf(ssi, "#Marks lists\n");
3401 g_hash_table_foreach(file_data_pool, marks_get_files, marks);
3403 secure_fprintf(ssi, "%s", marks->str);
3404 g_string_free(marks, FALSE);
3406 secure_fprintf(ssi, "#end\n");
3407 return (secure_close(ssi) == 0);
3410 static void marks_clear(gpointer key, gpointer value, gpointer UNUSED(userdata))
3412 gchar *file_name = key;
3417 if (isfile(file_name))
3420 if (fd && fd->marks > 0)
3426 if (fd->marks & mark_no)
3428 file_data_set_mark(fd, n , 0);
3436 void marks_clear_all()
3438 g_hash_table_foreach(file_data_pool, marks_clear, NULL);
3441 void file_data_set_page_num(FileData *fd, gint page_num)
3443 if (fd->page_total > 1 && page_num < 0)
3445 fd->page_num = fd->page_total - 1;
3447 else if (fd->page_total > 1 && page_num <= fd->page_total)
3449 fd->page_num = page_num - 1;
3455 file_data_send_notification(fd, NOTIFY_REREAD);
3458 void file_data_inc_page_num(FileData *fd)
3460 if (fd->page_total > 0 && fd->page_num < fd->page_total - 1)
3462 fd->page_num = fd->page_num + 1;
3464 else if (fd->page_total == 0)
3466 fd->page_num = fd->page_num + 1;
3468 file_data_send_notification(fd, NOTIFY_REREAD);
3471 void file_data_dec_page_num(FileData *fd)
3473 if (fd->page_num > 0)
3475 fd->page_num = fd->page_num - 1;
3477 file_data_send_notification(fd, NOTIFY_REREAD);
3480 void file_data_set_page_total(FileData *fd, gint page_total)
3482 fd->page_total = page_total;
3485 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */