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()
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));
712 fd = static_cast<FileData *>(list->data);
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 = static_cast<FileData *>(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 = static_cast<FileData *>(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 = static_cast<FileData *>(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 = static_cast<FileData *>(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 = static_cast<const FileData *>(a);
916 const FileData *fdb = static_cast<const FileData *>(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 = static_cast<gchar *>(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 = static_cast<FileData *>(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 = static_cast<FileData *>(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 = static_cast<FileData *>(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 = static_cast<FileData *>(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 = static_cast<FileData *>(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 = static_cast<FileData *>(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 = static_cast<FileData *>(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 = static_cast<FileData *>(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(static_cast<FileData *>(a), static_cast<FileData *>(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 = static_cast<GList *>(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 = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, basename));
1277 DEBUG_1("TG: parent fd found");
1278 list = static_cast<GList *>(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 = static_cast<FileData *>(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 (!file_data_pool)
1494 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
1497 if (!stat_utf8(path_utf8, &st))
1503 if (S_ISDIR(st.st_mode))
1504 return file_data_new(path_utf8, &st, TRUE);
1506 dir = remove_level_from_path(path_utf8);
1508 filelist_read_real(dir, &files, NULL, TRUE);
1510 fd = static_cast<FileData *>(g_hash_table_lookup(file_data_pool, path_utf8));
1511 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1517 filelist_free(files);
1523 void filelist_free(GList *list)
1530 file_data_unref((FileData *)work->data);
1538 GList *filelist_copy(GList *list)
1540 GList *new_list = NULL;
1548 fd = static_cast<FileData *>(work->data);
1551 new_list = g_list_prepend(new_list, file_data_ref(fd));
1554 return g_list_reverse(new_list);
1557 GList *filelist_from_path_list(GList *list)
1559 GList *new_list = NULL;
1567 path = static_cast<gchar *>(work->data);
1570 new_list = g_list_prepend(new_list, file_data_new_group(path));
1573 return g_list_reverse(new_list);
1576 GList *filelist_to_path_list(GList *list)
1578 GList *new_list = NULL;
1586 fd = static_cast<FileData *>(work->data);
1589 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1592 return g_list_reverse(new_list);
1595 GList *filelist_filter(GList *list, gboolean is_dir_list)
1599 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1604 FileData *fd = (FileData *)(work->data);
1605 const gchar *name = fd->name;
1607 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1608 (!is_dir_list && !filter_name_exists(name)) ||
1609 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1610 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1614 list = g_list_remove_link(list, link);
1615 file_data_unref(fd);
1626 *-----------------------------------------------------------------------------
1627 * filelist recursive
1628 *-----------------------------------------------------------------------------
1631 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1633 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1636 GList *filelist_sort_path(GList *list)
1638 return g_list_sort(list, filelist_sort_path_cb);
1641 static void filelist_recursive_append(GList **list, GList *dirs)
1648 FileData *fd = (FileData *)(work->data);
1652 if (filelist_read(fd, &f, &d))
1654 f = filelist_filter(f, FALSE);
1655 f = filelist_sort_path(f);
1656 *list = g_list_concat(*list, f);
1658 d = filelist_filter(d, TRUE);
1659 d = filelist_sort_path(d);
1660 filelist_recursive_append(list, d);
1668 static void filelist_recursive_append_full(GList **list, GList *dirs, SortType method, gboolean ascend)
1675 FileData *fd = (FileData *)(work->data);
1679 if (filelist_read(fd, &f, &d))
1681 f = filelist_filter(f, FALSE);
1682 f = filelist_sort_full(f, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1683 *list = g_list_concat(*list, f);
1685 d = filelist_filter(d, TRUE);
1686 d = filelist_sort_path(d);
1687 filelist_recursive_append_full(list, d, method, ascend);
1695 GList *filelist_recursive(FileData *dir_fd)
1700 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1701 list = filelist_filter(list, FALSE);
1702 list = filelist_sort_path(list);
1704 d = filelist_filter(d, TRUE);
1705 d = filelist_sort_path(d);
1706 filelist_recursive_append(&list, d);
1712 GList *filelist_recursive_full(FileData *dir_fd, SortType method, gboolean ascend)
1717 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1718 list = filelist_filter(list, FALSE);
1719 list = filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1721 d = filelist_filter(d, TRUE);
1722 d = filelist_sort_path(d);
1723 filelist_recursive_append_full(&list, d, method, ascend);
1730 *-----------------------------------------------------------------------------
1731 * file modification support
1732 *-----------------------------------------------------------------------------
1736 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1738 if (!fdci && fd) fdci = fd->change;
1742 g_free(fdci->source);
1747 if (fd) fd->change = NULL;
1750 static gboolean file_data_can_write_directly(FileData *fd)
1752 return filter_name_is_writable(fd->extension);
1755 static gboolean file_data_can_write_sidecar(FileData *fd)
1757 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1760 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1762 gchar *sidecar_path = NULL;
1765 if (!file_data_can_write_sidecar(fd)) return NULL;
1767 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1768 gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1771 FileData *sfd = static_cast<FileData *>(work->data);
1773 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1775 sidecar_path = g_strdup(sfd->path);
1779 g_free(extended_extension);
1781 if (!existing_only && !sidecar_path)
1783 if (options->metadata.sidecar_extended_name)
1784 sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1787 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1788 sidecar_path = g_strconcat(base, ".xmp", NULL);
1793 return sidecar_path;
1797 * marks and orientation
1800 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1801 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1802 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1803 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1805 gboolean file_data_get_mark(FileData *fd, gint n)
1807 gboolean valid = (fd->valid_marks & (1 << n));
1809 if (file_data_get_mark_func[n] && !valid)
1811 guint old = fd->marks;
1812 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1814 if (!value != !(fd->marks & (1 << n)))
1816 fd->marks = fd->marks ^ (1 << n);
1819 fd->valid_marks |= (1 << n);
1820 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1822 file_data_unref(fd);
1824 else if (!old && fd->marks)
1830 return !!(fd->marks & (1 << n));
1833 guint file_data_get_marks(FileData *fd)
1836 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1840 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1843 if (!value == !file_data_get_mark(fd, n)) return;
1845 if (file_data_set_mark_func[n])
1847 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1852 fd->marks = fd->marks ^ (1 << n);
1854 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1856 file_data_unref(fd);
1858 else if (!old && fd->marks)
1863 file_data_increment_version(fd);
1864 file_data_send_notification(fd, NOTIFY_MARKS);
1867 gboolean file_data_filter_marks(FileData *fd, guint filter)
1870 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1871 return ((fd->marks & filter) == filter);
1874 GList *file_data_filter_marks_list(GList *list, guint filter)
1881 FileData *fd = static_cast<FileData *>(work->data);
1885 if (!file_data_filter_marks(fd, filter))
1887 list = g_list_remove_link(list, link);
1888 file_data_unref(fd);
1896 gboolean file_data_filter_file_filter(FileData *fd, GRegex *filter)
1898 return g_regex_match(filter, fd->name, (GRegexMatchFlags)0, NULL);
1901 GList *file_data_filter_file_filter_list(GList *list, GRegex *filter)
1908 FileData *fd = static_cast<FileData *>(work->data);
1912 if (!file_data_filter_file_filter(fd, filter))
1914 list = g_list_remove_link(list, link);
1915 file_data_unref(fd);
1923 static gboolean file_data_filter_class(FileData *fd, guint filter)
1927 for (i = 0; i < FILE_FORMAT_CLASSES; i++)
1929 if (filter & (1 << i))
1931 if ((FileFormatClass)i == filter_file_get_class(fd->path))
1941 GList *file_data_filter_class_list(GList *list, guint filter)
1948 FileData *fd = static_cast<FileData *>(work->data);
1952 if (!file_data_filter_class(fd, filter))
1954 list = g_list_remove_link(list, link);
1955 file_data_unref(fd);
1963 static void file_data_notify_mark_func(gpointer UNUSED(key), gpointer value, gpointer UNUSED(user_data))
1965 FileData *fd = static_cast<FileData *>(value);
1966 file_data_increment_version(fd);
1967 file_data_send_notification(fd, NOTIFY_MARKS);
1970 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1972 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1974 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1976 file_data_get_mark_func[n] = get_mark_func;
1977 file_data_set_mark_func[n] = set_mark_func;
1978 file_data_mark_func_data[n] = data;
1979 file_data_destroy_mark_func[n] = notify;
1981 if (get_mark_func && file_data_pool)
1983 /* this effectively changes all known files */
1984 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1990 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1992 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1993 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1994 if (data) *data = file_data_mark_func_data[n];
1997 //gint file_data_get_user_orientation(FileData *fd)
1999 //return fd->user_orientation;
2002 //void file_data_set_user_orientation(FileData *fd, gint value)
2004 //if (fd->user_orientation == value) return;
2006 //fd->user_orientation = value;
2007 //file_data_increment_version(fd);
2008 //file_data_send_notification(fd, NOTIFY_ORIENTATION);
2013 * file_data - operates on the given fd
2014 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
2018 /* return list of sidecar file extensions in a string */
2019 gchar *file_data_sc_list_to_string(FileData *fd)
2022 GString *result = g_string_new("");
2024 work = fd->sidecar_files;
2027 FileData *sfd = static_cast<FileData *>(work->data);
2029 result = g_string_append(result, "+ ");
2030 result = g_string_append(result, sfd->extension);
2032 if (work) result = g_string_append_c(result, ' ');
2035 return g_string_free(result, FALSE);
2041 * add FileDataChangeInfo (see typedefs.h) for the given operation
2042 * uses file_data_add_change_info
2044 * fails if the fd->change already exists - change operations can't run in parallel
2045 * fd->change_info works as a lock
2047 * dest can be NULL - in this case the current name is used for now, it will
2052 FileDataChangeInfo types:
2054 MOVE - path is changed, name may be changed too
2055 RENAME - path remains unchanged, name is changed
2056 extension should remain (FIXME should we allow editing extension? it will make problems with grouping)
2057 sidecar names are changed too, extensions are not changed
2059 UPDATE - file size, date or grouping has been changed
2062 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
2064 FileDataChangeInfo *fdci;
2066 if (fd->change) return FALSE;
2068 fdci = g_new0(FileDataChangeInfo, 1);
2073 fdci->source = g_strdup(src);
2075 fdci->source = g_strdup(fd->path);
2078 fdci->dest = g_strdup(dest);
2085 static void file_data_planned_change_remove(FileData *fd)
2087 if (file_data_planned_change_hash &&
2088 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
2090 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
2092 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
2093 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
2094 file_data_unref(fd);
2095 if (g_hash_table_size(file_data_planned_change_hash) == 0)
2097 g_hash_table_destroy(file_data_planned_change_hash);
2098 file_data_planned_change_hash = NULL;
2099 DEBUG_1("planned change: empty");
2106 void file_data_free_ci(FileData *fd)
2108 FileDataChangeInfo *fdci = fd->change;
2112 file_data_planned_change_remove(fd);
2114 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
2116 g_free(fdci->source);
2124 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
2126 FileDataChangeInfo *fdci = fd->change;
2128 fdci->regroup_when_finished = enable;
2131 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
2135 if (fd->parent) fd = fd->parent;
2137 if (fd->change) return FALSE;
2139 work = fd->sidecar_files;
2142 FileData *sfd = static_cast<FileData *>(work->data);
2144 if (sfd->change) return FALSE;
2148 file_data_add_ci(fd, type, NULL, NULL);
2150 work = fd->sidecar_files;
2153 FileData *sfd = static_cast<FileData *>(work->data);
2155 file_data_add_ci(sfd, type, NULL, NULL);
2162 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
2166 if (fd->parent) fd = fd->parent;
2168 if (!fd->change || fd->change->type != type) return FALSE;
2170 work = fd->sidecar_files;
2173 FileData *sfd = static_cast<FileData *>(work->data);
2175 if (!sfd->change || sfd->change->type != type) return FALSE;
2183 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
2185 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
2186 file_data_sc_update_ci_copy(fd, dest_path);
2190 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
2192 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
2193 file_data_sc_update_ci_move(fd, dest_path);
2197 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
2199 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
2200 file_data_sc_update_ci_rename(fd, dest_path);
2204 gboolean file_data_sc_add_ci_delete(FileData *fd)
2206 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
2209 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
2211 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
2212 file_data_sc_update_ci_unspecified(fd, dest_path);
2216 gboolean file_data_add_ci_write_metadata(FileData *fd)
2218 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
2221 void file_data_sc_free_ci(FileData *fd)
2225 if (fd->parent) fd = fd->parent;
2227 file_data_free_ci(fd);
2229 work = fd->sidecar_files;
2232 FileData *sfd = static_cast<FileData *>(work->data);
2234 file_data_free_ci(sfd);
2239 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2242 gboolean ret = TRUE;
2247 FileData *fd = static_cast<FileData *>(work->data);
2249 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2256 static void file_data_sc_revert_ci_list(GList *fd_list)
2263 FileData *fd = static_cast<FileData *>(work->data);
2265 file_data_sc_free_ci(fd);
2270 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2277 FileData *fd = static_cast<FileData *>(work->data);
2279 if (!func(fd, dest))
2281 file_data_sc_revert_ci_list(work->prev);
2290 gboolean file_data_sc_add_ci_copy_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_copy);
2295 gboolean file_data_sc_add_ci_move_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_move);
2300 gboolean file_data_sc_add_ci_rename_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_rename);
2305 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2307 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2310 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2313 gboolean ret = TRUE;
2318 FileData *fd = static_cast<FileData *>(work->data);
2320 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2327 void file_data_free_ci_list(GList *fd_list)
2334 FileData *fd = static_cast<FileData *>(work->data);
2336 file_data_free_ci(fd);
2341 void file_data_sc_free_ci_list(GList *fd_list)
2348 FileData *fd = static_cast<FileData *>(work->data);
2350 file_data_sc_free_ci(fd);
2356 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2357 * fails if fd->change does not exist or the change type does not match
2360 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2362 FileDataChangeType type = fd->change->type;
2364 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2368 if (!file_data_planned_change_hash)
2369 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2371 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2373 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2374 g_hash_table_remove(file_data_planned_change_hash, old_path);
2375 file_data_unref(fd);
2378 ofd = static_cast<FileData *>(g_hash_table_lookup(file_data_planned_change_hash, new_path));
2383 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2384 g_hash_table_remove(file_data_planned_change_hash, new_path);
2385 file_data_unref(ofd);
2388 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2390 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2395 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2397 gchar *old_path = fd->change->dest;
2399 fd->change->dest = g_strdup(dest_path);
2400 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2404 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2406 const gchar *extension = registered_extension_from_path(fd->change->source);
2407 gchar *base = remove_extension_from_path(dest_path);
2408 gchar *old_path = fd->change->dest;
2410 fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2411 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2417 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2420 gchar *dest_path_full = NULL;
2422 if (fd->parent) fd = fd->parent;
2426 dest_path = fd->path;
2428 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2430 gchar *dir = remove_level_from_path(fd->path);
2432 dest_path_full = g_build_filename(dir, dest_path, NULL);
2434 dest_path = dest_path_full;
2436 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2438 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2439 dest_path = dest_path_full;
2442 file_data_update_ci_dest(fd, dest_path);
2444 work = fd->sidecar_files;
2447 FileData *sfd = static_cast<FileData *>(work->data);
2449 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2453 g_free(dest_path_full);
2456 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2458 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2459 file_data_sc_update_ci(fd, dest_path);
2463 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2465 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2468 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2470 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2473 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2475 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2478 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2480 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2483 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2485 gboolean (*func)(FileData *, const gchar *))
2488 gboolean ret = TRUE;
2493 FileData *fd = static_cast<FileData *>(work->data);
2495 if (!func(fd, dest)) ret = FALSE;
2502 gboolean file_data_sc_update_ci_move_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_move);
2507 gboolean file_data_sc_update_ci_copy_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_copy);
2512 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2514 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2519 * verify source and dest paths - dest image exists, etc.
2520 * it should detect all possible problems with the planned operation
2523 gint file_data_verify_ci(FileData *fd, GList *list)
2525 gint ret = CHANGE_OK;
2528 FileData *fd1 = NULL;
2532 DEBUG_1("Change checked: no change info: %s", fd->path);
2536 if (!isname(fd->path))
2538 /* this probably should not happen */
2539 ret |= CHANGE_NO_SRC;
2540 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2544 dir = remove_level_from_path(fd->path);
2546 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2547 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2548 fd->change->type != FILEDATA_CHANGE_RENAME &&
2549 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2552 ret |= CHANGE_WARN_UNSAVED_META;
2553 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2556 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2557 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2558 !access_file(fd->path, R_OK))
2560 ret |= CHANGE_NO_READ_PERM;
2561 DEBUG_1("Change checked: no read permission: %s", fd->path);
2563 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2564 !access_file(dir, W_OK))
2566 ret |= CHANGE_NO_WRITE_PERM_DIR;
2567 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2569 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2570 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2571 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2572 !access_file(fd->path, W_OK))
2574 ret |= CHANGE_WARN_NO_WRITE_PERM;
2575 DEBUG_1("Change checked: no write permission: %s", fd->path);
2577 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2578 - that means that there are no hard errors and warnings can be disabled
2579 - the destination is determined during the check
2581 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2583 /* determine destination file */
2584 gboolean have_dest = FALSE;
2585 gchar *dest_dir = NULL;
2587 if (options->metadata.save_in_image_file)
2589 if (file_data_can_write_directly(fd))
2591 /* we can write the file directly */
2592 if (access_file(fd->path, W_OK))
2598 if (options->metadata.warn_on_write_problems)
2600 ret |= CHANGE_WARN_NO_WRITE_PERM;
2601 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2605 else if (file_data_can_write_sidecar(fd))
2607 /* we can write sidecar */
2608 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2609 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2611 file_data_update_ci_dest(fd, sidecar);
2616 if (options->metadata.warn_on_write_problems)
2618 ret |= CHANGE_WARN_NO_WRITE_PERM;
2619 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2628 /* write private metadata file under ~/.geeqie */
2630 /* If an existing metadata file exists, we will try writing to
2631 * it's location regardless of the user's preference.
2633 gchar *metadata_path = NULL;
2635 /* but ignore XMP if we are not able to write it */
2636 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2638 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2640 if (metadata_path && !access_file(metadata_path, W_OK))
2642 g_free(metadata_path);
2643 metadata_path = NULL;
2650 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2651 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2653 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2655 metadata_path = g_build_filename(dest_dir, filename, NULL);
2659 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2661 file_data_update_ci_dest(fd, metadata_path);
2666 ret |= CHANGE_NO_WRITE_PERM_DEST;
2667 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2669 g_free(metadata_path);
2674 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2679 same = (strcmp(fd->path, fd->change->dest) == 0);
2683 const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2684 if (!dest_ext) dest_ext = "";
2685 if (!options->file_filter.disable_file_extension_checks)
2687 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2689 ret |= CHANGE_WARN_CHANGED_EXT;
2690 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2696 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /** @FIXME this is now needed for running editors */
2698 ret |= CHANGE_WARN_SAME;
2699 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2703 dest_dir = remove_level_from_path(fd->change->dest);
2705 if (!isdir(dest_dir))
2707 ret |= CHANGE_NO_DEST_DIR;
2708 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2710 else if (!access_file(dest_dir, W_OK))
2712 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2713 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2717 if (isfile(fd->change->dest))
2719 if (!access_file(fd->change->dest, W_OK))
2721 ret |= CHANGE_NO_WRITE_PERM_DEST;
2722 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2726 ret |= CHANGE_WARN_DEST_EXISTS;
2727 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2730 else if (isdir(fd->change->dest))
2732 ret |= CHANGE_DEST_EXISTS;
2733 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2740 /* During a rename operation, check if another planned destination file has
2743 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2744 fd->change->type == FILEDATA_CHANGE_COPY ||
2745 fd->change->type == FILEDATA_CHANGE_MOVE)
2750 fd1 = static_cast<FileData *>(work->data);
2752 if (fd1 != NULL && fd != fd1 )
2754 if (!strcmp(fd->change->dest, fd1->change->dest))
2756 ret |= CHANGE_DUPLICATE_DEST;
2762 fd->change->error = ret;
2763 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2770 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2775 ret = file_data_verify_ci(fd, list);
2777 work = fd->sidecar_files;
2780 FileData *sfd = static_cast<FileData *>(work->data);
2782 ret |= file_data_verify_ci(sfd, list);
2789 gchar *file_data_get_error_string(gint error)
2791 GString *result = g_string_new("");
2793 if (error & CHANGE_NO_SRC)
2795 if (result->len > 0) g_string_append(result, ", ");
2796 g_string_append(result, _("file or directory does not exist"));
2799 if (error & CHANGE_DEST_EXISTS)
2801 if (result->len > 0) g_string_append(result, ", ");
2802 g_string_append(result, _("destination already exists"));
2805 if (error & CHANGE_NO_WRITE_PERM_DEST)
2807 if (result->len > 0) g_string_append(result, ", ");
2808 g_string_append(result, _("destination can't be overwritten"));
2811 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2813 if (result->len > 0) g_string_append(result, ", ");
2814 g_string_append(result, _("destination directory is not writable"));
2817 if (error & CHANGE_NO_DEST_DIR)
2819 if (result->len > 0) g_string_append(result, ", ");
2820 g_string_append(result, _("destination directory does not exist"));
2823 if (error & CHANGE_NO_WRITE_PERM_DIR)
2825 if (result->len > 0) g_string_append(result, ", ");
2826 g_string_append(result, _("source directory is not writable"));
2829 if (error & CHANGE_NO_READ_PERM)
2831 if (result->len > 0) g_string_append(result, ", ");
2832 g_string_append(result, _("no read permission"));
2835 if (error & CHANGE_WARN_NO_WRITE_PERM)
2837 if (result->len > 0) g_string_append(result, ", ");
2838 g_string_append(result, _("file is readonly"));
2841 if (error & CHANGE_WARN_DEST_EXISTS)
2843 if (result->len > 0) g_string_append(result, ", ");
2844 g_string_append(result, _("destination already exists and will be overwritten"));
2847 if (error & CHANGE_WARN_SAME)
2849 if (result->len > 0) g_string_append(result, ", ");
2850 g_string_append(result, _("source and destination are the same"));
2853 if (error & CHANGE_WARN_CHANGED_EXT)
2855 if (result->len > 0) g_string_append(result, ", ");
2856 g_string_append(result, _("source and destination have different extension"));
2859 if (error & CHANGE_WARN_UNSAVED_META)
2861 if (result->len > 0) g_string_append(result, ", ");
2862 g_string_append(result, _("there are unsaved metadata changes for the file"));
2865 if (error & CHANGE_DUPLICATE_DEST)
2867 if (result->len > 0) g_string_append(result, ", ");
2868 g_string_append(result, _("another destination file has the same filename"));
2871 return g_string_free(result, FALSE);
2874 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2877 gint all_errors = 0;
2878 gint common_errors = ~0;
2883 if (!list) return 0;
2885 num = g_list_length(list);
2886 errors = g_new(int, num);
2894 fd = static_cast<FileData *>(work->data);
2897 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2898 all_errors |= error;
2899 common_errors &= error;
2906 if (desc && all_errors)
2909 GString *result = g_string_new("");
2913 gchar *str = file_data_get_error_string(common_errors);
2914 g_string_append(result, str);
2915 g_string_append(result, "\n");
2926 fd = static_cast<FileData *>(work->data);
2929 error = errors[i] & ~common_errors;
2933 gchar *str = file_data_get_error_string(error);
2934 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2939 *desc = g_string_free(result, FALSE);
2948 * perform the change described by FileFataChangeInfo
2949 * it is used for internal operations,
2950 * this function actually operates with files on the filesystem
2951 * it should implement safe delete
2954 static gboolean file_data_perform_move(FileData *fd)
2956 g_assert(!strcmp(fd->change->source, fd->path));
2957 return move_file(fd->change->source, fd->change->dest);
2960 static gboolean file_data_perform_copy(FileData *fd)
2962 g_assert(!strcmp(fd->change->source, fd->path));
2963 return copy_file(fd->change->source, fd->change->dest);
2966 static gboolean file_data_perform_delete(FileData *fd)
2968 if (isdir(fd->path) && !islink(fd->path))
2969 return rmdir_utf8(fd->path);
2971 if (options->file_ops.safe_delete_enable)
2972 return file_util_safe_unlink(fd->path);
2974 return unlink_file(fd->path);
2977 gboolean file_data_perform_ci(FileData *fd)
2979 FileDataChangeType type = fd->change->type;
2983 case FILEDATA_CHANGE_MOVE:
2984 return file_data_perform_move(fd);
2985 case FILEDATA_CHANGE_COPY:
2986 return file_data_perform_copy(fd);
2987 case FILEDATA_CHANGE_RENAME:
2988 return file_data_perform_move(fd); /* the same as move */
2989 case FILEDATA_CHANGE_DELETE:
2990 return file_data_perform_delete(fd);
2991 case FILEDATA_CHANGE_WRITE_METADATA:
2992 return metadata_write_perform(fd);
2993 case FILEDATA_CHANGE_UNSPECIFIED:
2994 /* nothing to do here */
3002 gboolean file_data_sc_perform_ci(FileData *fd)
3005 gboolean ret = TRUE;
3006 FileDataChangeType type = fd->change->type;
3008 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3010 work = fd->sidecar_files;
3013 FileData *sfd = static_cast<FileData *>(work->data);
3015 if (!file_data_perform_ci(sfd)) ret = FALSE;
3019 if (!file_data_perform_ci(fd)) ret = FALSE;
3025 * updates FileData structure according to FileDataChangeInfo
3028 gboolean file_data_apply_ci(FileData *fd)
3030 FileDataChangeType type = fd->change->type;
3032 /** @FIXME delete ?*/
3033 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
3035 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
3036 file_data_planned_change_remove(fd);
3038 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
3040 /* this change overwrites another file which is already known to other modules
3041 renaming fd would create duplicate FileData structure
3042 the best thing we can do is nothing
3044 /** @FIXME maybe we could copy stuff like marks
3046 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
3050 file_data_set_path(fd, fd->change->dest);
3053 file_data_increment_version(fd);
3054 file_data_send_notification(fd, NOTIFY_CHANGE);
3059 gboolean file_data_sc_apply_ci(FileData *fd)
3062 FileDataChangeType type = fd->change->type;
3064 if (!file_data_sc_check_ci(fd, type)) return FALSE;
3066 work = fd->sidecar_files;
3069 FileData *sfd = static_cast<FileData *>(work->data);
3071 file_data_apply_ci(sfd);
3075 file_data_apply_ci(fd);
3080 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
3083 if (fd->parent) fd = fd->parent;
3084 if (!g_list_find(list, fd)) return FALSE;
3086 work = fd->sidecar_files;
3089 if (!g_list_find(list, work->data)) return FALSE;
3095 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
3100 /* change partial groups to independent files */
3105 FileData *fd = static_cast<FileData *>(work->data);
3108 if (!file_data_list_contains_whole_group(list, fd))
3110 file_data_disable_grouping(fd, TRUE);
3113 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
3119 /* remove sidecars from the list,
3120 they can be still accessed via main_fd->sidecar_files */
3124 FileData *fd = static_cast<FileData *>(work->data);
3128 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
3130 out = g_list_prepend(out, file_data_ref(fd));
3134 filelist_free(list);
3135 out = g_list_reverse(out);
3145 * notify other modules about the change described by FileDataChangeInfo
3148 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks */
3149 /** @FIXME do we need the ignore_list? It looks like a workaround for ineffective
3150 implementation in view-file-list.cc */
3153 typedef struct _NotifyIdleData NotifyIdleData;
3155 struct _NotifyIdleData {
3161 typedef struct _NotifyData NotifyData;
3163 struct _NotifyData {
3164 FileDataNotifyFunc func;
3166 NotifyPriority priority;
3169 static GList *notify_func_list = NULL;
3171 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
3173 NotifyData *nda = (NotifyData *)a;
3174 NotifyData *ndb = (NotifyData *)b;
3176 if (nda->priority < ndb->priority) return -1;
3177 if (nda->priority > ndb->priority) return 1;
3181 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
3184 GList *work = notify_func_list;
3188 NotifyData *nd = (NotifyData *)work->data;
3190 if (nd->func == func && nd->data == data)
3192 g_warning("Notify func already registered");
3198 nd = g_new(NotifyData, 1);
3201 nd->priority = priority;
3203 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
3204 DEBUG_2("Notify func registered: %p", (void *)nd);
3209 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
3211 GList *work = notify_func_list;
3215 NotifyData *nd = (NotifyData *)work->data;
3217 if (nd->func == func && nd->data == data)
3219 notify_func_list = g_list_delete_link(notify_func_list, work);
3221 DEBUG_2("Notify func unregistered: %p", (void *)nd);
3227 g_warning("Notify func not found");
3232 //gboolean file_data_send_notification_idle_cb(gpointer data)
3234 //NotifyIdleData *nid = (NotifyIdleData *)data;
3235 //GList *work = notify_func_list;
3239 //NotifyData *nd = (NotifyData *)work->data;
3241 //nd->func(nid->fd, nid->type, nd->data);
3242 //work = work->next;
3244 //file_data_unref(nid->fd);
3249 void file_data_send_notification(FileData *fd, NotifyType type)
3251 GList *work = notify_func_list;
3255 NotifyData *nd = (NotifyData *)work->data;
3257 nd->func(fd, type, nd->data);
3261 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3262 nid->fd = file_data_ref(fd);
3264 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3268 static GHashTable *file_data_monitor_pool = NULL;
3269 static guint realtime_monitor_id = 0; /* event source id */
3271 static void realtime_monitor_check_cb(gpointer key, gpointer UNUSED(value), gpointer UNUSED(data))
3273 FileData *fd = static_cast<FileData *>(key);
3275 file_data_check_changed_files(fd);
3277 DEBUG_1("monitor %s", fd->path);
3280 static gboolean realtime_monitor_cb(gpointer UNUSED(data))
3282 if (!options->update_on_time_change) return TRUE;
3283 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
3287 gboolean file_data_register_real_time_monitor(FileData *fd)
3293 if (!file_data_monitor_pool)
3294 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3296 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3298 DEBUG_1("Register realtime %d %s", count, fd->path);
3301 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3303 if (!realtime_monitor_id)
3305 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
3311 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3315 g_assert(file_data_monitor_pool);
3317 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3319 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3321 g_assert(count > 0);
3326 g_hash_table_remove(file_data_monitor_pool, fd);
3328 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3330 file_data_unref(fd);
3332 if (g_hash_table_size(file_data_monitor_pool) == 0)
3334 g_source_remove(realtime_monitor_id);
3335 realtime_monitor_id = 0;
3343 *-----------------------------------------------------------------------------
3344 * Saving marks list, clearing marks
3345 * Uses file_data_pool
3346 *-----------------------------------------------------------------------------
3349 static void marks_get_files(gpointer key, gpointer value, gpointer userdata)
3351 gchar *file_name = static_cast<gchar *>(key);
3352 GString *result = static_cast<GString *>(userdata);
3355 if (isfile(file_name))
3357 fd = static_cast<FileData *>(value);
3358 if (fd && fd->marks > 0)
3360 g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks);
3365 gboolean marks_list_load(const gchar *path)
3373 pathl = path_from_utf8(path);
3374 f = fopen(pathl, "r");
3376 if (!f) return FALSE;
3378 /* first line must start with Marks comment */
3379 if (!fgets(s_buf, sizeof(s_buf), f) ||
3380 strncmp(s_buf, "#Marks", 6) != 0)
3386 while (fgets(s_buf, sizeof(s_buf), f))
3388 if (s_buf[0]=='#') continue;
3389 file_path = strtok(s_buf, ",");
3390 marks_value = strtok(NULL, ",");
3391 if (isfile(file_path))
3393 FileData *fd = file_data_new_no_grouping(file_path);
3398 gint mark_no = 1 << n;
3399 if (atoi(marks_value) & mark_no)
3401 file_data_set_mark(fd, n , 1);
3412 gboolean marks_list_save(gchar *path, gboolean save)
3414 SecureSaveInfo *ssi;
3416 GString *marks = g_string_new("");
3418 pathl = path_from_utf8(path);
3419 ssi = secure_open(pathl);
3423 log_printf(_("Error: Unable to write marks lists to: %s\n"), path);
3427 secure_fprintf(ssi, "#Marks lists\n");
3431 g_hash_table_foreach(file_data_pool, marks_get_files, marks);
3433 secure_fprintf(ssi, "%s", marks->str);
3434 g_string_free(marks, FALSE);
3436 secure_fprintf(ssi, "#end\n");
3437 return (secure_close(ssi) == 0);
3440 static void marks_clear(gpointer key, gpointer value, gpointer UNUSED(userdata))
3442 gchar *file_name = static_cast<gchar *>(key);
3447 if (isfile(file_name))
3449 fd = static_cast<FileData *>(value);
3450 if (fd && fd->marks > 0)
3456 if (fd->marks & mark_no)
3458 file_data_set_mark(fd, n , 0);
3466 void marks_clear_all()
3468 g_hash_table_foreach(file_data_pool, marks_clear, NULL);
3471 void file_data_set_page_num(FileData *fd, gint page_num)
3473 if (fd->page_total > 1 && page_num < 0)
3475 fd->page_num = fd->page_total - 1;
3477 else if (fd->page_total > 1 && page_num <= fd->page_total)
3479 fd->page_num = page_num - 1;
3485 file_data_send_notification(fd, NOTIFY_REREAD);
3488 void file_data_inc_page_num(FileData *fd)
3490 if (fd->page_total > 0 && fd->page_num < fd->page_total - 1)
3492 fd->page_num = fd->page_num + 1;
3494 else if (fd->page_total == 0)
3496 fd->page_num = fd->page_num + 1;
3498 file_data_send_notification(fd, NOTIFY_REREAD);
3501 void file_data_dec_page_num(FileData *fd)
3503 if (fd->page_num > 0)
3505 fd->page_num = fd->page_num - 1;
3507 file_data_send_notification(fd, NOTIFY_REREAD);
3510 void file_data_set_page_total(FileData *fd, gint page_total)
3512 fd->page_total = page_total;
3515 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */