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_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
721 file_data_change_info_free(NULL, fd);
726 * @brief Checks if the FileData is referenced
728 * Checks the refcount and whether the FileData is locked.
730 static gboolean file_data_check_has_ref(FileData *fd)
732 return fd->ref > 0 || fd->locked;
736 * @brief Consider freeing a FileData.
738 * This function will free a FileData and its children provided that neither its parent nor it has
739 * a positive refcount, and provided that neither is locked.
741 static void file_data_consider_free(FileData *fd)
744 FileData *parent = fd->parent ? fd->parent : fd;
746 g_assert(fd->magick == FD_MAGICK);
747 if (file_data_check_has_ref(fd)) return;
748 if (file_data_check_has_ref(parent)) return;
750 work = parent->sidecar_files;
753 FileData *sfd = work->data;
754 if (file_data_check_has_ref(sfd)) return;
758 /* Neither the parent nor the siblings are referenced, so we can free everything */
759 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
760 fd->path, fd->parent ? parent->path : "-");
762 work = parent->sidecar_files;
765 FileData *sfd = work->data;
770 g_list_free(parent->sidecar_files);
771 parent->sidecar_files = NULL;
773 file_data_free(parent);
776 #ifdef DEBUG_FILEDATA
777 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
779 void file_data_unref(FileData *fd)
782 if (fd == NULL) return;
783 if (fd->magick != FD_MAGICK)
784 #ifdef DEBUG_FILEDATA
785 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, fd);
787 log_printf("Error: fd magick mismatch fd=%p", fd);
789 g_assert(fd->magick == FD_MAGICK);
792 #ifdef DEBUG_FILEDATA
793 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", fd, fd->ref, fd->locked, fd->path,
796 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
799 // Free FileData if it's no longer ref'd
800 file_data_consider_free(fd);
804 * @brief Lock the FileData in memory.
806 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
807 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
808 * even if the code would continue to function properly even if the FileData were freed. Code that
809 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
811 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
812 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
814 void file_data_lock(FileData *fd)
816 if (fd == NULL) return;
817 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", fd);
819 g_assert(fd->magick == FD_MAGICK);
822 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
826 * @brief Reset the maintain-FileData-in-memory lock
828 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
829 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
830 * keeping it from being freed.
832 void file_data_unlock(FileData *fd)
834 if (fd == NULL) return;
835 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", fd);
837 g_assert(fd->magick == FD_MAGICK);
840 // Free FileData if it's no longer ref'd
841 file_data_consider_free(fd);
845 * @brief Lock all of the FileDatas in the provided list
847 * @see file_data_lock(#FileData)
849 void file_data_lock_list(GList *list)
856 FileData *fd = work->data;
863 * @brief Unlock all of the FileDatas in the provided list
865 * @see #file_data_unlock(#FileData)
867 void file_data_unlock_list(GList *list)
874 FileData *fd = work->data;
876 file_data_unlock(fd);
881 *-----------------------------------------------------------------------------
882 * sidecar file info struct
883 *-----------------------------------------------------------------------------
886 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
888 const FileData *fda = a;
889 const FileData *fdb = b;
891 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
892 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
894 return strcmp(fdb->extension, fda->extension);
898 static gint sidecar_file_priority(const gchar *extension)
903 if (extension == NULL)
906 work = sidecar_ext_get_list();
909 gchar *ext = work->data;
912 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
918 static void file_data_check_sidecars(const GList *basename_list)
920 /* basename_list contains the new group - first is the parent, then sorted sidecars */
921 /* all files in the list have ref count > 0 */
924 GList *s_work, *new_sidecars;
927 if (!basename_list) return;
930 DEBUG_2("basename start");
931 work = basename_list;
934 FileData *fd = work->data;
936 g_assert(fd->magick == FD_MAGICK);
937 DEBUG_2("basename: %p %s", fd, fd->name);
940 g_assert(fd->parent->magick == FD_MAGICK);
941 DEBUG_2(" parent: %p", fd->parent);
943 s_work = fd->sidecar_files;
946 FileData *sfd = s_work->data;
947 s_work = s_work->next;
948 g_assert(sfd->magick == FD_MAGICK);
949 DEBUG_2(" sidecar: %p %s", sfd, sfd->name);
952 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
955 parent_fd = basename_list->data;
957 /* check if the second and next entries of basename_list are already connected
958 as sidecars of the first entry (parent_fd) */
959 work = basename_list->next;
960 s_work = parent_fd->sidecar_files;
962 while (work && s_work)
964 if (work->data != s_work->data) break;
966 s_work = s_work->next;
969 if (!work && !s_work)
971 DEBUG_2("basename no change");
972 return; /* no change in grouping */
975 /* we have to regroup it */
977 /* first, disconnect everything and send notification*/
979 work = basename_list;
982 FileData *fd = work->data;
984 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
988 FileData *old_parent = fd->parent;
989 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
990 file_data_ref(old_parent);
991 file_data_disconnect_sidecar_file(old_parent, fd);
992 file_data_send_notification(old_parent, NOTIFY_REREAD);
993 file_data_unref(old_parent);
996 while (fd->sidecar_files)
998 FileData *sfd = fd->sidecar_files->data;
999 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
1001 file_data_disconnect_sidecar_file(fd, sfd);
1002 file_data_send_notification(sfd, NOTIFY_REREAD);
1003 file_data_unref(sfd);
1005 file_data_send_notification(fd, NOTIFY_GROUPING);
1007 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
1010 /* now we can form the new group */
1011 work = basename_list->next;
1012 new_sidecars = NULL;
1015 FileData *sfd = work->data;
1016 g_assert(sfd->magick == FD_MAGICK);
1017 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
1018 sfd->parent = parent_fd;
1019 new_sidecars = g_list_prepend(new_sidecars, sfd);
1022 g_assert(parent_fd->sidecar_files == NULL);
1023 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
1024 DEBUG_1("basename group changed for %s", parent_fd->path);
1028 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
1030 g_assert(target->magick == FD_MAGICK);
1031 g_assert(sfd->magick == FD_MAGICK);
1032 g_assert(g_list_find(target->sidecar_files, sfd));
1034 file_data_ref(target);
1037 g_assert(sfd->parent == target);
1039 file_data_increment_version(sfd); /* increments both sfd and target */
1041 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
1043 g_free(sfd->extended_extension);
1044 sfd->extended_extension = NULL;
1046 file_data_unref(target);
1047 file_data_unref(sfd);
1050 /* disables / enables grouping for particular file, sends UPDATE notification */
1051 void file_data_disable_grouping(FileData *fd, gboolean disable)
1053 if (!fd->disable_grouping == !disable) return;
1055 fd->disable_grouping = !!disable;
1061 FileData *parent = file_data_ref(fd->parent);
1062 file_data_disconnect_sidecar_file(parent, fd);
1063 file_data_send_notification(parent, NOTIFY_GROUPING);
1064 file_data_unref(parent);
1066 else if (fd->sidecar_files)
1068 GList *sidecar_files = filelist_copy(fd->sidecar_files);
1069 GList *work = sidecar_files;
1072 FileData *sfd = work->data;
1074 file_data_disconnect_sidecar_file(fd, sfd);
1075 file_data_send_notification(sfd, NOTIFY_GROUPING);
1077 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
1078 filelist_free(sidecar_files);
1082 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
1087 file_data_increment_version(fd);
1088 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
1090 file_data_send_notification(fd, NOTIFY_GROUPING);
1093 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
1100 FileData *fd = work->data;
1102 file_data_disable_grouping(fd, disable);
1110 *-----------------------------------------------------------------------------
1112 *-----------------------------------------------------------------------------
1116 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
1119 if (!filelist_sort_ascend)
1126 switch (filelist_sort_method)
1131 if (fa->size < fb->size) return -1;
1132 if (fa->size > fb->size) return 1;
1133 /* fall back to name */
1136 if (fa->date < fb->date) return -1;
1137 if (fa->date > fb->date) return 1;
1138 /* fall back to name */
1141 if (fa->cdate < fb->cdate) return -1;
1142 if (fa->cdate > fb->cdate) return 1;
1143 /* fall back to name */
1146 if (fa->exifdate < fb->exifdate) return -1;
1147 if (fa->exifdate > fb->exifdate) return 1;
1148 /* fall back to name */
1150 case SORT_EXIFTIMEDIGITIZED:
1151 if (fa->exifdate_digitized < fb->exifdate_digitized) return -1;
1152 if (fa->exifdate_digitized > fb->exifdate_digitized) return 1;
1153 /* fall back to name */
1156 if (fa->rating < fb->rating) return -1;
1157 if (fa->rating > fb->rating) return 1;
1158 /* fall back to name */
1161 if (fa->format_class < fb->format_class) return -1;
1162 if (fa->format_class > fb->format_class) return 1;
1163 /* fall back to name */
1165 #ifdef HAVE_STRVERSCMP
1167 ret = strverscmp(fa->name, fb->name);
1168 if (ret != 0) return ret;
1175 if (options->file_sort.case_sensitive)
1176 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1178 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1180 if (ret != 0) return ret;
1182 /* do not return 0 unless the files are really the same
1183 file_data_pool ensures that original_path is unique
1185 return strcmp(fa->original_path, fb->original_path);
1188 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1190 filelist_sort_method = method;
1191 filelist_sort_ascend = ascend;
1192 return filelist_sort_compare_filedata(fa, fb);
1195 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1197 return filelist_sort_compare_filedata(a, b);
1200 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1202 filelist_sort_method = method;
1203 filelist_sort_ascend = ascend;
1204 return g_list_sort(list, cb);
1207 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1209 filelist_sort_method = method;
1210 filelist_sort_ascend = ascend;
1211 return g_list_insert_sorted(list, data, cb);
1214 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1216 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1219 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
1221 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1225 *-----------------------------------------------------------------------------
1226 * basename hash - grouping of sidecars in filelist
1227 *-----------------------------------------------------------------------------
1231 static GHashTable *file_data_basename_hash_new(void)
1233 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1236 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1239 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1241 list = g_hash_table_lookup(basename_hash, basename);
1245 DEBUG_1("TG: basename_hash not found for %s",fd->path);
1246 const gchar *parent_extension = registered_extension_from_path(basename);
1248 if (parent_extension)
1250 DEBUG_1("TG: parent extension %s",parent_extension);
1251 gchar *parent_basename = g_strndup(basename, parent_extension - basename);
1252 DEBUG_1("TG: parent basename %s",parent_basename);
1253 FileData *parent_fd = g_hash_table_lookup(file_data_pool, basename);
1256 DEBUG_1("TG: parent fd found");
1257 list = g_hash_table_lookup(basename_hash, parent_basename);
1258 if (!g_list_find(list, parent_fd))
1260 DEBUG_1("TG: parent fd doesn't fit");
1261 g_free(parent_basename);
1267 basename = parent_basename;
1268 fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
1274 if (!g_list_find(list, fd))
1276 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1277 g_hash_table_insert(basename_hash, basename, list);
1286 static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
1288 file_data_basename_hash_insert((GHashTable *)basename_hash, (FileData *)fd);
1291 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
1293 filelist_free((GList *)value);
1296 static void file_data_basename_hash_free(GHashTable *basename_hash)
1298 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1299 g_hash_table_destroy(basename_hash);
1303 *-----------------------------------------------------------------------------
1304 * handling sidecars in filelist
1305 *-----------------------------------------------------------------------------
1308 static GList *filelist_filter_out_sidecars(GList *flist)
1310 GList *work = flist;
1311 GList *flist_filtered = NULL;
1315 FileData *fd = work->data;
1318 if (fd->parent) /* remove fd's that are children */
1319 file_data_unref(fd);
1321 flist_filtered = g_list_prepend(flist_filtered, fd);
1325 return flist_filtered;
1328 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
1330 GList *basename_list = (GList *)value;
1331 file_data_check_sidecars(basename_list);
1335 static gboolean is_hidden_file(const gchar *name)
1337 if (name[0] != '.') return FALSE;
1338 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1343 *-----------------------------------------------------------------------------
1344 * the main filelist function
1345 *-----------------------------------------------------------------------------
1348 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1353 GList *dlist = NULL;
1354 GList *flist = NULL;
1355 GList *xmp_files = NULL;
1356 gint (*stat_func)(const gchar *path, struct stat *buf);
1357 GHashTable *basename_hash = NULL;
1359 g_assert(files || dirs);
1361 if (files) *files = NULL;
1362 if (dirs) *dirs = NULL;
1364 pathl = path_from_utf8(dir_path);
1365 if (!pathl) return FALSE;
1367 dp = opendir(pathl);
1374 if (files) basename_hash = file_data_basename_hash_new();
1376 if (follow_symlinks)
1381 while ((dir = readdir(dp)) != NULL)
1383 struct stat ent_sbuf;
1384 const gchar *name = dir->d_name;
1387 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1390 filepath = g_build_filename(pathl, name, NULL);
1391 if (stat_func(filepath, &ent_sbuf) >= 0)
1393 if (S_ISDIR(ent_sbuf.st_mode))
1395 /* we ignore the .thumbnails dir for cleanliness */
1397 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1398 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1399 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1400 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1402 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1407 if (files && filter_name_exists(name))
1409 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1410 flist = g_list_prepend(flist, fd);
1411 if (fd->sidecar_priority && !fd->disable_grouping)
1413 if (strcmp(fd->extension, ".xmp") != 0)
1414 file_data_basename_hash_insert(basename_hash, fd);
1416 xmp_files = g_list_append(xmp_files, fd);
1423 if (errno == EOVERFLOW)
1425 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1437 g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
1438 g_list_free(xmp_files);
1441 if (dirs) *dirs = dlist;
1445 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1447 *files = filelist_filter_out_sidecars(flist);
1449 if (basename_hash) file_data_basename_hash_free(basename_hash);
1454 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1456 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1459 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1461 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1464 FileData *file_data_new_group(const gchar *path_utf8)
1471 if (!stat_utf8(path_utf8, &st))
1477 if (S_ISDIR(st.st_mode))
1478 return file_data_new(path_utf8, &st, TRUE);
1480 dir = remove_level_from_path(path_utf8);
1482 filelist_read_real(dir, &files, NULL, TRUE);
1484 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1485 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1491 filelist_free(files);
1497 void filelist_free(GList *list)
1504 file_data_unref((FileData *)work->data);
1512 GList *filelist_copy(GList *list)
1514 GList *new_list = NULL;
1525 new_list = g_list_prepend(new_list, file_data_ref(fd));
1528 return g_list_reverse(new_list);
1531 GList *filelist_from_path_list(GList *list)
1533 GList *new_list = NULL;
1544 new_list = g_list_prepend(new_list, file_data_new_group(path));
1547 return g_list_reverse(new_list);
1550 GList *filelist_to_path_list(GList *list)
1552 GList *new_list = NULL;
1563 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1566 return g_list_reverse(new_list);
1569 GList *filelist_filter(GList *list, gboolean is_dir_list)
1573 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1578 FileData *fd = (FileData *)(work->data);
1579 const gchar *name = fd->name;
1581 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1582 (!is_dir_list && !filter_name_exists(name)) ||
1583 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1584 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1588 list = g_list_remove_link(list, link);
1589 file_data_unref(fd);
1600 *-----------------------------------------------------------------------------
1601 * filelist recursive
1602 *-----------------------------------------------------------------------------
1605 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1607 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1610 GList *filelist_sort_path(GList *list)
1612 return g_list_sort(list, filelist_sort_path_cb);
1615 static void filelist_recursive_append(GList **list, GList *dirs)
1622 FileData *fd = (FileData *)(work->data);
1626 if (filelist_read(fd, &f, &d))
1628 f = filelist_filter(f, FALSE);
1629 f = filelist_sort_path(f);
1630 *list = g_list_concat(*list, f);
1632 d = filelist_filter(d, TRUE);
1633 d = filelist_sort_path(d);
1634 filelist_recursive_append(list, d);
1642 static void filelist_recursive_append_full(GList **list, GList *dirs, SortType method, gboolean ascend)
1649 FileData *fd = (FileData *)(work->data);
1653 if (filelist_read(fd, &f, &d))
1655 f = filelist_filter(f, FALSE);
1656 f = filelist_sort_full(f, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1657 *list = g_list_concat(*list, f);
1659 d = filelist_filter(d, TRUE);
1660 d = filelist_sort_path(d);
1661 filelist_recursive_append_full(list, d, method, ascend);
1669 GList *filelist_recursive(FileData *dir_fd)
1674 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1675 list = filelist_filter(list, FALSE);
1676 list = filelist_sort_path(list);
1678 d = filelist_filter(d, TRUE);
1679 d = filelist_sort_path(d);
1680 filelist_recursive_append(&list, d);
1686 GList *filelist_recursive_full(FileData *dir_fd, SortType method, gboolean ascend)
1691 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1692 list = filelist_filter(list, FALSE);
1693 list = filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1695 d = filelist_filter(d, TRUE);
1696 d = filelist_sort_path(d);
1697 filelist_recursive_append_full(&list, d, method, ascend);
1704 *-----------------------------------------------------------------------------
1705 * file modification support
1706 *-----------------------------------------------------------------------------
1710 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1712 if (!fdci && fd) fdci = fd->change;
1716 g_free(fdci->source);
1721 if (fd) fd->change = NULL;
1724 static gboolean file_data_can_write_directly(FileData *fd)
1726 return filter_name_is_writable(fd->extension);
1729 static gboolean file_data_can_write_sidecar(FileData *fd)
1731 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1734 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1736 gchar *sidecar_path = NULL;
1739 if (!file_data_can_write_sidecar(fd)) return NULL;
1741 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1742 gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1745 FileData *sfd = work->data;
1747 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1749 sidecar_path = g_strdup(sfd->path);
1753 g_free(extended_extension);
1755 if (!existing_only && !sidecar_path)
1757 if (options->metadata.sidecar_extended_name)
1758 sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1761 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1762 sidecar_path = g_strconcat(base, ".xmp", NULL);
1767 return sidecar_path;
1771 * marks and orientation
1774 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1775 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1776 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1777 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1779 gboolean file_data_get_mark(FileData *fd, gint n)
1781 gboolean valid = (fd->valid_marks & (1 << n));
1783 if (file_data_get_mark_func[n] && !valid)
1785 guint old = fd->marks;
1786 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1788 if (!value != !(fd->marks & (1 << n)))
1790 fd->marks = fd->marks ^ (1 << n);
1793 fd->valid_marks |= (1 << n);
1794 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1796 file_data_unref(fd);
1798 else if (!old && fd->marks)
1804 return !!(fd->marks & (1 << n));
1807 guint file_data_get_marks(FileData *fd)
1810 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1814 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1817 if (!value == !file_data_get_mark(fd, n)) return;
1819 if (file_data_set_mark_func[n])
1821 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1826 fd->marks = fd->marks ^ (1 << n);
1828 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1830 file_data_unref(fd);
1832 else if (!old && fd->marks)
1837 file_data_increment_version(fd);
1838 file_data_send_notification(fd, NOTIFY_MARKS);
1841 gboolean file_data_filter_marks(FileData *fd, guint filter)
1844 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1845 return ((fd->marks & filter) == filter);
1848 GList *file_data_filter_marks_list(GList *list, guint filter)
1855 FileData *fd = work->data;
1859 if (!file_data_filter_marks(fd, filter))
1861 list = g_list_remove_link(list, link);
1862 file_data_unref(fd);
1870 gboolean file_data_filter_file_filter(FileData *fd, GRegex *filter)
1872 return g_regex_match(filter, fd->name, 0, NULL);
1875 GList *file_data_filter_file_filter_list(GList *list, GRegex *filter)
1882 FileData *fd = work->data;
1886 if (!file_data_filter_file_filter(fd, filter))
1888 list = g_list_remove_link(list, link);
1889 file_data_unref(fd);
1897 static gboolean file_data_filter_class(FileData *fd, guint filter)
1901 for (i = 0; i < FILE_FORMAT_CLASSES; i++)
1903 if (filter & (1 << i))
1905 if ((FileFormatClass)i == filter_file_get_class(fd->path))
1915 GList *file_data_filter_class_list(GList *list, guint filter)
1922 FileData *fd = work->data;
1926 if (!file_data_filter_class(fd, filter))
1928 list = g_list_remove_link(list, link);
1929 file_data_unref(fd);
1937 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1939 FileData *fd = value;
1940 file_data_increment_version(fd);
1941 file_data_send_notification(fd, NOTIFY_MARKS);
1944 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1946 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1948 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1950 file_data_get_mark_func[n] = get_mark_func;
1951 file_data_set_mark_func[n] = set_mark_func;
1952 file_data_mark_func_data[n] = data;
1953 file_data_destroy_mark_func[n] = notify;
1955 if (get_mark_func && file_data_pool)
1957 /* this effectively changes all known files */
1958 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1964 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1966 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1967 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1968 if (data) *data = file_data_mark_func_data[n];
1971 gint file_data_get_user_orientation(FileData *fd)
1973 return fd->user_orientation;
1976 void file_data_set_user_orientation(FileData *fd, gint value)
1978 if (fd->user_orientation == value) return;
1980 fd->user_orientation = value;
1981 file_data_increment_version(fd);
1982 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1987 * file_data - operates on the given fd
1988 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1992 /* return list of sidecar file extensions in a string */
1993 gchar *file_data_sc_list_to_string(FileData *fd)
1996 GString *result = g_string_new("");
1998 work = fd->sidecar_files;
2001 FileData *sfd = work->data;
2003 result = g_string_append(result, "+ ");
2004 result = g_string_append(result, sfd->extension);
2006 if (work) result = g_string_append_c(result, ' ');
2009 return g_string_free(result, FALSE);
2015 * add FileDataChangeInfo (see typedefs.h) for the given operation
2016 * uses file_data_add_change_info
2018 * fails if the fd->change already exists - change operations can't run in parallel
2019 * fd->change_info works as a lock
2021 * dest can be NULL - in this case the current name is used for now, it will
2026 FileDataChangeInfo types:
2028 MOVE - path is changed, name may be changed too
2029 RENAME - path remains unchanged, name is changed
2030 extension should remain (FIXME should we allow editing extension? it will make problems with grouping)
2031 sidecar names are changed too, extensions are not changed
2033 UPDATE - file size, date or grouping has been changed
2036 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
2038 FileDataChangeInfo *fdci;
2040 if (fd->change) return FALSE;
2042 fdci = g_new0(FileDataChangeInfo, 1);
2047 fdci->source = g_strdup(src);
2049 fdci->source = g_strdup(fd->path);
2052 fdci->dest = g_strdup(dest);
2059 static void file_data_planned_change_remove(FileData *fd)
2061 if (file_data_planned_change_hash &&
2062 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
2064 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
2066 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
2067 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
2068 file_data_unref(fd);
2069 if (g_hash_table_size(file_data_planned_change_hash) == 0)
2071 g_hash_table_destroy(file_data_planned_change_hash);
2072 file_data_planned_change_hash = NULL;
2073 DEBUG_1("planned change: empty");
2080 void file_data_free_ci(FileData *fd)
2082 FileDataChangeInfo *fdci = fd->change;
2086 file_data_planned_change_remove(fd);
2088 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
2090 g_free(fdci->source);
2098 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
2100 FileDataChangeInfo *fdci = fd->change;
2102 fdci->regroup_when_finished = enable;
2105 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
2109 if (fd->parent) fd = fd->parent;
2111 if (fd->change) return FALSE;
2113 work = fd->sidecar_files;
2116 FileData *sfd = work->data;
2118 if (sfd->change) return FALSE;
2122 file_data_add_ci(fd, type, NULL, NULL);
2124 work = fd->sidecar_files;
2127 FileData *sfd = work->data;
2129 file_data_add_ci(sfd, type, NULL, NULL);
2136 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
2140 if (fd->parent) fd = fd->parent;
2142 if (!fd->change || fd->change->type != type) return FALSE;
2144 work = fd->sidecar_files;
2147 FileData *sfd = work->data;
2149 if (!sfd->change || sfd->change->type != type) return FALSE;
2157 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
2159 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
2160 file_data_sc_update_ci_copy(fd, dest_path);
2164 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
2166 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
2167 file_data_sc_update_ci_move(fd, dest_path);
2171 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
2173 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
2174 file_data_sc_update_ci_rename(fd, dest_path);
2178 gboolean file_data_sc_add_ci_delete(FileData *fd)
2180 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
2183 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
2185 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
2186 file_data_sc_update_ci_unspecified(fd, dest_path);
2190 gboolean file_data_add_ci_write_metadata(FileData *fd)
2192 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
2195 void file_data_sc_free_ci(FileData *fd)
2199 if (fd->parent) fd = fd->parent;
2201 file_data_free_ci(fd);
2203 work = fd->sidecar_files;
2206 FileData *sfd = work->data;
2208 file_data_free_ci(sfd);
2213 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2216 gboolean ret = TRUE;
2221 FileData *fd = work->data;
2223 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2230 static void file_data_sc_revert_ci_list(GList *fd_list)
2237 FileData *fd = work->data;
2239 file_data_sc_free_ci(fd);
2244 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2251 FileData *fd = work->data;
2253 if (!func(fd, dest))
2255 file_data_sc_revert_ci_list(work->prev);
2264 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2266 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2269 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2271 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2274 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2276 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2279 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2281 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2284 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2287 gboolean ret = TRUE;
2292 FileData *fd = work->data;
2294 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2301 void file_data_free_ci_list(GList *fd_list)
2308 FileData *fd = work->data;
2310 file_data_free_ci(fd);
2315 void file_data_sc_free_ci_list(GList *fd_list)
2322 FileData *fd = work->data;
2324 file_data_sc_free_ci(fd);
2330 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2331 * fails if fd->change does not exist or the change type does not match
2334 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2336 FileDataChangeType type = fd->change->type;
2338 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2342 if (!file_data_planned_change_hash)
2343 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2345 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2347 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2348 g_hash_table_remove(file_data_planned_change_hash, old_path);
2349 file_data_unref(fd);
2352 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
2357 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2358 g_hash_table_remove(file_data_planned_change_hash, new_path);
2359 file_data_unref(ofd);
2362 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2364 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2369 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2371 gchar *old_path = fd->change->dest;
2373 fd->change->dest = g_strdup(dest_path);
2374 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2378 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2380 const gchar *extension = registered_extension_from_path(fd->change->source);
2381 gchar *base = remove_extension_from_path(dest_path);
2382 gchar *old_path = fd->change->dest;
2384 fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2385 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2391 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2394 gchar *dest_path_full = NULL;
2396 if (fd->parent) fd = fd->parent;
2400 dest_path = fd->path;
2402 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2404 gchar *dir = remove_level_from_path(fd->path);
2406 dest_path_full = g_build_filename(dir, dest_path, NULL);
2408 dest_path = dest_path_full;
2410 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2412 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2413 dest_path = dest_path_full;
2416 file_data_update_ci_dest(fd, dest_path);
2418 work = fd->sidecar_files;
2421 FileData *sfd = work->data;
2423 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2427 g_free(dest_path_full);
2430 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2432 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2433 file_data_sc_update_ci(fd, dest_path);
2437 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2439 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2442 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2444 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2447 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2449 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2452 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2454 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2457 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2459 gboolean (*func)(FileData *, const gchar *))
2462 gboolean ret = TRUE;
2467 FileData *fd = work->data;
2469 if (!func(fd, dest)) ret = FALSE;
2476 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2478 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2481 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2483 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2486 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2488 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2493 * verify source and dest paths - dest image exists, etc.
2494 * it should detect all possible problems with the planned operation
2497 gint file_data_verify_ci(FileData *fd, GList *list)
2499 gint ret = CHANGE_OK;
2502 FileData *fd1 = NULL;
2506 DEBUG_1("Change checked: no change info: %s", fd->path);
2510 if (!isname(fd->path))
2512 /* this probably should not happen */
2513 ret |= CHANGE_NO_SRC;
2514 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2518 dir = remove_level_from_path(fd->path);
2520 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2521 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2522 fd->change->type != FILEDATA_CHANGE_RENAME &&
2523 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2526 ret |= CHANGE_WARN_UNSAVED_META;
2527 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2530 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2531 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2532 !access_file(fd->path, R_OK))
2534 ret |= CHANGE_NO_READ_PERM;
2535 DEBUG_1("Change checked: no read permission: %s", fd->path);
2537 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2538 !access_file(dir, W_OK))
2540 ret |= CHANGE_NO_WRITE_PERM_DIR;
2541 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2543 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2544 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2545 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2546 !access_file(fd->path, W_OK))
2548 ret |= CHANGE_WARN_NO_WRITE_PERM;
2549 DEBUG_1("Change checked: no write permission: %s", fd->path);
2551 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2552 - that means that there are no hard errors and warnings can be disabled
2553 - the destination is determined during the check
2555 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2557 /* determine destination file */
2558 gboolean have_dest = FALSE;
2559 gchar *dest_dir = NULL;
2561 if (options->metadata.save_in_image_file)
2563 if (file_data_can_write_directly(fd))
2565 /* we can write the file directly */
2566 if (access_file(fd->path, W_OK))
2572 if (options->metadata.warn_on_write_problems)
2574 ret |= CHANGE_WARN_NO_WRITE_PERM;
2575 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2579 else if (file_data_can_write_sidecar(fd))
2581 /* we can write sidecar */
2582 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2583 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2585 file_data_update_ci_dest(fd, sidecar);
2590 if (options->metadata.warn_on_write_problems)
2592 ret |= CHANGE_WARN_NO_WRITE_PERM;
2593 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2602 /* write private metadata file under ~/.geeqie */
2604 /* If an existing metadata file exists, we will try writing to
2605 * it's location regardless of the user's preference.
2607 gchar *metadata_path = NULL;
2609 /* but ignore XMP if we are not able to write it */
2610 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2612 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2614 if (metadata_path && !access_file(metadata_path, W_OK))
2616 g_free(metadata_path);
2617 metadata_path = NULL;
2624 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2625 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2627 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2629 metadata_path = g_build_filename(dest_dir, filename, NULL);
2633 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2635 file_data_update_ci_dest(fd, metadata_path);
2640 ret |= CHANGE_NO_WRITE_PERM_DEST;
2641 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2643 g_free(metadata_path);
2648 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2653 same = (strcmp(fd->path, fd->change->dest) == 0);
2657 const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2658 if (!dest_ext) dest_ext = "";
2659 if (!options->file_filter.disable_file_extension_checks)
2661 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2663 ret |= CHANGE_WARN_CHANGED_EXT;
2664 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2670 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /** @FIXME this is now needed for running editors */
2672 ret |= CHANGE_WARN_SAME;
2673 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2677 dest_dir = remove_level_from_path(fd->change->dest);
2679 if (!isdir(dest_dir))
2681 ret |= CHANGE_NO_DEST_DIR;
2682 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2684 else if (!access_file(dest_dir, W_OK))
2686 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2687 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2691 if (isfile(fd->change->dest))
2693 if (!access_file(fd->change->dest, W_OK))
2695 ret |= CHANGE_NO_WRITE_PERM_DEST;
2696 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2700 ret |= CHANGE_WARN_DEST_EXISTS;
2701 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2704 else if (isdir(fd->change->dest))
2706 ret |= CHANGE_DEST_EXISTS;
2707 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2714 /* During a rename operation, check if another planned destination file has
2717 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2718 fd->change->type == FILEDATA_CHANGE_COPY ||
2719 fd->change->type == FILEDATA_CHANGE_MOVE)
2726 if (fd1 != NULL && fd != fd1 )
2728 if (!strcmp(fd->change->dest, fd1->change->dest))
2730 ret |= CHANGE_DUPLICATE_DEST;
2736 fd->change->error = ret;
2737 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2744 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2749 ret = file_data_verify_ci(fd, list);
2751 work = fd->sidecar_files;
2754 FileData *sfd = work->data;
2756 ret |= file_data_verify_ci(sfd, list);
2763 gchar *file_data_get_error_string(gint error)
2765 GString *result = g_string_new("");
2767 if (error & CHANGE_NO_SRC)
2769 if (result->len > 0) g_string_append(result, ", ");
2770 g_string_append(result, _("file or directory does not exist"));
2773 if (error & CHANGE_DEST_EXISTS)
2775 if (result->len > 0) g_string_append(result, ", ");
2776 g_string_append(result, _("destination already exists"));
2779 if (error & CHANGE_NO_WRITE_PERM_DEST)
2781 if (result->len > 0) g_string_append(result, ", ");
2782 g_string_append(result, _("destination can't be overwritten"));
2785 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2787 if (result->len > 0) g_string_append(result, ", ");
2788 g_string_append(result, _("destination directory is not writable"));
2791 if (error & CHANGE_NO_DEST_DIR)
2793 if (result->len > 0) g_string_append(result, ", ");
2794 g_string_append(result, _("destination directory does not exist"));
2797 if (error & CHANGE_NO_WRITE_PERM_DIR)
2799 if (result->len > 0) g_string_append(result, ", ");
2800 g_string_append(result, _("source directory is not writable"));
2803 if (error & CHANGE_NO_READ_PERM)
2805 if (result->len > 0) g_string_append(result, ", ");
2806 g_string_append(result, _("no read permission"));
2809 if (error & CHANGE_WARN_NO_WRITE_PERM)
2811 if (result->len > 0) g_string_append(result, ", ");
2812 g_string_append(result, _("file is readonly"));
2815 if (error & CHANGE_WARN_DEST_EXISTS)
2817 if (result->len > 0) g_string_append(result, ", ");
2818 g_string_append(result, _("destination already exists and will be overwritten"));
2821 if (error & CHANGE_WARN_SAME)
2823 if (result->len > 0) g_string_append(result, ", ");
2824 g_string_append(result, _("source and destination are the same"));
2827 if (error & CHANGE_WARN_CHANGED_EXT)
2829 if (result->len > 0) g_string_append(result, ", ");
2830 g_string_append(result, _("source and destination have different extension"));
2833 if (error & CHANGE_WARN_UNSAVED_META)
2835 if (result->len > 0) g_string_append(result, ", ");
2836 g_string_append(result, _("there are unsaved metadata changes for the file"));
2839 if (error & CHANGE_DUPLICATE_DEST)
2841 if (result->len > 0) g_string_append(result, ", ");
2842 g_string_append(result, _("another destination file has the same filename"));
2845 return g_string_free(result, FALSE);
2848 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2851 gint all_errors = 0;
2852 gint common_errors = ~0;
2857 if (!list) return 0;
2859 num = g_list_length(list);
2860 errors = g_new(int, num);
2871 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2872 all_errors |= error;
2873 common_errors &= error;
2880 if (desc && all_errors)
2883 GString *result = g_string_new("");
2887 gchar *str = file_data_get_error_string(common_errors);
2888 g_string_append(result, str);
2889 g_string_append(result, "\n");
2903 error = errors[i] & ~common_errors;
2907 gchar *str = file_data_get_error_string(error);
2908 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2913 *desc = g_string_free(result, FALSE);
2922 * perform the change described by FileFataChangeInfo
2923 * it is used for internal operations,
2924 * this function actually operates with files on the filesystem
2925 * it should implement safe delete
2928 static gboolean file_data_perform_move(FileData *fd)
2930 g_assert(!strcmp(fd->change->source, fd->path));
2931 return move_file(fd->change->source, fd->change->dest);
2934 static gboolean file_data_perform_copy(FileData *fd)
2936 g_assert(!strcmp(fd->change->source, fd->path));
2937 return copy_file(fd->change->source, fd->change->dest);
2940 static gboolean file_data_perform_delete(FileData *fd)
2942 if (isdir(fd->path) && !islink(fd->path))
2943 return rmdir_utf8(fd->path);
2945 if (options->file_ops.safe_delete_enable)
2946 return file_util_safe_unlink(fd->path);
2948 return unlink_file(fd->path);
2951 gboolean file_data_perform_ci(FileData *fd)
2953 FileDataChangeType type = fd->change->type;
2957 case FILEDATA_CHANGE_MOVE:
2958 return file_data_perform_move(fd);
2959 case FILEDATA_CHANGE_COPY:
2960 return file_data_perform_copy(fd);
2961 case FILEDATA_CHANGE_RENAME:
2962 return file_data_perform_move(fd); /* the same as move */
2963 case FILEDATA_CHANGE_DELETE:
2964 return file_data_perform_delete(fd);
2965 case FILEDATA_CHANGE_WRITE_METADATA:
2966 return metadata_write_perform(fd);
2967 case FILEDATA_CHANGE_UNSPECIFIED:
2968 /* nothing to do here */
2976 gboolean file_data_sc_perform_ci(FileData *fd)
2979 gboolean ret = TRUE;
2980 FileDataChangeType type = fd->change->type;
2982 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2984 work = fd->sidecar_files;
2987 FileData *sfd = work->data;
2989 if (!file_data_perform_ci(sfd)) ret = FALSE;
2993 if (!file_data_perform_ci(fd)) ret = FALSE;
2999 * updates FileData structure according to FileDataChangeInfo
3002 gboolean file_data_apply_ci(FileData *fd)
3004 FileDataChangeType type = fd->change->type;
3006 /** @FIXME delete ?*/
3007 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
3009 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
3010 file_data_planned_change_remove(fd);
3012 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
3014 /* this change overwrites another file which is already known to other modules
3015 renaming fd would create duplicate FileData structure
3016 the best thing we can do is nothing
3018 /** @FIXME maybe we could copy stuff like marks
3020 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
3024 file_data_set_path(fd, fd->change->dest);
3027 file_data_increment_version(fd);
3028 file_data_send_notification(fd, NOTIFY_CHANGE);
3033 gboolean file_data_sc_apply_ci(FileData *fd)
3036 FileDataChangeType type = fd->change->type;
3038 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3040 work = fd->sidecar_files;
3043 FileData *sfd = work->data;
3045 file_data_apply_ci(sfd);
3049 file_data_apply_ci(fd);
3054 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
3057 if (fd->parent) fd = fd->parent;
3058 if (!g_list_find(list, fd)) return FALSE;
3060 work = fd->sidecar_files;
3063 if (!g_list_find(list, work->data)) return FALSE;
3069 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
3074 /* change partial groups to independent files */
3079 FileData *fd = work->data;
3082 if (!file_data_list_contains_whole_group(list, fd))
3084 file_data_disable_grouping(fd, TRUE);
3087 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
3093 /* remove sidecars from the list,
3094 they can be still accessed via main_fd->sidecar_files */
3098 FileData *fd = work->data;
3102 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
3104 out = g_list_prepend(out, file_data_ref(fd));
3108 filelist_free(list);
3109 out = g_list_reverse(out);
3119 * notify other modules about the change described by FileDataChangeInfo
3122 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks */
3123 /** @FIXME do we need the ignore_list? It looks like a workaround for ineffective
3124 implementation in view_file_list.c */
3127 typedef struct _NotifyIdleData NotifyIdleData;
3129 struct _NotifyIdleData {
3135 typedef struct _NotifyData NotifyData;
3137 struct _NotifyData {
3138 FileDataNotifyFunc func;
3140 NotifyPriority priority;
3143 static GList *notify_func_list = NULL;
3145 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
3147 NotifyData *nda = (NotifyData *)a;
3148 NotifyData *ndb = (NotifyData *)b;
3150 if (nda->priority < ndb->priority) return -1;
3151 if (nda->priority > ndb->priority) return 1;
3155 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
3158 GList *work = notify_func_list;
3162 NotifyData *nd = (NotifyData *)work->data;
3164 if (nd->func == func && nd->data == data)
3166 g_warning("Notify func already registered");
3172 nd = g_new(NotifyData, 1);
3175 nd->priority = priority;
3177 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
3178 DEBUG_2("Notify func registered: %p", nd);
3183 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
3185 GList *work = notify_func_list;
3189 NotifyData *nd = (NotifyData *)work->data;
3191 if (nd->func == func && nd->data == data)
3193 notify_func_list = g_list_delete_link(notify_func_list, work);
3195 DEBUG_2("Notify func unregistered: %p", nd);
3201 g_warning("Notify func not found");
3206 gboolean file_data_send_notification_idle_cb(gpointer data)
3208 NotifyIdleData *nid = (NotifyIdleData *)data;
3209 GList *work = notify_func_list;
3213 NotifyData *nd = (NotifyData *)work->data;
3215 nd->func(nid->fd, nid->type, nd->data);
3218 file_data_unref(nid->fd);
3223 void file_data_send_notification(FileData *fd, NotifyType type)
3225 GList *work = notify_func_list;
3229 NotifyData *nd = (NotifyData *)work->data;
3231 nd->func(fd, type, nd->data);
3235 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3236 nid->fd = file_data_ref(fd);
3238 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3242 static GHashTable *file_data_monitor_pool = NULL;
3243 static guint realtime_monitor_id = 0; /* event source id */
3245 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
3249 file_data_check_changed_files(fd);
3251 DEBUG_1("monitor %s", fd->path);
3254 static gboolean realtime_monitor_cb(gpointer data)
3256 if (!options->update_on_time_change) return TRUE;
3257 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
3261 gboolean file_data_register_real_time_monitor(FileData *fd)
3267 if (!file_data_monitor_pool)
3268 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3270 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3272 DEBUG_1("Register realtime %d %s", count, fd->path);
3275 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3277 if (!realtime_monitor_id)
3279 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
3285 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3289 g_assert(file_data_monitor_pool);
3291 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3293 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3295 g_assert(count > 0);
3300 g_hash_table_remove(file_data_monitor_pool, fd);
3302 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3304 file_data_unref(fd);
3306 if (g_hash_table_size(file_data_monitor_pool) == 0)
3308 g_source_remove(realtime_monitor_id);
3309 realtime_monitor_id = 0;
3317 *-----------------------------------------------------------------------------
3318 * Saving marks list, clearing marks
3319 * Uses file_data_pool
3320 *-----------------------------------------------------------------------------
3323 static void marks_get_files(gpointer key, gpointer value, gpointer userdata)
3325 gchar *file_name = key;
3326 GString *result = userdata;
3329 if (isfile(file_name))
3332 if (fd && fd->marks > 0)
3334 g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks);
3339 gboolean marks_list_load(const gchar *path)
3347 pathl = path_from_utf8(path);
3348 f = fopen(pathl, "r");
3350 if (!f) return FALSE;
3352 /* first line must start with Marks comment */
3353 if (!fgets(s_buf, sizeof(s_buf), f) ||
3354 strncmp(s_buf, "#Marks", 6) != 0)
3360 while (fgets(s_buf, sizeof(s_buf), f))
3362 if (s_buf[0]=='#') continue;
3363 file_path = strtok(s_buf, ",");
3364 marks_value = strtok(NULL, ",");
3365 if (isfile(file_path))
3367 FileData *fd = file_data_new_no_grouping(file_path);
3372 gint mark_no = 1 << n;
3373 if (atoi(marks_value) & mark_no)
3375 file_data_set_mark(fd, n , 1);
3386 gboolean marks_list_save(gchar *path, gboolean save)
3388 SecureSaveInfo *ssi;
3390 GString *marks = g_string_new("");
3392 pathl = path_from_utf8(path);
3393 ssi = secure_open(pathl);
3397 log_printf(_("Error: Unable to write marks lists to: %s\n"), path);
3401 secure_fprintf(ssi, "#Marks lists\n");
3405 g_hash_table_foreach(file_data_pool, marks_get_files, marks);
3407 secure_fprintf(ssi, "%s", marks->str);
3408 g_string_free(marks, FALSE);
3410 secure_fprintf(ssi, "#end\n");
3411 return (secure_close(ssi) == 0);
3414 static void marks_clear(gpointer key, gpointer value, gpointer userdata)
3416 gchar *file_name = key;
3421 if (isfile(file_name))
3424 if (fd && fd->marks > 0)
3430 if (fd->marks & mark_no)
3432 file_data_set_mark(fd, n , 0);
3440 void marks_clear_all()
3442 g_hash_table_foreach(file_data_pool, marks_clear, NULL);
3445 void file_data_set_page_num(FileData *fd, gint page_num)
3447 if (fd->page_total > 1 && page_num < 0)
3449 fd->page_num = fd->page_total - 1;
3451 else if (fd->page_total > 1 && page_num <= fd->page_total)
3453 fd->page_num = page_num - 1;
3459 file_data_send_notification(fd, NOTIFY_REREAD);
3462 void file_data_inc_page_num(FileData *fd)
3464 if (fd->page_total > 0 && fd->page_num < fd->page_total - 1)
3466 fd->page_num = fd->page_num + 1;
3468 else if (fd->page_total == 0)
3470 fd->page_num = fd->page_num + 1;
3472 file_data_send_notification(fd, NOTIFY_REREAD);
3475 void file_data_dec_page_num(FileData *fd)
3477 if (fd->page_num > 0)
3479 fd->page_num = fd->page_num - 1;
3481 file_data_send_notification(fd, NOTIFY_REREAD);
3484 void file_data_set_page_total(FileData *fd, gint page_total)
3486 fd->page_total = page_total;
3489 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */