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 = static_cast<FileData *>(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);
247 sfd = static_cast<FileData *>(work->data);
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 = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
385 if (!fd && file_data_planned_change_hash)
387 fd = static_cast<FileData *>(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 = static_cast<FileData *>(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, "%4u:%2u:%2u %2u:%2u:%2u", &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, "%4u:%2u:%2u %2u:%2u:%2u", &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()
700 #ifdef DEBUG_FILEDATA
706 list = g_hash_table_get_values(file_data_pool);
708 log_printf("%d", global_file_data_count);
709 log_printf("%d", g_list_length(list));
713 fd = static_cast<FileData *>(list->data);
714 log_printf("%-4d %s", fd->ref, fd->path);
723 static void file_data_free(FileData *fd)
725 g_assert(fd->magick == FD_MAGICK);
726 g_assert(fd->ref == 0);
727 g_assert(!fd->locked);
729 #ifdef DEBUG_FILEDATA
730 global_file_data_count--;
731 DEBUG_2("file data count--: %d", global_file_data_count);
734 metadata_cache_free(fd);
735 g_hash_table_remove(file_data_pool, fd->original_path);
738 g_free(fd->original_path);
739 g_free(fd->collate_key_name);
740 g_free(fd->collate_key_name_nocase);
741 g_free(fd->extended_extension);
742 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
743 histmap_free(fd->histmap);
746 g_free(fd->sym_link);
747 g_free(fd->format_name);
748 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
750 file_data_change_info_free(NULL, fd);
755 * @brief Checks if the FileData is referenced
757 * Checks the refcount and whether the FileData is locked.
759 static gboolean file_data_check_has_ref(FileData *fd)
761 return fd->ref > 0 || fd->locked;
765 * @brief Consider freeing a FileData.
767 * This function will free a FileData and its children provided that neither its parent nor it has
768 * a positive refcount, and provided that neither is locked.
770 static void file_data_consider_free(FileData *fd)
773 FileData *parent = fd->parent ? fd->parent : fd;
775 g_assert(fd->magick == FD_MAGICK);
776 if (file_data_check_has_ref(fd)) return;
777 if (file_data_check_has_ref(parent)) return;
779 work = parent->sidecar_files;
782 FileData *sfd = static_cast<FileData *>(work->data);
783 if (file_data_check_has_ref(sfd)) return;
787 /* Neither the parent nor the siblings are referenced, so we can free everything */
788 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
789 fd->path, fd->parent ? parent->path : "-");
791 work = parent->sidecar_files;
794 FileData *sfd = static_cast<FileData *>(work->data);
799 g_list_free(parent->sidecar_files);
800 parent->sidecar_files = NULL;
802 file_data_free(parent);
805 #ifdef DEBUG_FILEDATA
806 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
808 void file_data_unref(FileData *fd)
811 if (fd == NULL) return;
812 if (fd->magick != FD_MAGICK)
813 #ifdef DEBUG_FILEDATA
814 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, (void *)fd);
816 log_printf("Error: fd magick mismatch fd=%p", fd);
818 g_assert(fd->magick == FD_MAGICK);
821 #ifdef DEBUG_FILEDATA
822 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->locked, fd->path,
825 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
828 // Free FileData if it's no longer ref'd
829 file_data_consider_free(fd);
833 * @brief Lock the FileData in memory.
835 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
836 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
837 * even if the code would continue to function properly even if the FileData were freed. Code that
838 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
840 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
841 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
843 void file_data_lock(FileData *fd)
845 if (fd == NULL) return;
846 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
848 g_assert(fd->magick == FD_MAGICK);
851 DEBUG_2("file_data_ref fd=%p (%d): '%s'", (void *)fd, fd->ref, fd->path);
855 * @brief Reset the maintain-FileData-in-memory lock
857 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
858 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
859 * keeping it from being freed.
861 void file_data_unlock(FileData *fd)
863 if (fd == NULL) return;
864 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
866 g_assert(fd->magick == FD_MAGICK);
869 // Free FileData if it's no longer ref'd
870 file_data_consider_free(fd);
874 * @brief Lock all of the FileDatas in the provided list
876 * @see file_data_lock(#FileData)
878 void file_data_lock_list(GList *list)
885 FileData *fd = static_cast<FileData *>(work->data);
892 * @brief Unlock all of the FileDatas in the provided list
894 * @see #file_data_unlock(#FileData)
896 void file_data_unlock_list(GList *list)
903 FileData *fd = static_cast<FileData *>(work->data);
905 file_data_unlock(fd);
910 *-----------------------------------------------------------------------------
911 * sidecar file info struct
912 *-----------------------------------------------------------------------------
915 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
917 const FileData *fda = static_cast<const FileData *>(a);
918 const FileData *fdb = static_cast<const FileData *>(b);
920 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
921 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
923 return strcmp(fdb->extension, fda->extension);
927 static gint sidecar_file_priority(const gchar *extension)
932 if (extension == NULL)
935 work = sidecar_ext_get_list();
938 gchar *ext = static_cast<gchar *>(work->data);
941 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
947 static void file_data_check_sidecars(const GList *basename_list)
949 /* basename_list contains the new group - first is the parent, then sorted sidecars */
950 /* all files in the list have ref count > 0 */
953 GList *s_work, *new_sidecars;
956 if (!basename_list) return;
959 DEBUG_2("basename start");
960 work = basename_list;
963 FileData *fd = static_cast<FileData *>(work->data);
965 g_assert(fd->magick == FD_MAGICK);
966 DEBUG_2("basename: %p %s", (void *)fd, fd->name);
969 g_assert(fd->parent->magick == FD_MAGICK);
970 DEBUG_2(" parent: %p", (void *)fd->parent);
972 s_work = fd->sidecar_files;
975 FileData *sfd = static_cast<FileData *>(s_work->data);
976 s_work = s_work->next;
977 g_assert(sfd->magick == FD_MAGICK);
978 DEBUG_2(" sidecar: %p %s", (void *)sfd, sfd->name);
981 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
984 parent_fd = static_cast<FileData *>(basename_list->data);
986 /* check if the second and next entries of basename_list are already connected
987 as sidecars of the first entry (parent_fd) */
988 work = basename_list->next;
989 s_work = parent_fd->sidecar_files;
991 while (work && s_work)
993 if (work->data != s_work->data) break;
995 s_work = s_work->next;
998 if (!work && !s_work)
1000 DEBUG_2("basename no change");
1001 return; /* no change in grouping */
1004 /* we have to regroup it */
1006 /* first, disconnect everything and send notification*/
1008 work = basename_list;
1011 FileData *fd = static_cast<FileData *>(work->data);
1013 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
1017 FileData *old_parent = fd->parent;
1018 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
1019 file_data_ref(old_parent);
1020 file_data_disconnect_sidecar_file(old_parent, fd);
1021 file_data_send_notification(old_parent, NOTIFY_REREAD);
1022 file_data_unref(old_parent);
1025 while (fd->sidecar_files)
1027 FileData *sfd = static_cast<FileData *>(fd->sidecar_files->data);
1028 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
1030 file_data_disconnect_sidecar_file(fd, sfd);
1031 file_data_send_notification(sfd, NOTIFY_REREAD);
1032 file_data_unref(sfd);
1034 file_data_send_notification(fd, NOTIFY_GROUPING);
1036 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
1039 /* now we can form the new group */
1040 work = basename_list->next;
1041 new_sidecars = NULL;
1044 FileData *sfd = static_cast<FileData *>(work->data);
1045 g_assert(sfd->magick == FD_MAGICK);
1046 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
1047 sfd->parent = parent_fd;
1048 new_sidecars = g_list_prepend(new_sidecars, sfd);
1051 g_assert(parent_fd->sidecar_files == NULL);
1052 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
1053 DEBUG_1("basename group changed for %s", parent_fd->path);
1057 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
1059 g_assert(target->magick == FD_MAGICK);
1060 g_assert(sfd->magick == FD_MAGICK);
1061 g_assert(g_list_find(target->sidecar_files, sfd));
1063 file_data_ref(target);
1066 g_assert(sfd->parent == target);
1068 file_data_increment_version(sfd); /* increments both sfd and target */
1070 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
1072 g_free(sfd->extended_extension);
1073 sfd->extended_extension = NULL;
1075 file_data_unref(target);
1076 file_data_unref(sfd);
1079 /* disables / enables grouping for particular file, sends UPDATE notification */
1080 void file_data_disable_grouping(FileData *fd, gboolean disable)
1082 if (!fd->disable_grouping == !disable) return;
1084 fd->disable_grouping = !!disable;
1090 FileData *parent = file_data_ref(fd->parent);
1091 file_data_disconnect_sidecar_file(parent, fd);
1092 file_data_send_notification(parent, NOTIFY_GROUPING);
1093 file_data_unref(parent);
1095 else if (fd->sidecar_files)
1097 GList *sidecar_files = filelist_copy(fd->sidecar_files);
1098 GList *work = sidecar_files;
1101 FileData *sfd = static_cast<FileData *>(work->data);
1103 file_data_disconnect_sidecar_file(fd, sfd);
1104 file_data_send_notification(sfd, NOTIFY_GROUPING);
1106 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
1107 filelist_free(sidecar_files);
1111 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
1116 file_data_increment_version(fd);
1117 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
1119 file_data_send_notification(fd, NOTIFY_GROUPING);
1122 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
1129 FileData *fd = static_cast<FileData *>(work->data);
1131 file_data_disable_grouping(fd, disable);
1139 *-----------------------------------------------------------------------------
1141 *-----------------------------------------------------------------------------
1145 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
1148 if (!filelist_sort_ascend)
1155 switch (filelist_sort_method)
1160 if (fa->size < fb->size) return -1;
1161 if (fa->size > fb->size) return 1;
1162 /* fall back to name */
1165 if (fa->date < fb->date) return -1;
1166 if (fa->date > fb->date) return 1;
1167 /* fall back to name */
1170 if (fa->cdate < fb->cdate) return -1;
1171 if (fa->cdate > fb->cdate) return 1;
1172 /* fall back to name */
1175 if (fa->exifdate < fb->exifdate) return -1;
1176 if (fa->exifdate > fb->exifdate) return 1;
1177 /* fall back to name */
1179 case SORT_EXIFTIMEDIGITIZED:
1180 if (fa->exifdate_digitized < fb->exifdate_digitized) return -1;
1181 if (fa->exifdate_digitized > fb->exifdate_digitized) return 1;
1182 /* fall back to name */
1185 if (fa->rating < fb->rating) return -1;
1186 if (fa->rating > fb->rating) return 1;
1187 /* fall back to name */
1190 if (fa->format_class < fb->format_class) return -1;
1191 if (fa->format_class > fb->format_class) return 1;
1192 /* fall back to name */
1198 if (options->file_sort.case_sensitive)
1199 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1201 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1203 if (ret != 0) return ret;
1205 /* do not return 0 unless the files are really the same
1206 file_data_pool ensures that original_path is unique
1208 return strcmp(fa->original_path, fb->original_path);
1211 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1213 filelist_sort_method = method;
1214 filelist_sort_ascend = ascend;
1215 return filelist_sort_compare_filedata(fa, fb);
1218 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1220 return filelist_sort_compare_filedata(static_cast<FileData *>(a), static_cast<FileData *>(b));
1223 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1225 filelist_sort_method = method;
1226 filelist_sort_ascend = ascend;
1227 return g_list_sort(list, cb);
1230 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1232 filelist_sort_method = method;
1233 filelist_sort_ascend = ascend;
1234 return g_list_insert_sorted(list, data, cb);
1237 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1239 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1242 //GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
1244 //return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1248 *-----------------------------------------------------------------------------
1249 * basename hash - grouping of sidecars in filelist
1250 *-----------------------------------------------------------------------------
1254 static GHashTable *file_data_basename_hash_new(void)
1256 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1259 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1262 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1264 list = static_cast<GList *>(g_hash_table_lookup(basename_hash, basename));
1268 DEBUG_1("TG: basename_hash not found for %s",fd->path);
1269 const gchar *parent_extension = registered_extension_from_path(basename);
1271 if (parent_extension)
1273 DEBUG_1("TG: parent extension %s",parent_extension);
1274 gchar *parent_basename = g_strndup(basename, parent_extension - basename);
1275 DEBUG_1("TG: parent basename %s",parent_basename);
1276 FileData *parent_fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, basename));
1279 DEBUG_1("TG: parent fd found");
1280 list = static_cast<GList *>(g_hash_table_lookup(basename_hash, parent_basename));
1281 if (!g_list_find(list, parent_fd))
1283 DEBUG_1("TG: parent fd doesn't fit");
1284 g_free(parent_basename);
1290 basename = parent_basename;
1291 fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
1297 if (!g_list_find(list, fd))
1299 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1300 g_hash_table_insert(basename_hash, basename, list);
1309 static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
1311 file_data_basename_hash_insert((GHashTable *)basename_hash, (FileData *)fd);
1314 static void file_data_basename_hash_remove_list(gpointer UNUSED(key), gpointer value, gpointer UNUSED(data))
1316 filelist_free((GList *)value);
1319 static void file_data_basename_hash_free(GHashTable *basename_hash)
1321 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1322 g_hash_table_destroy(basename_hash);
1326 *-----------------------------------------------------------------------------
1327 * handling sidecars in filelist
1328 *-----------------------------------------------------------------------------
1331 static GList *filelist_filter_out_sidecars(GList *flist)
1333 GList *work = flist;
1334 GList *flist_filtered = NULL;
1338 FileData *fd = static_cast<FileData *>(work->data);
1341 if (fd->parent) /* remove fd's that are children */
1342 file_data_unref(fd);
1344 flist_filtered = g_list_prepend(flist_filtered, fd);
1348 return flist_filtered;
1351 static void file_data_basename_hash_to_sidecars(gpointer UNUSED(key), gpointer value, gpointer UNUSED(data))
1353 GList *basename_list = (GList *)value;
1354 file_data_check_sidecars(basename_list);
1358 static gboolean is_hidden_file(const gchar *name)
1360 if (name[0] != '.') return FALSE;
1361 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1366 *-----------------------------------------------------------------------------
1367 * the main filelist function
1368 *-----------------------------------------------------------------------------
1371 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1376 GList *dlist = NULL;
1377 GList *flist = NULL;
1378 GList *xmp_files = NULL;
1379 gint (*stat_func)(const gchar *path, struct stat *buf);
1380 GHashTable *basename_hash = NULL;
1382 g_assert(files || dirs);
1384 if (files) *files = NULL;
1385 if (dirs) *dirs = NULL;
1387 pathl = path_from_utf8(dir_path);
1388 if (!pathl) return FALSE;
1390 dp = opendir(pathl);
1397 if (files) basename_hash = file_data_basename_hash_new();
1399 if (follow_symlinks)
1404 while ((dir = readdir(dp)) != NULL)
1406 struct stat ent_sbuf;
1407 const gchar *name = dir->d_name;
1410 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1413 filepath = g_build_filename(pathl, name, NULL);
1414 if (stat_func(filepath, &ent_sbuf) >= 0)
1416 if (S_ISDIR(ent_sbuf.st_mode))
1418 /* we ignore the .thumbnails dir for cleanliness */
1420 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1421 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1422 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1423 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1425 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1430 if (files && filter_name_exists(name))
1432 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1433 flist = g_list_prepend(flist, fd);
1434 if (fd->sidecar_priority && !fd->disable_grouping)
1436 if (strcmp(fd->extension, ".xmp") != 0)
1437 file_data_basename_hash_insert(basename_hash, fd);
1439 xmp_files = g_list_append(xmp_files, fd);
1446 if (errno == EOVERFLOW)
1448 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1460 g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
1461 g_list_free(xmp_files);
1464 if (dirs) *dirs = dlist;
1468 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1470 *files = filelist_filter_out_sidecars(flist);
1472 if (basename_hash) file_data_basename_hash_free(basename_hash);
1477 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1479 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1482 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1484 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1487 FileData *file_data_new_group(const gchar *path_utf8)
1494 if (!file_data_pool)
1496 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
1499 if (!stat_utf8(path_utf8, &st))
1505 if (S_ISDIR(st.st_mode))
1506 return file_data_new(path_utf8, &st, TRUE);
1508 dir = remove_level_from_path(path_utf8);
1510 filelist_read_real(dir, &files, NULL, TRUE);
1512 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
1513 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1519 filelist_free(files);
1525 void filelist_free(GList *list)
1532 file_data_unref((FileData *)work->data);
1540 GList *filelist_copy(GList *list)
1542 GList *new_list = NULL;
1550 fd = static_cast<FileData *>(work->data);
1553 new_list = g_list_prepend(new_list, file_data_ref(fd));
1556 return g_list_reverse(new_list);
1559 GList *filelist_from_path_list(GList *list)
1561 GList *new_list = NULL;
1569 path = static_cast<gchar *>(work->data);
1572 new_list = g_list_prepend(new_list, file_data_new_group(path));
1575 return g_list_reverse(new_list);
1578 GList *filelist_to_path_list(GList *list)
1580 GList *new_list = NULL;
1588 fd = static_cast<FileData *>(work->data);
1591 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1594 return g_list_reverse(new_list);
1597 GList *filelist_filter(GList *list, gboolean is_dir_list)
1601 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1606 FileData *fd = (FileData *)(work->data);
1607 const gchar *name = fd->name;
1609 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1610 (!is_dir_list && !filter_name_exists(name)) ||
1611 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1612 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1616 list = g_list_remove_link(list, link);
1617 file_data_unref(fd);
1628 *-----------------------------------------------------------------------------
1629 * filelist recursive
1630 *-----------------------------------------------------------------------------
1633 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1635 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1638 GList *filelist_sort_path(GList *list)
1640 return g_list_sort(list, filelist_sort_path_cb);
1643 static void filelist_recursive_append(GList **list, GList *dirs)
1650 FileData *fd = (FileData *)(work->data);
1654 if (filelist_read(fd, &f, &d))
1656 f = filelist_filter(f, FALSE);
1657 f = filelist_sort_path(f);
1658 *list = g_list_concat(*list, f);
1660 d = filelist_filter(d, TRUE);
1661 d = filelist_sort_path(d);
1662 filelist_recursive_append(list, d);
1670 static void filelist_recursive_append_full(GList **list, GList *dirs, SortType method, gboolean ascend)
1677 FileData *fd = (FileData *)(work->data);
1681 if (filelist_read(fd, &f, &d))
1683 f = filelist_filter(f, FALSE);
1684 f = filelist_sort_full(f, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1685 *list = g_list_concat(*list, f);
1687 d = filelist_filter(d, TRUE);
1688 d = filelist_sort_path(d);
1689 filelist_recursive_append_full(list, d, method, ascend);
1697 GList *filelist_recursive(FileData *dir_fd)
1702 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1703 list = filelist_filter(list, FALSE);
1704 list = filelist_sort_path(list);
1706 d = filelist_filter(d, TRUE);
1707 d = filelist_sort_path(d);
1708 filelist_recursive_append(&list, d);
1714 GList *filelist_recursive_full(FileData *dir_fd, SortType method, gboolean ascend)
1719 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1720 list = filelist_filter(list, FALSE);
1721 list = filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1723 d = filelist_filter(d, TRUE);
1724 d = filelist_sort_path(d);
1725 filelist_recursive_append_full(&list, d, method, ascend);
1732 *-----------------------------------------------------------------------------
1733 * file modification support
1734 *-----------------------------------------------------------------------------
1738 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1740 if (!fdci && fd) fdci = fd->change;
1744 g_free(fdci->source);
1749 if (fd) fd->change = NULL;
1752 static gboolean file_data_can_write_directly(FileData *fd)
1754 return filter_name_is_writable(fd->extension);
1757 static gboolean file_data_can_write_sidecar(FileData *fd)
1759 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1762 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1764 gchar *sidecar_path = NULL;
1767 if (!file_data_can_write_sidecar(fd)) return NULL;
1769 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1770 gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1773 FileData *sfd = static_cast<FileData *>(work->data);
1775 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1777 sidecar_path = g_strdup(sfd->path);
1781 g_free(extended_extension);
1783 if (!existing_only && !sidecar_path)
1785 if (options->metadata.sidecar_extended_name)
1786 sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1789 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1790 sidecar_path = g_strconcat(base, ".xmp", NULL);
1795 return sidecar_path;
1799 * marks and orientation
1802 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1803 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1804 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1805 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1807 gboolean file_data_get_mark(FileData *fd, gint n)
1809 gboolean valid = (fd->valid_marks & (1 << n));
1811 if (file_data_get_mark_func[n] && !valid)
1813 guint old = fd->marks;
1814 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1816 if (!value != !(fd->marks & (1 << n)))
1818 fd->marks = fd->marks ^ (1 << n);
1821 fd->valid_marks |= (1 << n);
1822 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1824 file_data_unref(fd);
1826 else if (!old && fd->marks)
1832 return !!(fd->marks & (1 << n));
1835 guint file_data_get_marks(FileData *fd)
1838 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1842 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1845 if (!value == !file_data_get_mark(fd, n)) return;
1847 if (file_data_set_mark_func[n])
1849 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1854 fd->marks = fd->marks ^ (1 << n);
1856 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1858 file_data_unref(fd);
1860 else if (!old && fd->marks)
1865 file_data_increment_version(fd);
1866 file_data_send_notification(fd, NOTIFY_MARKS);
1869 gboolean file_data_filter_marks(FileData *fd, guint filter)
1872 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1873 return ((fd->marks & filter) == filter);
1876 GList *file_data_filter_marks_list(GList *list, guint filter)
1883 FileData *fd = static_cast<FileData *>(work->data);
1887 if (!file_data_filter_marks(fd, filter))
1889 list = g_list_remove_link(list, link);
1890 file_data_unref(fd);
1898 gboolean file_data_filter_file_filter(FileData *fd, GRegex *filter)
1900 return g_regex_match(filter, fd->name, (GRegexMatchFlags)0, NULL);
1903 GList *file_data_filter_file_filter_list(GList *list, GRegex *filter)
1910 FileData *fd = static_cast<FileData *>(work->data);
1914 if (!file_data_filter_file_filter(fd, filter))
1916 list = g_list_remove_link(list, link);
1917 file_data_unref(fd);
1925 static gboolean file_data_filter_class(FileData *fd, guint filter)
1929 for (i = 0; i < FILE_FORMAT_CLASSES; i++)
1931 if (filter & (1 << i))
1933 if ((FileFormatClass)i == filter_file_get_class(fd->path))
1943 GList *file_data_filter_class_list(GList *list, guint filter)
1950 FileData *fd = static_cast<FileData *>(work->data);
1954 if (!file_data_filter_class(fd, filter))
1956 list = g_list_remove_link(list, link);
1957 file_data_unref(fd);
1965 static void file_data_notify_mark_func(gpointer UNUSED(key), gpointer value, gpointer UNUSED(user_data))
1967 FileData *fd = static_cast<FileData *>(value);
1968 file_data_increment_version(fd);
1969 file_data_send_notification(fd, NOTIFY_MARKS);
1972 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1974 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1976 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1978 file_data_get_mark_func[n] = get_mark_func;
1979 file_data_set_mark_func[n] = set_mark_func;
1980 file_data_mark_func_data[n] = data;
1981 file_data_destroy_mark_func[n] = notify;
1983 if (get_mark_func && file_data_pool)
1985 /* this effectively changes all known files */
1986 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1992 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1994 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1995 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1996 if (data) *data = file_data_mark_func_data[n];
1999 //gint file_data_get_user_orientation(FileData *fd)
2001 //return fd->user_orientation;
2004 //void file_data_set_user_orientation(FileData *fd, gint value)
2006 //if (fd->user_orientation == value) return;
2008 //fd->user_orientation = value;
2009 //file_data_increment_version(fd);
2010 //file_data_send_notification(fd, NOTIFY_ORIENTATION);
2015 * file_data - operates on the given fd
2016 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
2020 /* return list of sidecar file extensions in a string */
2021 gchar *file_data_sc_list_to_string(FileData *fd)
2024 GString *result = g_string_new("");
2026 work = fd->sidecar_files;
2029 FileData *sfd = static_cast<FileData *>(work->data);
2031 result = g_string_append(result, "+ ");
2032 result = g_string_append(result, sfd->extension);
2034 if (work) result = g_string_append_c(result, ' ');
2037 return g_string_free(result, FALSE);
2043 * add FileDataChangeInfo (see typedefs.h) for the given operation
2044 * uses file_data_add_change_info
2046 * fails if the fd->change already exists - change operations can't run in parallel
2047 * fd->change_info works as a lock
2049 * dest can be NULL - in this case the current name is used for now, it will
2054 FileDataChangeInfo types:
2056 MOVE - path is changed, name may be changed too
2057 RENAME - path remains unchanged, name is changed
2058 extension should remain (FIXME should we allow editing extension? it will make problems with grouping)
2059 sidecar names are changed too, extensions are not changed
2061 UPDATE - file size, date or grouping has been changed
2064 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
2066 FileDataChangeInfo *fdci;
2068 if (fd->change) return FALSE;
2070 fdci = g_new0(FileDataChangeInfo, 1);
2075 fdci->source = g_strdup(src);
2077 fdci->source = g_strdup(fd->path);
2080 fdci->dest = g_strdup(dest);
2087 static void file_data_planned_change_remove(FileData *fd)
2089 if (file_data_planned_change_hash &&
2090 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
2092 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
2094 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
2095 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
2096 file_data_unref(fd);
2097 if (g_hash_table_size(file_data_planned_change_hash) == 0)
2099 g_hash_table_destroy(file_data_planned_change_hash);
2100 file_data_planned_change_hash = NULL;
2101 DEBUG_1("planned change: empty");
2108 void file_data_free_ci(FileData *fd)
2110 FileDataChangeInfo *fdci = fd->change;
2114 file_data_planned_change_remove(fd);
2116 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
2118 g_free(fdci->source);
2126 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
2128 FileDataChangeInfo *fdci = fd->change;
2130 fdci->regroup_when_finished = enable;
2133 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
2137 if (fd->parent) fd = fd->parent;
2139 if (fd->change) return FALSE;
2141 work = fd->sidecar_files;
2144 FileData *sfd = static_cast<FileData *>(work->data);
2146 if (sfd->change) return FALSE;
2150 file_data_add_ci(fd, type, NULL, NULL);
2152 work = fd->sidecar_files;
2155 FileData *sfd = static_cast<FileData *>(work->data);
2157 file_data_add_ci(sfd, type, NULL, NULL);
2164 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
2168 if (fd->parent) fd = fd->parent;
2170 if (!fd->change || fd->change->type != type) return FALSE;
2172 work = fd->sidecar_files;
2175 FileData *sfd = static_cast<FileData *>(work->data);
2177 if (!sfd->change || sfd->change->type != type) return FALSE;
2185 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
2187 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
2188 file_data_sc_update_ci_copy(fd, dest_path);
2192 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
2194 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
2195 file_data_sc_update_ci_move(fd, dest_path);
2199 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
2201 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
2202 file_data_sc_update_ci_rename(fd, dest_path);
2206 gboolean file_data_sc_add_ci_delete(FileData *fd)
2208 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
2211 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
2213 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
2214 file_data_sc_update_ci_unspecified(fd, dest_path);
2218 gboolean file_data_add_ci_write_metadata(FileData *fd)
2220 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
2223 void file_data_sc_free_ci(FileData *fd)
2227 if (fd->parent) fd = fd->parent;
2229 file_data_free_ci(fd);
2231 work = fd->sidecar_files;
2234 FileData *sfd = static_cast<FileData *>(work->data);
2236 file_data_free_ci(sfd);
2241 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2244 gboolean ret = TRUE;
2249 FileData *fd = static_cast<FileData *>(work->data);
2251 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2258 static void file_data_sc_revert_ci_list(GList *fd_list)
2265 FileData *fd = static_cast<FileData *>(work->data);
2267 file_data_sc_free_ci(fd);
2272 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2279 FileData *fd = static_cast<FileData *>(work->data);
2281 if (!func(fd, dest))
2283 file_data_sc_revert_ci_list(work->prev);
2292 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2294 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2297 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2299 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2302 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2304 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2307 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2309 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2312 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2315 gboolean ret = TRUE;
2320 FileData *fd = static_cast<FileData *>(work->data);
2322 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2329 void file_data_free_ci_list(GList *fd_list)
2336 FileData *fd = static_cast<FileData *>(work->data);
2338 file_data_free_ci(fd);
2343 void file_data_sc_free_ci_list(GList *fd_list)
2350 FileData *fd = static_cast<FileData *>(work->data);
2352 file_data_sc_free_ci(fd);
2358 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2359 * fails if fd->change does not exist or the change type does not match
2362 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2364 FileDataChangeType type = fd->change->type;
2366 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2370 if (!file_data_planned_change_hash)
2371 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2373 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2375 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2376 g_hash_table_remove(file_data_planned_change_hash, old_path);
2377 file_data_unref(fd);
2380 ofd = static_cast<FileData *>(g_hash_table_lookup(file_data_planned_change_hash, new_path));
2385 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2386 g_hash_table_remove(file_data_planned_change_hash, new_path);
2387 file_data_unref(ofd);
2390 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2392 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2397 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2399 gchar *old_path = fd->change->dest;
2401 fd->change->dest = g_strdup(dest_path);
2402 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2406 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2408 const gchar *extension = registered_extension_from_path(fd->change->source);
2409 gchar *base = remove_extension_from_path(dest_path);
2410 gchar *old_path = fd->change->dest;
2412 fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2413 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2419 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2422 gchar *dest_path_full = NULL;
2424 if (fd->parent) fd = fd->parent;
2428 dest_path = fd->path;
2430 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2432 gchar *dir = remove_level_from_path(fd->path);
2434 dest_path_full = g_build_filename(dir, dest_path, NULL);
2436 dest_path = dest_path_full;
2438 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2440 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2441 dest_path = dest_path_full;
2444 file_data_update_ci_dest(fd, dest_path);
2446 work = fd->sidecar_files;
2449 FileData *sfd = static_cast<FileData *>(work->data);
2451 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2455 g_free(dest_path_full);
2458 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2460 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2461 file_data_sc_update_ci(fd, dest_path);
2465 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2467 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2470 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2472 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2475 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2477 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2480 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2482 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2485 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2487 gboolean (*func)(FileData *, const gchar *))
2490 gboolean ret = TRUE;
2495 FileData *fd = static_cast<FileData *>(work->data);
2497 if (!func(fd, dest)) ret = FALSE;
2504 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2506 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2509 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2511 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2514 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2516 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2521 * verify source and dest paths - dest image exists, etc.
2522 * it should detect all possible problems with the planned operation
2525 gint file_data_verify_ci(FileData *fd, GList *list)
2527 gint ret = CHANGE_OK;
2530 FileData *fd1 = NULL;
2534 DEBUG_1("Change checked: no change info: %s", fd->path);
2538 if (!isname(fd->path))
2540 /* this probably should not happen */
2541 ret |= CHANGE_NO_SRC;
2542 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2546 dir = remove_level_from_path(fd->path);
2548 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2549 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2550 fd->change->type != FILEDATA_CHANGE_RENAME &&
2551 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2554 ret |= CHANGE_WARN_UNSAVED_META;
2555 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2558 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2559 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2560 !access_file(fd->path, R_OK))
2562 ret |= CHANGE_NO_READ_PERM;
2563 DEBUG_1("Change checked: no read permission: %s", fd->path);
2565 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2566 !access_file(dir, W_OK))
2568 ret |= CHANGE_NO_WRITE_PERM_DIR;
2569 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2571 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2572 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2573 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2574 !access_file(fd->path, W_OK))
2576 ret |= CHANGE_WARN_NO_WRITE_PERM;
2577 DEBUG_1("Change checked: no write permission: %s", fd->path);
2579 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2580 - that means that there are no hard errors and warnings can be disabled
2581 - the destination is determined during the check
2583 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2585 /* determine destination file */
2586 gboolean have_dest = FALSE;
2587 gchar *dest_dir = NULL;
2589 if (options->metadata.save_in_image_file)
2591 if (file_data_can_write_directly(fd))
2593 /* we can write the file directly */
2594 if (access_file(fd->path, W_OK))
2600 if (options->metadata.warn_on_write_problems)
2602 ret |= CHANGE_WARN_NO_WRITE_PERM;
2603 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2607 else if (file_data_can_write_sidecar(fd))
2609 /* we can write sidecar */
2610 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2611 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2613 file_data_update_ci_dest(fd, sidecar);
2618 if (options->metadata.warn_on_write_problems)
2620 ret |= CHANGE_WARN_NO_WRITE_PERM;
2621 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2630 /* write private metadata file under ~/.geeqie */
2632 /* If an existing metadata file exists, we will try writing to
2633 * it's location regardless of the user's preference.
2635 gchar *metadata_path = NULL;
2637 /* but ignore XMP if we are not able to write it */
2638 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2640 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2642 if (metadata_path && !access_file(metadata_path, W_OK))
2644 g_free(metadata_path);
2645 metadata_path = NULL;
2652 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2653 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2655 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2657 metadata_path = g_build_filename(dest_dir, filename, NULL);
2661 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2663 file_data_update_ci_dest(fd, metadata_path);
2668 ret |= CHANGE_NO_WRITE_PERM_DEST;
2669 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2671 g_free(metadata_path);
2676 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2681 same = (strcmp(fd->path, fd->change->dest) == 0);
2685 const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2686 if (!dest_ext) dest_ext = "";
2687 if (!options->file_filter.disable_file_extension_checks)
2689 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2691 ret |= CHANGE_WARN_CHANGED_EXT;
2692 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2698 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /** @FIXME this is now needed for running editors */
2700 ret |= CHANGE_WARN_SAME;
2701 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2705 dest_dir = remove_level_from_path(fd->change->dest);
2707 if (!isdir(dest_dir))
2709 ret |= CHANGE_NO_DEST_DIR;
2710 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2712 else if (!access_file(dest_dir, W_OK))
2714 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2715 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2719 if (isfile(fd->change->dest))
2721 if (!access_file(fd->change->dest, W_OK))
2723 ret |= CHANGE_NO_WRITE_PERM_DEST;
2724 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2728 ret |= CHANGE_WARN_DEST_EXISTS;
2729 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2732 else if (isdir(fd->change->dest))
2734 ret |= CHANGE_DEST_EXISTS;
2735 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2742 /* During a rename operation, check if another planned destination file has
2745 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2746 fd->change->type == FILEDATA_CHANGE_COPY ||
2747 fd->change->type == FILEDATA_CHANGE_MOVE)
2752 fd1 = static_cast<FileData *>(work->data);
2754 if (fd1 != NULL && fd != fd1 )
2756 if (!strcmp(fd->change->dest, fd1->change->dest))
2758 ret |= CHANGE_DUPLICATE_DEST;
2764 fd->change->error = ret;
2765 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2772 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2777 ret = file_data_verify_ci(fd, list);
2779 work = fd->sidecar_files;
2782 FileData *sfd = static_cast<FileData *>(work->data);
2784 ret |= file_data_verify_ci(sfd, list);
2791 gchar *file_data_get_error_string(gint error)
2793 GString *result = g_string_new("");
2795 if (error & CHANGE_NO_SRC)
2797 if (result->len > 0) g_string_append(result, ", ");
2798 g_string_append(result, _("file or directory does not exist"));
2801 if (error & CHANGE_DEST_EXISTS)
2803 if (result->len > 0) g_string_append(result, ", ");
2804 g_string_append(result, _("destination already exists"));
2807 if (error & CHANGE_NO_WRITE_PERM_DEST)
2809 if (result->len > 0) g_string_append(result, ", ");
2810 g_string_append(result, _("destination can't be overwritten"));
2813 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2815 if (result->len > 0) g_string_append(result, ", ");
2816 g_string_append(result, _("destination directory is not writable"));
2819 if (error & CHANGE_NO_DEST_DIR)
2821 if (result->len > 0) g_string_append(result, ", ");
2822 g_string_append(result, _("destination directory does not exist"));
2825 if (error & CHANGE_NO_WRITE_PERM_DIR)
2827 if (result->len > 0) g_string_append(result, ", ");
2828 g_string_append(result, _("source directory is not writable"));
2831 if (error & CHANGE_NO_READ_PERM)
2833 if (result->len > 0) g_string_append(result, ", ");
2834 g_string_append(result, _("no read permission"));
2837 if (error & CHANGE_WARN_NO_WRITE_PERM)
2839 if (result->len > 0) g_string_append(result, ", ");
2840 g_string_append(result, _("file is readonly"));
2843 if (error & CHANGE_WARN_DEST_EXISTS)
2845 if (result->len > 0) g_string_append(result, ", ");
2846 g_string_append(result, _("destination already exists and will be overwritten"));
2849 if (error & CHANGE_WARN_SAME)
2851 if (result->len > 0) g_string_append(result, ", ");
2852 g_string_append(result, _("source and destination are the same"));
2855 if (error & CHANGE_WARN_CHANGED_EXT)
2857 if (result->len > 0) g_string_append(result, ", ");
2858 g_string_append(result, _("source and destination have different extension"));
2861 if (error & CHANGE_WARN_UNSAVED_META)
2863 if (result->len > 0) g_string_append(result, ", ");
2864 g_string_append(result, _("there are unsaved metadata changes for the file"));
2867 if (error & CHANGE_DUPLICATE_DEST)
2869 if (result->len > 0) g_string_append(result, ", ");
2870 g_string_append(result, _("another destination file has the same filename"));
2873 return g_string_free(result, FALSE);
2876 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2879 gint all_errors = 0;
2880 gint common_errors = ~0;
2885 if (!list) return 0;
2887 num = g_list_length(list);
2888 errors = g_new(int, num);
2896 fd = static_cast<FileData *>(work->data);
2899 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2900 all_errors |= error;
2901 common_errors &= error;
2908 if (desc && all_errors)
2911 GString *result = g_string_new("");
2915 gchar *str = file_data_get_error_string(common_errors);
2916 g_string_append(result, str);
2917 g_string_append(result, "\n");
2928 fd = static_cast<FileData *>(work->data);
2931 error = errors[i] & ~common_errors;
2935 gchar *str = file_data_get_error_string(error);
2936 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2941 *desc = g_string_free(result, FALSE);
2950 * perform the change described by FileFataChangeInfo
2951 * it is used for internal operations,
2952 * this function actually operates with files on the filesystem
2953 * it should implement safe delete
2956 static gboolean file_data_perform_move(FileData *fd)
2958 g_assert(!strcmp(fd->change->source, fd->path));
2959 return move_file(fd->change->source, fd->change->dest);
2962 static gboolean file_data_perform_copy(FileData *fd)
2964 g_assert(!strcmp(fd->change->source, fd->path));
2965 return copy_file(fd->change->source, fd->change->dest);
2968 static gboolean file_data_perform_delete(FileData *fd)
2970 if (isdir(fd->path) && !islink(fd->path))
2971 return rmdir_utf8(fd->path);
2973 if (options->file_ops.safe_delete_enable)
2974 return file_util_safe_unlink(fd->path);
2976 return unlink_file(fd->path);
2979 gboolean file_data_perform_ci(FileData *fd)
2981 FileDataChangeType type = fd->change->type;
2985 case FILEDATA_CHANGE_MOVE:
2986 return file_data_perform_move(fd);
2987 case FILEDATA_CHANGE_COPY:
2988 return file_data_perform_copy(fd);
2989 case FILEDATA_CHANGE_RENAME:
2990 return file_data_perform_move(fd); /* the same as move */
2991 case FILEDATA_CHANGE_DELETE:
2992 return file_data_perform_delete(fd);
2993 case FILEDATA_CHANGE_WRITE_METADATA:
2994 return metadata_write_perform(fd);
2995 case FILEDATA_CHANGE_UNSPECIFIED:
2996 /* nothing to do here */
3004 gboolean file_data_sc_perform_ci(FileData *fd)
3007 gboolean ret = TRUE;
3008 FileDataChangeType type = fd->change->type;
3010 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3012 work = fd->sidecar_files;
3015 FileData *sfd = static_cast<FileData *>(work->data);
3017 if (!file_data_perform_ci(sfd)) ret = FALSE;
3021 if (!file_data_perform_ci(fd)) ret = FALSE;
3027 * updates FileData structure according to FileDataChangeInfo
3030 gboolean file_data_apply_ci(FileData *fd)
3032 FileDataChangeType type = fd->change->type;
3034 /** @FIXME delete ?*/
3035 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
3037 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
3038 file_data_planned_change_remove(fd);
3040 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
3042 /* this change overwrites another file which is already known to other modules
3043 renaming fd would create duplicate FileData structure
3044 the best thing we can do is nothing
3046 /** @FIXME maybe we could copy stuff like marks
3048 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
3052 file_data_set_path(fd, fd->change->dest);
3055 file_data_increment_version(fd);
3056 file_data_send_notification(fd, NOTIFY_CHANGE);
3061 gboolean file_data_sc_apply_ci(FileData *fd)
3064 FileDataChangeType type = fd->change->type;
3066 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3068 work = fd->sidecar_files;
3071 FileData *sfd = static_cast<FileData *>(work->data);
3073 file_data_apply_ci(sfd);
3077 file_data_apply_ci(fd);
3082 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
3085 if (fd->parent) fd = fd->parent;
3086 if (!g_list_find(list, fd)) return FALSE;
3088 work = fd->sidecar_files;
3091 if (!g_list_find(list, work->data)) return FALSE;
3097 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
3102 /* change partial groups to independent files */
3107 FileData *fd = static_cast<FileData *>(work->data);
3110 if (!file_data_list_contains_whole_group(list, fd))
3112 file_data_disable_grouping(fd, TRUE);
3115 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
3121 /* remove sidecars from the list,
3122 they can be still accessed via main_fd->sidecar_files */
3126 FileData *fd = static_cast<FileData *>(work->data);
3130 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
3132 out = g_list_prepend(out, file_data_ref(fd));
3136 filelist_free(list);
3137 out = g_list_reverse(out);
3147 * notify other modules about the change described by FileDataChangeInfo
3150 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks */
3151 /** @FIXME do we need the ignore_list? It looks like a workaround for ineffective
3152 implementation in view-file-list.cc */
3155 typedef struct _NotifyIdleData NotifyIdleData;
3157 struct _NotifyIdleData {
3163 typedef struct _NotifyData NotifyData;
3165 struct _NotifyData {
3166 FileDataNotifyFunc func;
3168 NotifyPriority priority;
3171 static GList *notify_func_list = NULL;
3173 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
3175 NotifyData *nda = (NotifyData *)a;
3176 NotifyData *ndb = (NotifyData *)b;
3178 if (nda->priority < ndb->priority) return -1;
3179 if (nda->priority > ndb->priority) return 1;
3183 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
3186 GList *work = notify_func_list;
3190 NotifyData *nd = (NotifyData *)work->data;
3192 if (nd->func == func && nd->data == data)
3194 g_warning("Notify func already registered");
3200 nd = g_new(NotifyData, 1);
3203 nd->priority = priority;
3205 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
3206 DEBUG_2("Notify func registered: %p", (void *)nd);
3211 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
3213 GList *work = notify_func_list;
3217 NotifyData *nd = (NotifyData *)work->data;
3219 if (nd->func == func && nd->data == data)
3221 notify_func_list = g_list_delete_link(notify_func_list, work);
3223 DEBUG_2("Notify func unregistered: %p", (void *)nd);
3229 g_warning("Notify func not found");
3234 //gboolean file_data_send_notification_idle_cb(gpointer data)
3236 //NotifyIdleData *nid = (NotifyIdleData *)data;
3237 //GList *work = notify_func_list;
3241 //NotifyData *nd = (NotifyData *)work->data;
3243 //nd->func(nid->fd, nid->type, nd->data);
3244 //work = work->next;
3246 //file_data_unref(nid->fd);
3251 void file_data_send_notification(FileData *fd, NotifyType type)
3253 GList *work = notify_func_list;
3257 NotifyData *nd = (NotifyData *)work->data;
3259 nd->func(fd, type, nd->data);
3263 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3264 nid->fd = file_data_ref(fd);
3266 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3270 static GHashTable *file_data_monitor_pool = NULL;
3271 static guint realtime_monitor_id = 0; /* event source id */
3273 static void realtime_monitor_check_cb(gpointer key, gpointer UNUSED(value), gpointer UNUSED(data))
3275 FileData *fd = static_cast<FileData *>(key);
3277 file_data_check_changed_files(fd);
3279 DEBUG_1("monitor %s", fd->path);
3282 static gboolean realtime_monitor_cb(gpointer UNUSED(data))
3284 if (!options->update_on_time_change) return TRUE;
3285 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
3289 gboolean file_data_register_real_time_monitor(FileData *fd)
3295 if (!file_data_monitor_pool)
3296 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3298 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3300 DEBUG_1("Register realtime %d %s", count, fd->path);
3303 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3305 if (!realtime_monitor_id)
3307 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
3313 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3317 g_assert(file_data_monitor_pool);
3319 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3321 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3323 g_assert(count > 0);
3328 g_hash_table_remove(file_data_monitor_pool, fd);
3330 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3332 file_data_unref(fd);
3334 if (g_hash_table_size(file_data_monitor_pool) == 0)
3336 g_source_remove(realtime_monitor_id);
3337 realtime_monitor_id = 0;
3345 *-----------------------------------------------------------------------------
3346 * Saving marks list, clearing marks
3347 * Uses file_data_pool
3348 *-----------------------------------------------------------------------------
3351 static void marks_get_files(gpointer key, gpointer value, gpointer userdata)
3353 gchar *file_name = static_cast<gchar *>(key);
3354 GString *result = static_cast<GString *>(userdata);
3357 if (isfile(file_name))
3359 fd = static_cast<FileData *>(value);
3360 if (fd && fd->marks > 0)
3362 g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks);
3367 gboolean marks_list_load(const gchar *path)
3375 pathl = path_from_utf8(path);
3376 f = fopen(pathl, "r");
3378 if (!f) return FALSE;
3380 /* first line must start with Marks comment */
3381 if (!fgets(s_buf, sizeof(s_buf), f) ||
3382 strncmp(s_buf, "#Marks", 6) != 0)
3388 while (fgets(s_buf, sizeof(s_buf), f))
3390 if (s_buf[0]=='#') continue;
3391 file_path = strtok(s_buf, ",");
3392 marks_value = strtok(NULL, ",");
3393 if (isfile(file_path))
3395 FileData *fd = file_data_new_no_grouping(file_path);
3400 gint mark_no = 1 << n;
3401 if (atoi(marks_value) & mark_no)
3403 file_data_set_mark(fd, n , 1);
3414 gboolean marks_list_save(gchar *path, gboolean save)
3416 SecureSaveInfo *ssi;
3418 GString *marks = g_string_new("");
3420 pathl = path_from_utf8(path);
3421 ssi = secure_open(pathl);
3425 log_printf(_("Error: Unable to write marks lists to: %s\n"), path);
3429 secure_fprintf(ssi, "#Marks lists\n");
3433 g_hash_table_foreach(file_data_pool, marks_get_files, marks);
3435 secure_fprintf(ssi, "%s", marks->str);
3436 g_string_free(marks, FALSE);
3438 secure_fprintf(ssi, "#end\n");
3439 return (secure_close(ssi) == 0);
3442 static void marks_clear(gpointer key, gpointer value, gpointer UNUSED(userdata))
3444 gchar *file_name = static_cast<gchar *>(key);
3449 if (isfile(file_name))
3451 fd = static_cast<FileData *>(value);
3452 if (fd && fd->marks > 0)
3458 if (fd->marks & mark_no)
3460 file_data_set_mark(fd, n , 0);
3468 void marks_clear_all()
3470 g_hash_table_foreach(file_data_pool, marks_clear, NULL);
3473 void file_data_set_page_num(FileData *fd, gint page_num)
3475 if (fd->page_total > 1 && page_num < 0)
3477 fd->page_num = fd->page_total - 1;
3479 else if (fd->page_total > 1 && page_num <= fd->page_total)
3481 fd->page_num = page_num - 1;
3487 file_data_send_notification(fd, NOTIFY_REREAD);
3490 void file_data_inc_page_num(FileData *fd)
3492 if (fd->page_total > 0 && fd->page_num < fd->page_total - 1)
3494 fd->page_num = fd->page_num + 1;
3496 else if (fd->page_total == 0)
3498 fd->page_num = fd->page_num + 1;
3500 file_data_send_notification(fd, NOTIFY_REREAD);
3503 void file_data_dec_page_num(FileData *fd)
3505 if (fd->page_num > 0)
3507 fd->page_num = fd->page_num - 1;
3509 file_data_send_notification(fd, NOTIFY_REREAD);
3512 void file_data_set_page_total(FileData *fd, gint page_total)
3514 fd->page_total = page_total;
3517 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */