2 * Copyright (C) 2006 John Ellis
3 * Copyright (C) 2008 - 2016 The Geeqie Team
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 #include "filefilter.h"
27 #include "thumb-standard.h"
28 #include "ui-fileops.h"
31 #include "histogram.h"
32 #include "secure-save.h"
40 gint global_file_data_count = 0;
43 static GHashTable *file_data_pool = NULL;
44 static GHashTable *file_data_planned_change_hash = NULL;
46 static gint sidecar_file_priority(const gchar *extension);
47 static void file_data_check_sidecars(const GList *basename_list);
48 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
51 static SortType filelist_sort_method = SORT_NONE;
52 static gboolean filelist_sort_ascend = TRUE;
55 *-----------------------------------------------------------------------------
56 * text conversion utils
57 *-----------------------------------------------------------------------------
60 gchar *text_from_size(gint64 size)
66 /* what I would like to use is printf("%'d", size)
67 * BUT: not supported on every libc :(
71 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
72 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
76 a = g_strdup_printf("%d", (guint)size);
82 b = g_new(gchar, l + n + 1);
107 gchar *text_from_size_abrev(gint64 size)
109 if (size < (gint64)1024)
111 return g_strdup_printf(_("%d bytes"), (gint)size);
113 if (size < (gint64)1048576)
115 return g_strdup_printf(_("%.1f KiB"), (gdouble)size / 1024.0);
117 if (size < (gint64)1073741824)
119 return g_strdup_printf(_("%.1f MiB"), (gdouble)size / 1048576.0);
122 /* to avoid overflowing the gdouble, do division in two steps */
124 return g_strdup_printf(_("%.1f GiB"), (gdouble)size / 1024.0);
127 /* note: returned string is valid until next call to text_from_time() */
128 const gchar *text_from_time(time_t t)
130 static gchar *ret = NULL;
134 GError *error = NULL;
136 btime = localtime(&t);
138 /* the %x warning about 2 digit years is not an error */
139 buflen = strftime(buf, sizeof(buf), "%x %X", btime);
140 if (buflen < 1) return "";
143 ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
146 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
155 *-----------------------------------------------------------------------------
156 * changed files detection and notification
157 *-----------------------------------------------------------------------------
160 void file_data_increment_version(FileData *fd)
166 fd->parent->version++;
167 fd->parent->valid_marks = 0;
171 static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st)
173 if (fd->size != st->st_size ||
174 fd->date != st->st_mtime)
176 fd->size = st->st_size;
177 fd->date = st->st_mtime;
178 fd->cdate = st->st_ctime;
179 fd->mode = st->st_mode;
180 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
181 fd->thumb_pixbuf = NULL;
182 file_data_increment_version(fd);
183 file_data_send_notification(fd, NOTIFY_REREAD);
189 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
191 gboolean ret = FALSE;
194 ret = file_data_check_changed_single_file(fd, st);
196 work = fd->sidecar_files;
199 FileData *sfd = work->data;
203 if (!stat_utf8(sfd->path, &st))
208 file_data_disconnect_sidecar_file(fd, sfd);
210 file_data_increment_version(sfd);
211 file_data_send_notification(sfd, NOTIFY_REREAD);
212 file_data_unref(sfd);
216 ret |= file_data_check_changed_files_recursive(sfd, &st);
222 gboolean file_data_check_changed_files(FileData *fd)
224 gboolean ret = FALSE;
227 if (fd->parent) fd = fd->parent;
229 if (!stat_utf8(fd->path, &st))
233 FileData *sfd = NULL;
235 /* parent is missing, we have to rebuild whole group */
240 /* file_data_disconnect_sidecar_file might delete the file,
241 we have to keep the reference to prevent this */
242 sidecars = filelist_copy(fd->sidecar_files);
250 file_data_disconnect_sidecar_file(fd, sfd);
252 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
253 /* now we can release the sidecars */
254 filelist_free(sidecars);
255 file_data_increment_version(fd);
256 file_data_send_notification(fd, NOTIFY_REREAD);
261 ret |= file_data_check_changed_files_recursive(fd, &st);
268 *-----------------------------------------------------------------------------
269 * file name, extension, sorting, ...
270 *-----------------------------------------------------------------------------
273 static void file_data_set_collate_keys(FileData *fd)
275 gchar *caseless_name;
278 valid_name = g_filename_display_name(fd->name);
279 caseless_name = g_utf8_casefold(valid_name, -1);
281 g_free(fd->collate_key_name);
282 g_free(fd->collate_key_name_nocase);
284 if (options->file_sort.natural)
286 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
287 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
291 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
292 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
296 g_free(caseless_name);
299 static void file_data_set_path(FileData *fd, const gchar *path)
301 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
302 g_assert(file_data_pool);
306 if (fd->original_path)
308 g_hash_table_remove(file_data_pool, fd->original_path);
309 g_free(fd->original_path);
312 g_assert(!g_hash_table_lookup(file_data_pool, path));
314 fd->original_path = g_strdup(path);
315 g_hash_table_insert(file_data_pool, fd->original_path, fd);
317 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
319 fd->path = g_strdup(path);
321 fd->extension = fd->name + 1;
322 file_data_set_collate_keys(fd);
326 fd->path = g_strdup(path);
327 fd->name = filename_from_path(fd->path);
329 if (strcmp(fd->name, "..") == 0)
331 gchar *dir = remove_level_from_path(path);
333 fd->path = remove_level_from_path(dir);
336 fd->extension = fd->name + 2;
337 file_data_set_collate_keys(fd);
340 else if (strcmp(fd->name, ".") == 0)
343 fd->path = remove_level_from_path(path);
345 fd->extension = fd->name + 1;
346 file_data_set_collate_keys(fd);
350 fd->extension = registered_extension_from_path(fd->path);
351 if (fd->extension == NULL)
353 fd->extension = fd->name + strlen(fd->name);
356 fd->sidecar_priority = sidecar_file_priority(fd->extension);
357 file_data_set_collate_keys(fd);
361 *-----------------------------------------------------------------------------
362 * create or reuse Filedata
363 *-----------------------------------------------------------------------------
366 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
372 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
374 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
377 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
379 fd = g_hash_table_lookup(file_data_pool, path_utf8);
385 if (!fd && file_data_planned_change_hash)
387 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
390 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
391 if (!isfile(fd->path))
394 file_data_apply_ci(fd);
407 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
410 changed = file_data_check_changed_single_file(fd, st);
412 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
417 fd = g_new0(FileData, 1);
418 #ifdef DEBUG_FILEDATA
419 global_file_data_count++;
420 DEBUG_2("file data count++: %d", global_file_data_count);
423 fd->size = st->st_size;
424 fd->date = st->st_mtime;
425 fd->cdate = st->st_ctime;
426 fd->mode = st->st_mode;
428 fd->magick = FD_MAGICK;
430 fd->rating = STAR_RATING_NOT_READ;
431 fd->format_class = filter_file_get_class(path_utf8);
435 user = getpwuid(st->st_uid);
438 fd->owner = g_strdup_printf("%u", st->st_uid);
442 fd->owner = g_strdup(user->pw_name);
445 group = getgrgid(st->st_gid);
448 fd->group = g_strdup_printf("%u", st->st_gid);
452 fd->group = g_strdup(group->gr_name);
455 fd->sym_link = get_symbolic_link(path_utf8);
457 if (disable_sidecars) fd->disable_grouping = TRUE;
459 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
464 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
466 gchar *path_utf8 = path_to_utf8(path);
467 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
473 FileData *file_data_new_simple(const gchar *path_utf8)
478 if (!stat_utf8(path_utf8, &st))
484 fd = g_hash_table_lookup(file_data_pool, path_utf8);
485 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
494 void read_exif_time_data(FileData *file)
496 if (file->exifdate > 0)
498 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
509 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
510 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), (void *)file, file->path);
515 uint year, month, day, hour, min, sec;
517 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
518 time_str.tm_year = year - 1900;
519 time_str.tm_mon = month - 1;
520 time_str.tm_mday = day;
521 time_str.tm_hour = hour;
522 time_str.tm_min = min;
523 time_str.tm_sec = sec;
524 time_str.tm_isdst = 0;
526 file->exifdate = mktime(&time_str);
532 void read_exif_time_digitized_data(FileData *file)
534 if (file->exifdate_digitized > 0)
536 DEBUG_1("%s set_exif_time_digitized_data: Already exists for %s", get_exec_time(), file->path);
547 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeDigitized");
548 DEBUG_2("%s set_exif_time_digitized_data: reading %p %s", get_exec_time(), (void *)file, file->path);
553 uint year, month, day, hour, min, sec;
555 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
556 time_str.tm_year = year - 1900;
557 time_str.tm_mon = month - 1;
558 time_str.tm_mday = day;
559 time_str.tm_hour = hour;
560 time_str.tm_min = min;
561 time_str.tm_sec = sec;
562 time_str.tm_isdst = 0;
564 file->exifdate_digitized = mktime(&time_str);
570 void read_rating_data(FileData *file)
574 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
577 file->rating = atoi(rating_str);
586 //void set_exif_time_data(GList *files)
588 //DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
592 //FileData *file = files->data;
594 //read_exif_time_data(file);
595 //files = files->next;
599 //void set_exif_time_digitized_data(GList *files)
601 //DEBUG_1("%s set_exif_time_digitized_data: ...", get_exec_time());
605 //FileData *file = files->data;
607 //read_exif_time_digitized_data(file);
608 //files = files->next;
612 //void set_rating_data(GList *files)
615 //DEBUG_1("%s set_rating_data: ...", get_exec_time());
619 //FileData *file = files->data;
620 //rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
623 //file->rating = atoi(rating_str);
624 //g_free(rating_str);
626 //files = files->next;
630 FileData *file_data_new_no_grouping(const gchar *path_utf8)
634 if (!stat_utf8(path_utf8, &st))
640 return file_data_new(path_utf8, &st, TRUE);
643 FileData *file_data_new_dir(const gchar *path_utf8)
647 if (!stat_utf8(path_utf8, &st))
653 /* dir or non-existing yet */
654 g_assert(S_ISDIR(st.st_mode));
656 return file_data_new(path_utf8, &st, TRUE);
660 *-----------------------------------------------------------------------------
662 *-----------------------------------------------------------------------------
665 #ifdef DEBUG_FILEDATA
666 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
668 FileData *file_data_ref(FileData *fd)
671 if (fd == NULL) return NULL;
672 if (fd->magick != FD_MAGICK)
673 #ifdef DEBUG_FILEDATA
674 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, (void *)fd);
676 log_printf("Error: fd magick mismatch fd=%p", fd);
678 g_assert(fd->magick == FD_MAGICK);
681 #ifdef DEBUG_FILEDATA
682 DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->path, file, line);
684 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
690 * @brief Print ref. count and image name
693 * Print image ref. count and full path name of all images in
694 * the file_data_pool.
696 * Used only by DEBUG_FD()
698 void file_data_dump()
705 list = g_hash_table_get_values(file_data_pool);
707 log_printf("%d", global_file_data_count);
708 log_printf("%d", g_list_length(list));
713 log_printf("%-4d %s", fd->ref, fd->path);
721 static void file_data_free(FileData *fd)
723 g_assert(fd->magick == FD_MAGICK);
724 g_assert(fd->ref == 0);
725 g_assert(!fd->locked);
727 #ifdef DEBUG_FILEDATA
728 global_file_data_count--;
729 DEBUG_2("file data count--: %d", global_file_data_count);
732 metadata_cache_free(fd);
733 g_hash_table_remove(file_data_pool, fd->original_path);
736 g_free(fd->original_path);
737 g_free(fd->collate_key_name);
738 g_free(fd->collate_key_name_nocase);
739 g_free(fd->extended_extension);
740 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
741 histmap_free(fd->histmap);
744 g_free(fd->sym_link);
745 g_free(fd->format_name);
746 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
748 file_data_change_info_free(NULL, fd);
753 * @brief Checks if the FileData is referenced
755 * Checks the refcount and whether the FileData is locked.
757 static gboolean file_data_check_has_ref(FileData *fd)
759 return fd->ref > 0 || fd->locked;
763 * @brief Consider freeing a FileData.
765 * This function will free a FileData and its children provided that neither its parent nor it has
766 * a positive refcount, and provided that neither is locked.
768 static void file_data_consider_free(FileData *fd)
771 FileData *parent = fd->parent ? fd->parent : fd;
773 g_assert(fd->magick == FD_MAGICK);
774 if (file_data_check_has_ref(fd)) return;
775 if (file_data_check_has_ref(parent)) return;
777 work = parent->sidecar_files;
780 FileData *sfd = work->data;
781 if (file_data_check_has_ref(sfd)) return;
785 /* Neither the parent nor the siblings are referenced, so we can free everything */
786 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
787 fd->path, fd->parent ? parent->path : "-");
789 work = parent->sidecar_files;
792 FileData *sfd = work->data;
797 g_list_free(parent->sidecar_files);
798 parent->sidecar_files = NULL;
800 file_data_free(parent);
803 #ifdef DEBUG_FILEDATA
804 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
806 void file_data_unref(FileData *fd)
809 if (fd == NULL) return;
810 if (fd->magick != FD_MAGICK)
811 #ifdef DEBUG_FILEDATA
812 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, (void *)fd);
814 log_printf("Error: fd magick mismatch fd=%p", fd);
816 g_assert(fd->magick == FD_MAGICK);
819 #ifdef DEBUG_FILEDATA
820 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->locked, fd->path,
823 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
826 // Free FileData if it's no longer ref'd
827 file_data_consider_free(fd);
831 * @brief Lock the FileData in memory.
833 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
834 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
835 * even if the code would continue to function properly even if the FileData were freed. Code that
836 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
838 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
839 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
841 void file_data_lock(FileData *fd)
843 if (fd == NULL) return;
844 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
846 g_assert(fd->magick == FD_MAGICK);
849 DEBUG_2("file_data_ref fd=%p (%d): '%s'", (void *)fd, fd->ref, fd->path);
853 * @brief Reset the maintain-FileData-in-memory lock
855 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
856 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
857 * keeping it from being freed.
859 void file_data_unlock(FileData *fd)
861 if (fd == NULL) return;
862 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
864 g_assert(fd->magick == FD_MAGICK);
867 // Free FileData if it's no longer ref'd
868 file_data_consider_free(fd);
872 * @brief Lock all of the FileDatas in the provided list
874 * @see file_data_lock(#FileData)
876 void file_data_lock_list(GList *list)
883 FileData *fd = work->data;
890 * @brief Unlock all of the FileDatas in the provided list
892 * @see #file_data_unlock(#FileData)
894 void file_data_unlock_list(GList *list)
901 FileData *fd = work->data;
903 file_data_unlock(fd);
908 *-----------------------------------------------------------------------------
909 * sidecar file info struct
910 *-----------------------------------------------------------------------------
913 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
915 const FileData *fda = a;
916 const FileData *fdb = b;
918 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
919 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
921 return strcmp(fdb->extension, fda->extension);
925 static gint sidecar_file_priority(const gchar *extension)
930 if (extension == NULL)
933 work = sidecar_ext_get_list();
936 gchar *ext = work->data;
939 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
945 static void file_data_check_sidecars(const GList *basename_list)
947 /* basename_list contains the new group - first is the parent, then sorted sidecars */
948 /* all files in the list have ref count > 0 */
951 GList *s_work, *new_sidecars;
954 if (!basename_list) return;
957 DEBUG_2("basename start");
958 work = basename_list;
961 FileData *fd = work->data;
963 g_assert(fd->magick == FD_MAGICK);
964 DEBUG_2("basename: %p %s", (void *)fd, fd->name);
967 g_assert(fd->parent->magick == FD_MAGICK);
968 DEBUG_2(" parent: %p", (void *)fd->parent);
970 s_work = fd->sidecar_files;
973 FileData *sfd = s_work->data;
974 s_work = s_work->next;
975 g_assert(sfd->magick == FD_MAGICK);
976 DEBUG_2(" sidecar: %p %s", (void *)sfd, sfd->name);
979 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
982 parent_fd = basename_list->data;
984 /* check if the second and next entries of basename_list are already connected
985 as sidecars of the first entry (parent_fd) */
986 work = basename_list->next;
987 s_work = parent_fd->sidecar_files;
989 while (work && s_work)
991 if (work->data != s_work->data) break;
993 s_work = s_work->next;
996 if (!work && !s_work)
998 DEBUG_2("basename no change");
999 return; /* no change in grouping */
1002 /* we have to regroup it */
1004 /* first, disconnect everything and send notification*/
1006 work = basename_list;
1009 FileData *fd = work->data;
1011 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
1015 FileData *old_parent = fd->parent;
1016 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
1017 file_data_ref(old_parent);
1018 file_data_disconnect_sidecar_file(old_parent, fd);
1019 file_data_send_notification(old_parent, NOTIFY_REREAD);
1020 file_data_unref(old_parent);
1023 while (fd->sidecar_files)
1025 FileData *sfd = fd->sidecar_files->data;
1026 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
1028 file_data_disconnect_sidecar_file(fd, sfd);
1029 file_data_send_notification(sfd, NOTIFY_REREAD);
1030 file_data_unref(sfd);
1032 file_data_send_notification(fd, NOTIFY_GROUPING);
1034 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
1037 /* now we can form the new group */
1038 work = basename_list->next;
1039 new_sidecars = NULL;
1042 FileData *sfd = work->data;
1043 g_assert(sfd->magick == FD_MAGICK);
1044 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
1045 sfd->parent = parent_fd;
1046 new_sidecars = g_list_prepend(new_sidecars, sfd);
1049 g_assert(parent_fd->sidecar_files == NULL);
1050 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
1051 DEBUG_1("basename group changed for %s", parent_fd->path);
1055 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
1057 g_assert(target->magick == FD_MAGICK);
1058 g_assert(sfd->magick == FD_MAGICK);
1059 g_assert(g_list_find(target->sidecar_files, sfd));
1061 file_data_ref(target);
1064 g_assert(sfd->parent == target);
1066 file_data_increment_version(sfd); /* increments both sfd and target */
1068 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
1070 g_free(sfd->extended_extension);
1071 sfd->extended_extension = NULL;
1073 file_data_unref(target);
1074 file_data_unref(sfd);
1077 /* disables / enables grouping for particular file, sends UPDATE notification */
1078 void file_data_disable_grouping(FileData *fd, gboolean disable)
1080 if (!fd->disable_grouping == !disable) return;
1082 fd->disable_grouping = !!disable;
1088 FileData *parent = file_data_ref(fd->parent);
1089 file_data_disconnect_sidecar_file(parent, fd);
1090 file_data_send_notification(parent, NOTIFY_GROUPING);
1091 file_data_unref(parent);
1093 else if (fd->sidecar_files)
1095 GList *sidecar_files = filelist_copy(fd->sidecar_files);
1096 GList *work = sidecar_files;
1099 FileData *sfd = work->data;
1101 file_data_disconnect_sidecar_file(fd, sfd);
1102 file_data_send_notification(sfd, NOTIFY_GROUPING);
1104 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
1105 filelist_free(sidecar_files);
1109 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
1114 file_data_increment_version(fd);
1115 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
1117 file_data_send_notification(fd, NOTIFY_GROUPING);
1120 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
1127 FileData *fd = work->data;
1129 file_data_disable_grouping(fd, disable);
1137 *-----------------------------------------------------------------------------
1139 *-----------------------------------------------------------------------------
1143 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
1146 if (!filelist_sort_ascend)
1153 switch (filelist_sort_method)
1158 if (fa->size < fb->size) return -1;
1159 if (fa->size > fb->size) return 1;
1160 /* fall back to name */
1163 if (fa->date < fb->date) return -1;
1164 if (fa->date > fb->date) return 1;
1165 /* fall back to name */
1168 if (fa->cdate < fb->cdate) return -1;
1169 if (fa->cdate > fb->cdate) return 1;
1170 /* fall back to name */
1173 if (fa->exifdate < fb->exifdate) return -1;
1174 if (fa->exifdate > fb->exifdate) return 1;
1175 /* fall back to name */
1177 case SORT_EXIFTIMEDIGITIZED:
1178 if (fa->exifdate_digitized < fb->exifdate_digitized) return -1;
1179 if (fa->exifdate_digitized > fb->exifdate_digitized) return 1;
1180 /* fall back to name */
1183 if (fa->rating < fb->rating) return -1;
1184 if (fa->rating > fb->rating) return 1;
1185 /* fall back to name */
1188 if (fa->format_class < fb->format_class) return -1;
1189 if (fa->format_class > fb->format_class) return 1;
1190 /* fall back to name */
1196 if (options->file_sort.case_sensitive)
1197 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1199 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1201 if (ret != 0) return ret;
1203 /* do not return 0 unless the files are really the same
1204 file_data_pool ensures that original_path is unique
1206 return strcmp(fa->original_path, fb->original_path);
1209 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1211 filelist_sort_method = method;
1212 filelist_sort_ascend = ascend;
1213 return filelist_sort_compare_filedata(fa, fb);
1216 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1218 return filelist_sort_compare_filedata(a, b);
1221 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1223 filelist_sort_method = method;
1224 filelist_sort_ascend = ascend;
1225 return g_list_sort(list, cb);
1228 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1230 filelist_sort_method = method;
1231 filelist_sort_ascend = ascend;
1232 return g_list_insert_sorted(list, data, cb);
1235 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1237 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1240 //GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
1242 //return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1246 *-----------------------------------------------------------------------------
1247 * basename hash - grouping of sidecars in filelist
1248 *-----------------------------------------------------------------------------
1252 static GHashTable *file_data_basename_hash_new(void)
1254 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1257 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1260 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1262 list = g_hash_table_lookup(basename_hash, basename);
1266 DEBUG_1("TG: basename_hash not found for %s",fd->path);
1267 const gchar *parent_extension = registered_extension_from_path(basename);
1269 if (parent_extension)
1271 DEBUG_1("TG: parent extension %s",parent_extension);
1272 gchar *parent_basename = g_strndup(basename, parent_extension - basename);
1273 DEBUG_1("TG: parent basename %s",parent_basename);
1274 FileData *parent_fd = g_hash_table_lookup(file_data_pool, basename);
1277 DEBUG_1("TG: parent fd found");
1278 list = g_hash_table_lookup(basename_hash, parent_basename);
1279 if (!g_list_find(list, parent_fd))
1281 DEBUG_1("TG: parent fd doesn't fit");
1282 g_free(parent_basename);
1288 basename = parent_basename;
1289 fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
1295 if (!g_list_find(list, fd))
1297 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1298 g_hash_table_insert(basename_hash, basename, list);
1307 static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
1309 file_data_basename_hash_insert((GHashTable *)basename_hash, (FileData *)fd);
1312 static void file_data_basename_hash_remove_list(gpointer UNUSED(key), gpointer value, gpointer UNUSED(data))
1314 filelist_free((GList *)value);
1317 static void file_data_basename_hash_free(GHashTable *basename_hash)
1319 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1320 g_hash_table_destroy(basename_hash);
1324 *-----------------------------------------------------------------------------
1325 * handling sidecars in filelist
1326 *-----------------------------------------------------------------------------
1329 static GList *filelist_filter_out_sidecars(GList *flist)
1331 GList *work = flist;
1332 GList *flist_filtered = NULL;
1336 FileData *fd = work->data;
1339 if (fd->parent) /* remove fd's that are children */
1340 file_data_unref(fd);
1342 flist_filtered = g_list_prepend(flist_filtered, fd);
1346 return flist_filtered;
1349 static void file_data_basename_hash_to_sidecars(gpointer UNUSED(key), gpointer value, gpointer UNUSED(data))
1351 GList *basename_list = (GList *)value;
1352 file_data_check_sidecars(basename_list);
1356 static gboolean is_hidden_file(const gchar *name)
1358 if (name[0] != '.') return FALSE;
1359 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1364 *-----------------------------------------------------------------------------
1365 * the main filelist function
1366 *-----------------------------------------------------------------------------
1369 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1374 GList *dlist = NULL;
1375 GList *flist = NULL;
1376 GList *xmp_files = NULL;
1377 gint (*stat_func)(const gchar *path, struct stat *buf);
1378 GHashTable *basename_hash = NULL;
1380 g_assert(files || dirs);
1382 if (files) *files = NULL;
1383 if (dirs) *dirs = NULL;
1385 pathl = path_from_utf8(dir_path);
1386 if (!pathl) return FALSE;
1388 dp = opendir(pathl);
1395 if (files) basename_hash = file_data_basename_hash_new();
1397 if (follow_symlinks)
1402 while ((dir = readdir(dp)) != NULL)
1404 struct stat ent_sbuf;
1405 const gchar *name = dir->d_name;
1408 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1411 filepath = g_build_filename(pathl, name, NULL);
1412 if (stat_func(filepath, &ent_sbuf) >= 0)
1414 if (S_ISDIR(ent_sbuf.st_mode))
1416 /* we ignore the .thumbnails dir for cleanliness */
1418 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1419 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1420 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1421 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1423 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1428 if (files && filter_name_exists(name))
1430 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1431 flist = g_list_prepend(flist, fd);
1432 if (fd->sidecar_priority && !fd->disable_grouping)
1434 if (strcmp(fd->extension, ".xmp") != 0)
1435 file_data_basename_hash_insert(basename_hash, fd);
1437 xmp_files = g_list_append(xmp_files, fd);
1444 if (errno == EOVERFLOW)
1446 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1458 g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
1459 g_list_free(xmp_files);
1462 if (dirs) *dirs = dlist;
1466 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1468 *files = filelist_filter_out_sidecars(flist);
1470 if (basename_hash) file_data_basename_hash_free(basename_hash);
1475 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1477 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1480 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1482 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1485 FileData *file_data_new_group(const gchar *path_utf8)
1492 if (!stat_utf8(path_utf8, &st))
1498 if (S_ISDIR(st.st_mode))
1499 return file_data_new(path_utf8, &st, TRUE);
1501 dir = remove_level_from_path(path_utf8);
1503 filelist_read_real(dir, &files, NULL, TRUE);
1505 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1506 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1512 filelist_free(files);
1518 void filelist_free(GList *list)
1525 file_data_unref((FileData *)work->data);
1533 GList *filelist_copy(GList *list)
1535 GList *new_list = NULL;
1546 new_list = g_list_prepend(new_list, file_data_ref(fd));
1549 return g_list_reverse(new_list);
1552 GList *filelist_from_path_list(GList *list)
1554 GList *new_list = NULL;
1565 new_list = g_list_prepend(new_list, file_data_new_group(path));
1568 return g_list_reverse(new_list);
1571 GList *filelist_to_path_list(GList *list)
1573 GList *new_list = NULL;
1584 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1587 return g_list_reverse(new_list);
1590 GList *filelist_filter(GList *list, gboolean is_dir_list)
1594 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1599 FileData *fd = (FileData *)(work->data);
1600 const gchar *name = fd->name;
1602 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1603 (!is_dir_list && !filter_name_exists(name)) ||
1604 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1605 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1609 list = g_list_remove_link(list, link);
1610 file_data_unref(fd);
1621 *-----------------------------------------------------------------------------
1622 * filelist recursive
1623 *-----------------------------------------------------------------------------
1626 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1628 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1631 GList *filelist_sort_path(GList *list)
1633 return g_list_sort(list, filelist_sort_path_cb);
1636 static void filelist_recursive_append(GList **list, GList *dirs)
1643 FileData *fd = (FileData *)(work->data);
1647 if (filelist_read(fd, &f, &d))
1649 f = filelist_filter(f, FALSE);
1650 f = filelist_sort_path(f);
1651 *list = g_list_concat(*list, f);
1653 d = filelist_filter(d, TRUE);
1654 d = filelist_sort_path(d);
1655 filelist_recursive_append(list, d);
1663 static void filelist_recursive_append_full(GList **list, GList *dirs, SortType method, gboolean ascend)
1670 FileData *fd = (FileData *)(work->data);
1674 if (filelist_read(fd, &f, &d))
1676 f = filelist_filter(f, FALSE);
1677 f = filelist_sort_full(f, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1678 *list = g_list_concat(*list, f);
1680 d = filelist_filter(d, TRUE);
1681 d = filelist_sort_path(d);
1682 filelist_recursive_append_full(list, d, method, ascend);
1690 GList *filelist_recursive(FileData *dir_fd)
1695 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1696 list = filelist_filter(list, FALSE);
1697 list = filelist_sort_path(list);
1699 d = filelist_filter(d, TRUE);
1700 d = filelist_sort_path(d);
1701 filelist_recursive_append(&list, d);
1707 GList *filelist_recursive_full(FileData *dir_fd, SortType method, gboolean ascend)
1712 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1713 list = filelist_filter(list, FALSE);
1714 list = filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1716 d = filelist_filter(d, TRUE);
1717 d = filelist_sort_path(d);
1718 filelist_recursive_append_full(&list, d, method, ascend);
1725 *-----------------------------------------------------------------------------
1726 * file modification support
1727 *-----------------------------------------------------------------------------
1731 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1733 if (!fdci && fd) fdci = fd->change;
1737 g_free(fdci->source);
1742 if (fd) fd->change = NULL;
1745 static gboolean file_data_can_write_directly(FileData *fd)
1747 return filter_name_is_writable(fd->extension);
1750 static gboolean file_data_can_write_sidecar(FileData *fd)
1752 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1755 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1757 gchar *sidecar_path = NULL;
1760 if (!file_data_can_write_sidecar(fd)) return NULL;
1762 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1763 gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1766 FileData *sfd = work->data;
1768 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1770 sidecar_path = g_strdup(sfd->path);
1774 g_free(extended_extension);
1776 if (!existing_only && !sidecar_path)
1778 if (options->metadata.sidecar_extended_name)
1779 sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1782 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1783 sidecar_path = g_strconcat(base, ".xmp", NULL);
1788 return sidecar_path;
1792 * marks and orientation
1795 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1796 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1797 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1798 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1800 gboolean file_data_get_mark(FileData *fd, gint n)
1802 gboolean valid = (fd->valid_marks & (1 << n));
1804 if (file_data_get_mark_func[n] && !valid)
1806 guint old = fd->marks;
1807 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1809 if (!value != !(fd->marks & (1 << n)))
1811 fd->marks = fd->marks ^ (1 << n);
1814 fd->valid_marks |= (1 << n);
1815 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1817 file_data_unref(fd);
1819 else if (!old && fd->marks)
1825 return !!(fd->marks & (1 << n));
1828 guint file_data_get_marks(FileData *fd)
1831 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1835 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1838 if (!value == !file_data_get_mark(fd, n)) return;
1840 if (file_data_set_mark_func[n])
1842 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1847 fd->marks = fd->marks ^ (1 << n);
1849 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1851 file_data_unref(fd);
1853 else if (!old && fd->marks)
1858 file_data_increment_version(fd);
1859 file_data_send_notification(fd, NOTIFY_MARKS);
1862 gboolean file_data_filter_marks(FileData *fd, guint filter)
1865 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1866 return ((fd->marks & filter) == filter);
1869 GList *file_data_filter_marks_list(GList *list, guint filter)
1876 FileData *fd = work->data;
1880 if (!file_data_filter_marks(fd, filter))
1882 list = g_list_remove_link(list, link);
1883 file_data_unref(fd);
1891 gboolean file_data_filter_file_filter(FileData *fd, GRegex *filter)
1893 return g_regex_match(filter, fd->name, 0, NULL);
1896 GList *file_data_filter_file_filter_list(GList *list, GRegex *filter)
1903 FileData *fd = work->data;
1907 if (!file_data_filter_file_filter(fd, filter))
1909 list = g_list_remove_link(list, link);
1910 file_data_unref(fd);
1918 static gboolean file_data_filter_class(FileData *fd, guint filter)
1922 for (i = 0; i < FILE_FORMAT_CLASSES; i++)
1924 if (filter & (1 << i))
1926 if ((FileFormatClass)i == filter_file_get_class(fd->path))
1936 GList *file_data_filter_class_list(GList *list, guint filter)
1943 FileData *fd = work->data;
1947 if (!file_data_filter_class(fd, filter))
1949 list = g_list_remove_link(list, link);
1950 file_data_unref(fd);
1958 static void file_data_notify_mark_func(gpointer UNUSED(key), gpointer value, gpointer UNUSED(user_data))
1960 FileData *fd = value;
1961 file_data_increment_version(fd);
1962 file_data_send_notification(fd, NOTIFY_MARKS);
1965 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1967 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1969 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1971 file_data_get_mark_func[n] = get_mark_func;
1972 file_data_set_mark_func[n] = set_mark_func;
1973 file_data_mark_func_data[n] = data;
1974 file_data_destroy_mark_func[n] = notify;
1976 if (get_mark_func && file_data_pool)
1978 /* this effectively changes all known files */
1979 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1985 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1987 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1988 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1989 if (data) *data = file_data_mark_func_data[n];
1992 //gint file_data_get_user_orientation(FileData *fd)
1994 //return fd->user_orientation;
1997 //void file_data_set_user_orientation(FileData *fd, gint value)
1999 //if (fd->user_orientation == value) return;
2001 //fd->user_orientation = value;
2002 //file_data_increment_version(fd);
2003 //file_data_send_notification(fd, NOTIFY_ORIENTATION);
2008 * file_data - operates on the given fd
2009 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
2013 /* return list of sidecar file extensions in a string */
2014 gchar *file_data_sc_list_to_string(FileData *fd)
2017 GString *result = g_string_new("");
2019 work = fd->sidecar_files;
2022 FileData *sfd = work->data;
2024 result = g_string_append(result, "+ ");
2025 result = g_string_append(result, sfd->extension);
2027 if (work) result = g_string_append_c(result, ' ');
2030 return g_string_free(result, FALSE);
2036 * add FileDataChangeInfo (see typedefs.h) for the given operation
2037 * uses file_data_add_change_info
2039 * fails if the fd->change already exists - change operations can't run in parallel
2040 * fd->change_info works as a lock
2042 * dest can be NULL - in this case the current name is used for now, it will
2047 FileDataChangeInfo types:
2049 MOVE - path is changed, name may be changed too
2050 RENAME - path remains unchanged, name is changed
2051 extension should remain (FIXME should we allow editing extension? it will make problems with grouping)
2052 sidecar names are changed too, extensions are not changed
2054 UPDATE - file size, date or grouping has been changed
2057 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
2059 FileDataChangeInfo *fdci;
2061 if (fd->change) return FALSE;
2063 fdci = g_new0(FileDataChangeInfo, 1);
2068 fdci->source = g_strdup(src);
2070 fdci->source = g_strdup(fd->path);
2073 fdci->dest = g_strdup(dest);
2080 static void file_data_planned_change_remove(FileData *fd)
2082 if (file_data_planned_change_hash &&
2083 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
2085 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
2087 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
2088 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
2089 file_data_unref(fd);
2090 if (g_hash_table_size(file_data_planned_change_hash) == 0)
2092 g_hash_table_destroy(file_data_planned_change_hash);
2093 file_data_planned_change_hash = NULL;
2094 DEBUG_1("planned change: empty");
2101 void file_data_free_ci(FileData *fd)
2103 FileDataChangeInfo *fdci = fd->change;
2107 file_data_planned_change_remove(fd);
2109 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
2111 g_free(fdci->source);
2119 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
2121 FileDataChangeInfo *fdci = fd->change;
2123 fdci->regroup_when_finished = enable;
2126 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
2130 if (fd->parent) fd = fd->parent;
2132 if (fd->change) return FALSE;
2134 work = fd->sidecar_files;
2137 FileData *sfd = work->data;
2139 if (sfd->change) return FALSE;
2143 file_data_add_ci(fd, type, NULL, NULL);
2145 work = fd->sidecar_files;
2148 FileData *sfd = work->data;
2150 file_data_add_ci(sfd, type, NULL, NULL);
2157 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
2161 if (fd->parent) fd = fd->parent;
2163 if (!fd->change || fd->change->type != type) return FALSE;
2165 work = fd->sidecar_files;
2168 FileData *sfd = work->data;
2170 if (!sfd->change || sfd->change->type != type) return FALSE;
2178 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
2180 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
2181 file_data_sc_update_ci_copy(fd, dest_path);
2185 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
2187 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
2188 file_data_sc_update_ci_move(fd, dest_path);
2192 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
2194 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
2195 file_data_sc_update_ci_rename(fd, dest_path);
2199 gboolean file_data_sc_add_ci_delete(FileData *fd)
2201 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
2204 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
2206 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
2207 file_data_sc_update_ci_unspecified(fd, dest_path);
2211 gboolean file_data_add_ci_write_metadata(FileData *fd)
2213 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
2216 void file_data_sc_free_ci(FileData *fd)
2220 if (fd->parent) fd = fd->parent;
2222 file_data_free_ci(fd);
2224 work = fd->sidecar_files;
2227 FileData *sfd = work->data;
2229 file_data_free_ci(sfd);
2234 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2237 gboolean ret = TRUE;
2242 FileData *fd = work->data;
2244 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2251 static void file_data_sc_revert_ci_list(GList *fd_list)
2258 FileData *fd = work->data;
2260 file_data_sc_free_ci(fd);
2265 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2272 FileData *fd = work->data;
2274 if (!func(fd, dest))
2276 file_data_sc_revert_ci_list(work->prev);
2285 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2287 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2290 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2292 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2295 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2297 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2300 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2302 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2305 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2308 gboolean ret = TRUE;
2313 FileData *fd = work->data;
2315 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2322 void file_data_free_ci_list(GList *fd_list)
2329 FileData *fd = work->data;
2331 file_data_free_ci(fd);
2336 void file_data_sc_free_ci_list(GList *fd_list)
2343 FileData *fd = work->data;
2345 file_data_sc_free_ci(fd);
2351 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2352 * fails if fd->change does not exist or the change type does not match
2355 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2357 FileDataChangeType type = fd->change->type;
2359 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2363 if (!file_data_planned_change_hash)
2364 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2366 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2368 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2369 g_hash_table_remove(file_data_planned_change_hash, old_path);
2370 file_data_unref(fd);
2373 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
2378 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2379 g_hash_table_remove(file_data_planned_change_hash, new_path);
2380 file_data_unref(ofd);
2383 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2385 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2390 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2392 gchar *old_path = fd->change->dest;
2394 fd->change->dest = g_strdup(dest_path);
2395 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2399 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2401 const gchar *extension = registered_extension_from_path(fd->change->source);
2402 gchar *base = remove_extension_from_path(dest_path);
2403 gchar *old_path = fd->change->dest;
2405 fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2406 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2412 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2415 gchar *dest_path_full = NULL;
2417 if (fd->parent) fd = fd->parent;
2421 dest_path = fd->path;
2423 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2425 gchar *dir = remove_level_from_path(fd->path);
2427 dest_path_full = g_build_filename(dir, dest_path, NULL);
2429 dest_path = dest_path_full;
2431 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2433 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2434 dest_path = dest_path_full;
2437 file_data_update_ci_dest(fd, dest_path);
2439 work = fd->sidecar_files;
2442 FileData *sfd = work->data;
2444 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2448 g_free(dest_path_full);
2451 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2453 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2454 file_data_sc_update_ci(fd, dest_path);
2458 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2460 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2463 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2465 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2468 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2470 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2473 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2475 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2478 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2480 gboolean (*func)(FileData *, const gchar *))
2483 gboolean ret = TRUE;
2488 FileData *fd = work->data;
2490 if (!func(fd, dest)) ret = FALSE;
2497 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2499 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2502 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2504 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2507 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2509 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2514 * verify source and dest paths - dest image exists, etc.
2515 * it should detect all possible problems with the planned operation
2518 gint file_data_verify_ci(FileData *fd, GList *list)
2520 gint ret = CHANGE_OK;
2523 FileData *fd1 = NULL;
2527 DEBUG_1("Change checked: no change info: %s", fd->path);
2531 if (!isname(fd->path))
2533 /* this probably should not happen */
2534 ret |= CHANGE_NO_SRC;
2535 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2539 dir = remove_level_from_path(fd->path);
2541 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2542 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2543 fd->change->type != FILEDATA_CHANGE_RENAME &&
2544 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2547 ret |= CHANGE_WARN_UNSAVED_META;
2548 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2551 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2552 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2553 !access_file(fd->path, R_OK))
2555 ret |= CHANGE_NO_READ_PERM;
2556 DEBUG_1("Change checked: no read permission: %s", fd->path);
2558 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2559 !access_file(dir, W_OK))
2561 ret |= CHANGE_NO_WRITE_PERM_DIR;
2562 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2564 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2565 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2566 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2567 !access_file(fd->path, W_OK))
2569 ret |= CHANGE_WARN_NO_WRITE_PERM;
2570 DEBUG_1("Change checked: no write permission: %s", fd->path);
2572 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2573 - that means that there are no hard errors and warnings can be disabled
2574 - the destination is determined during the check
2576 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2578 /* determine destination file */
2579 gboolean have_dest = FALSE;
2580 gchar *dest_dir = NULL;
2582 if (options->metadata.save_in_image_file)
2584 if (file_data_can_write_directly(fd))
2586 /* we can write the file directly */
2587 if (access_file(fd->path, W_OK))
2593 if (options->metadata.warn_on_write_problems)
2595 ret |= CHANGE_WARN_NO_WRITE_PERM;
2596 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2600 else if (file_data_can_write_sidecar(fd))
2602 /* we can write sidecar */
2603 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2604 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2606 file_data_update_ci_dest(fd, sidecar);
2611 if (options->metadata.warn_on_write_problems)
2613 ret |= CHANGE_WARN_NO_WRITE_PERM;
2614 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2623 /* write private metadata file under ~/.geeqie */
2625 /* If an existing metadata file exists, we will try writing to
2626 * it's location regardless of the user's preference.
2628 gchar *metadata_path = NULL;
2630 /* but ignore XMP if we are not able to write it */
2631 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2633 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2635 if (metadata_path && !access_file(metadata_path, W_OK))
2637 g_free(metadata_path);
2638 metadata_path = NULL;
2645 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2646 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2648 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2650 metadata_path = g_build_filename(dest_dir, filename, NULL);
2654 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2656 file_data_update_ci_dest(fd, metadata_path);
2661 ret |= CHANGE_NO_WRITE_PERM_DEST;
2662 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2664 g_free(metadata_path);
2669 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2674 same = (strcmp(fd->path, fd->change->dest) == 0);
2678 const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2679 if (!dest_ext) dest_ext = "";
2680 if (!options->file_filter.disable_file_extension_checks)
2682 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2684 ret |= CHANGE_WARN_CHANGED_EXT;
2685 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2691 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /** @FIXME this is now needed for running editors */
2693 ret |= CHANGE_WARN_SAME;
2694 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2698 dest_dir = remove_level_from_path(fd->change->dest);
2700 if (!isdir(dest_dir))
2702 ret |= CHANGE_NO_DEST_DIR;
2703 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2705 else if (!access_file(dest_dir, W_OK))
2707 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2708 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2712 if (isfile(fd->change->dest))
2714 if (!access_file(fd->change->dest, W_OK))
2716 ret |= CHANGE_NO_WRITE_PERM_DEST;
2717 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2721 ret |= CHANGE_WARN_DEST_EXISTS;
2722 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2725 else if (isdir(fd->change->dest))
2727 ret |= CHANGE_DEST_EXISTS;
2728 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2735 /* During a rename operation, check if another planned destination file has
2738 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2739 fd->change->type == FILEDATA_CHANGE_COPY ||
2740 fd->change->type == FILEDATA_CHANGE_MOVE)
2747 if (fd1 != NULL && fd != fd1 )
2749 if (!strcmp(fd->change->dest, fd1->change->dest))
2751 ret |= CHANGE_DUPLICATE_DEST;
2757 fd->change->error = ret;
2758 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2765 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2770 ret = file_data_verify_ci(fd, list);
2772 work = fd->sidecar_files;
2775 FileData *sfd = work->data;
2777 ret |= file_data_verify_ci(sfd, list);
2784 gchar *file_data_get_error_string(gint error)
2786 GString *result = g_string_new("");
2788 if (error & CHANGE_NO_SRC)
2790 if (result->len > 0) g_string_append(result, ", ");
2791 g_string_append(result, _("file or directory does not exist"));
2794 if (error & CHANGE_DEST_EXISTS)
2796 if (result->len > 0) g_string_append(result, ", ");
2797 g_string_append(result, _("destination already exists"));
2800 if (error & CHANGE_NO_WRITE_PERM_DEST)
2802 if (result->len > 0) g_string_append(result, ", ");
2803 g_string_append(result, _("destination can't be overwritten"));
2806 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2808 if (result->len > 0) g_string_append(result, ", ");
2809 g_string_append(result, _("destination directory is not writable"));
2812 if (error & CHANGE_NO_DEST_DIR)
2814 if (result->len > 0) g_string_append(result, ", ");
2815 g_string_append(result, _("destination directory does not exist"));
2818 if (error & CHANGE_NO_WRITE_PERM_DIR)
2820 if (result->len > 0) g_string_append(result, ", ");
2821 g_string_append(result, _("source directory is not writable"));
2824 if (error & CHANGE_NO_READ_PERM)
2826 if (result->len > 0) g_string_append(result, ", ");
2827 g_string_append(result, _("no read permission"));
2830 if (error & CHANGE_WARN_NO_WRITE_PERM)
2832 if (result->len > 0) g_string_append(result, ", ");
2833 g_string_append(result, _("file is readonly"));
2836 if (error & CHANGE_WARN_DEST_EXISTS)
2838 if (result->len > 0) g_string_append(result, ", ");
2839 g_string_append(result, _("destination already exists and will be overwritten"));
2842 if (error & CHANGE_WARN_SAME)
2844 if (result->len > 0) g_string_append(result, ", ");
2845 g_string_append(result, _("source and destination are the same"));
2848 if (error & CHANGE_WARN_CHANGED_EXT)
2850 if (result->len > 0) g_string_append(result, ", ");
2851 g_string_append(result, _("source and destination have different extension"));
2854 if (error & CHANGE_WARN_UNSAVED_META)
2856 if (result->len > 0) g_string_append(result, ", ");
2857 g_string_append(result, _("there are unsaved metadata changes for the file"));
2860 if (error & CHANGE_DUPLICATE_DEST)
2862 if (result->len > 0) g_string_append(result, ", ");
2863 g_string_append(result, _("another destination file has the same filename"));
2866 return g_string_free(result, FALSE);
2869 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2872 gint all_errors = 0;
2873 gint common_errors = ~0;
2878 if (!list) return 0;
2880 num = g_list_length(list);
2881 errors = g_new(int, num);
2892 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2893 all_errors |= error;
2894 common_errors &= error;
2901 if (desc && all_errors)
2904 GString *result = g_string_new("");
2908 gchar *str = file_data_get_error_string(common_errors);
2909 g_string_append(result, str);
2910 g_string_append(result, "\n");
2924 error = errors[i] & ~common_errors;
2928 gchar *str = file_data_get_error_string(error);
2929 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2934 *desc = g_string_free(result, FALSE);
2943 * perform the change described by FileFataChangeInfo
2944 * it is used for internal operations,
2945 * this function actually operates with files on the filesystem
2946 * it should implement safe delete
2949 static gboolean file_data_perform_move(FileData *fd)
2951 g_assert(!strcmp(fd->change->source, fd->path));
2952 return move_file(fd->change->source, fd->change->dest);
2955 static gboolean file_data_perform_copy(FileData *fd)
2957 g_assert(!strcmp(fd->change->source, fd->path));
2958 return copy_file(fd->change->source, fd->change->dest);
2961 static gboolean file_data_perform_delete(FileData *fd)
2963 if (isdir(fd->path) && !islink(fd->path))
2964 return rmdir_utf8(fd->path);
2966 if (options->file_ops.safe_delete_enable)
2967 return file_util_safe_unlink(fd->path);
2969 return unlink_file(fd->path);
2972 gboolean file_data_perform_ci(FileData *fd)
2974 FileDataChangeType type = fd->change->type;
2978 case FILEDATA_CHANGE_MOVE:
2979 return file_data_perform_move(fd);
2980 case FILEDATA_CHANGE_COPY:
2981 return file_data_perform_copy(fd);
2982 case FILEDATA_CHANGE_RENAME:
2983 return file_data_perform_move(fd); /* the same as move */
2984 case FILEDATA_CHANGE_DELETE:
2985 return file_data_perform_delete(fd);
2986 case FILEDATA_CHANGE_WRITE_METADATA:
2987 return metadata_write_perform(fd);
2988 case FILEDATA_CHANGE_UNSPECIFIED:
2989 /* nothing to do here */
2997 gboolean file_data_sc_perform_ci(FileData *fd)
3000 gboolean ret = TRUE;
3001 FileDataChangeType type = fd->change->type;
3003 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3005 work = fd->sidecar_files;
3008 FileData *sfd = work->data;
3010 if (!file_data_perform_ci(sfd)) ret = FALSE;
3014 if (!file_data_perform_ci(fd)) ret = FALSE;
3020 * updates FileData structure according to FileDataChangeInfo
3023 gboolean file_data_apply_ci(FileData *fd)
3025 FileDataChangeType type = fd->change->type;
3027 /** @FIXME delete ?*/
3028 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
3030 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
3031 file_data_planned_change_remove(fd);
3033 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
3035 /* this change overwrites another file which is already known to other modules
3036 renaming fd would create duplicate FileData structure
3037 the best thing we can do is nothing
3039 /** @FIXME maybe we could copy stuff like marks
3041 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
3045 file_data_set_path(fd, fd->change->dest);
3048 file_data_increment_version(fd);
3049 file_data_send_notification(fd, NOTIFY_CHANGE);
3054 gboolean file_data_sc_apply_ci(FileData *fd)
3057 FileDataChangeType type = fd->change->type;
3059 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3061 work = fd->sidecar_files;
3064 FileData *sfd = work->data;
3066 file_data_apply_ci(sfd);
3070 file_data_apply_ci(fd);
3075 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
3078 if (fd->parent) fd = fd->parent;
3079 if (!g_list_find(list, fd)) return FALSE;
3081 work = fd->sidecar_files;
3084 if (!g_list_find(list, work->data)) return FALSE;
3090 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
3095 /* change partial groups to independent files */
3100 FileData *fd = work->data;
3103 if (!file_data_list_contains_whole_group(list, fd))
3105 file_data_disable_grouping(fd, TRUE);
3108 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
3114 /* remove sidecars from the list,
3115 they can be still accessed via main_fd->sidecar_files */
3119 FileData *fd = work->data;
3123 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
3125 out = g_list_prepend(out, file_data_ref(fd));
3129 filelist_free(list);
3130 out = g_list_reverse(out);
3140 * notify other modules about the change described by FileDataChangeInfo
3143 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks */
3144 /** @FIXME do we need the ignore_list? It looks like a workaround for ineffective
3145 implementation in view-file-list.cc */
3148 typedef struct _NotifyIdleData NotifyIdleData;
3150 struct _NotifyIdleData {
3156 typedef struct _NotifyData NotifyData;
3158 struct _NotifyData {
3159 FileDataNotifyFunc func;
3161 NotifyPriority priority;
3164 static GList *notify_func_list = NULL;
3166 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
3168 NotifyData *nda = (NotifyData *)a;
3169 NotifyData *ndb = (NotifyData *)b;
3171 if (nda->priority < ndb->priority) return -1;
3172 if (nda->priority > ndb->priority) return 1;
3176 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
3179 GList *work = notify_func_list;
3183 NotifyData *nd = (NotifyData *)work->data;
3185 if (nd->func == func && nd->data == data)
3187 g_warning("Notify func already registered");
3193 nd = g_new(NotifyData, 1);
3196 nd->priority = priority;
3198 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
3199 DEBUG_2("Notify func registered: %p", (void *)nd);
3204 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
3206 GList *work = notify_func_list;
3210 NotifyData *nd = (NotifyData *)work->data;
3212 if (nd->func == func && nd->data == data)
3214 notify_func_list = g_list_delete_link(notify_func_list, work);
3216 DEBUG_2("Notify func unregistered: %p", (void *)nd);
3222 g_warning("Notify func not found");
3227 //gboolean file_data_send_notification_idle_cb(gpointer data)
3229 //NotifyIdleData *nid = (NotifyIdleData *)data;
3230 //GList *work = notify_func_list;
3234 //NotifyData *nd = (NotifyData *)work->data;
3236 //nd->func(nid->fd, nid->type, nd->data);
3237 //work = work->next;
3239 //file_data_unref(nid->fd);
3244 void file_data_send_notification(FileData *fd, NotifyType type)
3246 GList *work = notify_func_list;
3250 NotifyData *nd = (NotifyData *)work->data;
3252 nd->func(fd, type, nd->data);
3256 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3257 nid->fd = file_data_ref(fd);
3259 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3263 static GHashTable *file_data_monitor_pool = NULL;
3264 static guint realtime_monitor_id = 0; /* event source id */
3266 static void realtime_monitor_check_cb(gpointer key, gpointer UNUSED(value), gpointer UNUSED(data))
3270 file_data_check_changed_files(fd);
3272 DEBUG_1("monitor %s", fd->path);
3275 static gboolean realtime_monitor_cb(gpointer UNUSED(data))
3277 if (!options->update_on_time_change) return TRUE;
3278 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
3282 gboolean file_data_register_real_time_monitor(FileData *fd)
3288 if (!file_data_monitor_pool)
3289 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3291 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3293 DEBUG_1("Register realtime %d %s", count, fd->path);
3296 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3298 if (!realtime_monitor_id)
3300 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
3306 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3310 g_assert(file_data_monitor_pool);
3312 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3314 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3316 g_assert(count > 0);
3321 g_hash_table_remove(file_data_monitor_pool, fd);
3323 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3325 file_data_unref(fd);
3327 if (g_hash_table_size(file_data_monitor_pool) == 0)
3329 g_source_remove(realtime_monitor_id);
3330 realtime_monitor_id = 0;
3338 *-----------------------------------------------------------------------------
3339 * Saving marks list, clearing marks
3340 * Uses file_data_pool
3341 *-----------------------------------------------------------------------------
3344 static void marks_get_files(gpointer key, gpointer value, gpointer userdata)
3346 gchar *file_name = key;
3347 GString *result = userdata;
3350 if (isfile(file_name))
3353 if (fd && fd->marks > 0)
3355 g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks);
3360 gboolean marks_list_load(const gchar *path)
3368 pathl = path_from_utf8(path);
3369 f = fopen(pathl, "r");
3371 if (!f) return FALSE;
3373 /* first line must start with Marks comment */
3374 if (!fgets(s_buf, sizeof(s_buf), f) ||
3375 strncmp(s_buf, "#Marks", 6) != 0)
3381 while (fgets(s_buf, sizeof(s_buf), f))
3383 if (s_buf[0]=='#') continue;
3384 file_path = strtok(s_buf, ",");
3385 marks_value = strtok(NULL, ",");
3386 if (isfile(file_path))
3388 FileData *fd = file_data_new_no_grouping(file_path);
3393 gint mark_no = 1 << n;
3394 if (atoi(marks_value) & mark_no)
3396 file_data_set_mark(fd, n , 1);
3407 gboolean marks_list_save(gchar *path, gboolean save)
3409 SecureSaveInfo *ssi;
3411 GString *marks = g_string_new("");
3413 pathl = path_from_utf8(path);
3414 ssi = secure_open(pathl);
3418 log_printf(_("Error: Unable to write marks lists to: %s\n"), path);
3422 secure_fprintf(ssi, "#Marks lists\n");
3426 g_hash_table_foreach(file_data_pool, marks_get_files, marks);
3428 secure_fprintf(ssi, "%s", marks->str);
3429 g_string_free(marks, FALSE);
3431 secure_fprintf(ssi, "#end\n");
3432 return (secure_close(ssi) == 0);
3435 static void marks_clear(gpointer key, gpointer value, gpointer UNUSED(userdata))
3437 gchar *file_name = key;
3442 if (isfile(file_name))
3445 if (fd && fd->marks > 0)
3451 if (fd->marks & mark_no)
3453 file_data_set_mark(fd, n , 0);
3461 void marks_clear_all()
3463 g_hash_table_foreach(file_data_pool, marks_clear, NULL);
3466 void file_data_set_page_num(FileData *fd, gint page_num)
3468 if (fd->page_total > 1 && page_num < 0)
3470 fd->page_num = fd->page_total - 1;
3472 else if (fd->page_total > 1 && page_num <= fd->page_total)
3474 fd->page_num = page_num - 1;
3480 file_data_send_notification(fd, NOTIFY_REREAD);
3483 void file_data_inc_page_num(FileData *fd)
3485 if (fd->page_total > 0 && fd->page_num < fd->page_total - 1)
3487 fd->page_num = fd->page_num + 1;
3489 else if (fd->page_total == 0)
3491 fd->page_num = fd->page_num + 1;
3493 file_data_send_notification(fd, NOTIFY_REREAD);
3496 void file_data_dec_page_num(FileData *fd)
3498 if (fd->page_num > 0)
3500 fd->page_num = fd->page_num - 1;
3502 file_data_send_notification(fd, NOTIFY_REREAD);
3505 void file_data_set_page_total(FileData *fd, gint page_total)
3507 fd->page_total = page_total;
3510 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */