2 * Copyright (C) 2006 John Ellis
3 * Copyright (C) 2008 - 2016 The Geeqie Team
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 #include "filefilter.h"
27 #include "thumb_standard.h"
28 #include "ui_fileops.h"
31 #include "histogram.h"
32 #include "secure_save.h"
41 gint global_file_data_count = 0;
44 static GHashTable *file_data_pool = NULL;
45 static GHashTable *file_data_planned_change_hash = NULL;
47 static gint sidecar_file_priority(const gchar *extension);
48 static void file_data_check_sidecars(const GList *basename_list);
49 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
52 static SortType filelist_sort_method = SORT_NONE;
53 static gboolean filelist_sort_ascend = TRUE;
56 *-----------------------------------------------------------------------------
57 * text conversion utils
58 *-----------------------------------------------------------------------------
61 gchar *text_from_size(gint64 size)
67 /* what I would like to use is printf("%'d", size)
68 * BUT: not supported on every libc :(
72 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
73 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
77 a = g_strdup_printf("%d", (guint)size);
83 b = g_new(gchar, l + n + 1);
108 gchar *text_from_size_abrev(gint64 size)
110 if (size < (gint64)1024)
112 return g_strdup_printf(_("%d bytes"), (gint)size);
114 if (size < (gint64)1048576)
116 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
118 if (size < (gint64)1073741824)
120 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
123 /* to avoid overflowing the gdouble, do division in two steps */
125 return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
128 /* note: returned string is valid until next call to text_from_time() */
129 const gchar *text_from_time(time_t t)
131 static gchar *ret = NULL;
135 GError *error = NULL;
137 btime = localtime(&t);
139 /* the %x warning about 2 digit years is not an error */
140 buflen = strftime(buf, sizeof(buf), "%x %X", btime);
141 if (buflen < 1) return "";
144 ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
147 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
156 *-----------------------------------------------------------------------------
157 * changed files detection and notification
158 *-----------------------------------------------------------------------------
161 void file_data_increment_version(FileData *fd)
167 fd->parent->version++;
168 fd->parent->valid_marks = 0;
172 static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st)
174 if (fd->size != st->st_size ||
175 fd->date != st->st_mtime)
177 fd->size = st->st_size;
178 fd->date = st->st_mtime;
179 fd->cdate = st->st_ctime;
180 fd->mode = st->st_mode;
181 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
182 fd->thumb_pixbuf = NULL;
183 file_data_increment_version(fd);
184 file_data_send_notification(fd, NOTIFY_REREAD);
190 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
192 gboolean ret = FALSE;
195 ret = file_data_check_changed_single_file(fd, st);
197 work = fd->sidecar_files;
200 FileData *sfd = work->data;
204 if (!stat_utf8(sfd->path, &st))
209 file_data_disconnect_sidecar_file(fd, sfd);
211 file_data_increment_version(sfd);
212 file_data_send_notification(sfd, NOTIFY_REREAD);
213 file_data_unref(sfd);
217 ret |= file_data_check_changed_files_recursive(sfd, &st);
223 gboolean file_data_check_changed_files(FileData *fd)
225 gboolean ret = FALSE;
228 if (fd->parent) fd = fd->parent;
230 if (!stat_utf8(fd->path, &st))
234 FileData *sfd = NULL;
236 /* parent is missing, we have to rebuild whole group */
241 /* file_data_disconnect_sidecar_file might delete the file,
242 we have to keep the reference to prevent this */
243 sidecars = filelist_copy(fd->sidecar_files);
251 file_data_disconnect_sidecar_file(fd, sfd);
253 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
254 /* now we can release the sidecars */
255 filelist_free(sidecars);
256 file_data_increment_version(fd);
257 file_data_send_notification(fd, NOTIFY_REREAD);
262 ret |= file_data_check_changed_files_recursive(fd, &st);
269 *-----------------------------------------------------------------------------
270 * file name, extension, sorting, ...
271 *-----------------------------------------------------------------------------
274 static void file_data_set_collate_keys(FileData *fd)
276 gchar *caseless_name;
279 valid_name = g_filename_display_name(fd->name);
280 caseless_name = g_utf8_casefold(valid_name, -1);
282 g_free(fd->collate_key_name);
283 g_free(fd->collate_key_name_nocase);
285 #if GTK_CHECK_VERSION(2, 8, 0)
286 if (options->file_sort.natural)
288 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
289 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
293 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
294 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
297 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
298 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
302 g_free(caseless_name);
305 static void file_data_set_path(FileData *fd, const gchar *path)
307 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
308 g_assert(file_data_pool);
312 if (fd->original_path)
314 g_hash_table_remove(file_data_pool, fd->original_path);
315 g_free(fd->original_path);
318 g_assert(!g_hash_table_lookup(file_data_pool, path));
320 fd->original_path = g_strdup(path);
321 g_hash_table_insert(file_data_pool, fd->original_path, fd);
323 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
325 fd->path = g_strdup(path);
327 fd->extension = fd->name + 1;
328 file_data_set_collate_keys(fd);
332 fd->path = g_strdup(path);
333 fd->name = filename_from_path(fd->path);
335 if (strcmp(fd->name, "..") == 0)
337 gchar *dir = remove_level_from_path(path);
339 fd->path = remove_level_from_path(dir);
342 fd->extension = fd->name + 2;
343 file_data_set_collate_keys(fd);
346 else if (strcmp(fd->name, ".") == 0)
349 fd->path = remove_level_from_path(path);
351 fd->extension = fd->name + 1;
352 file_data_set_collate_keys(fd);
356 fd->extension = registered_extension_from_path(fd->path);
357 if (fd->extension == NULL)
359 fd->extension = fd->name + strlen(fd->name);
362 fd->sidecar_priority = sidecar_file_priority(fd->extension);
363 file_data_set_collate_keys(fd);
367 *-----------------------------------------------------------------------------
368 * create or reuse Filedata
369 *-----------------------------------------------------------------------------
372 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
378 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
380 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
383 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
385 fd = g_hash_table_lookup(file_data_pool, path_utf8);
391 if (!fd && file_data_planned_change_hash)
393 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
396 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
397 if (!isfile(fd->path))
400 file_data_apply_ci(fd);
413 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
416 changed = file_data_check_changed_single_file(fd, st);
418 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
423 fd = g_new0(FileData, 1);
424 #ifdef DEBUG_FILEDATA
425 global_file_data_count++;
426 DEBUG_2("file data count++: %d", global_file_data_count);
429 fd->size = st->st_size;
430 fd->date = st->st_mtime;
431 fd->cdate = st->st_ctime;
432 fd->mode = st->st_mode;
434 fd->magick = FD_MAGICK;
436 fd->rating = STAR_RATING_NOT_READ;
437 fd->format_class = filter_file_get_class(path_utf8);
439 user = getpwuid(st->st_uid);
442 fd->owner = g_strdup_printf("%u", st->st_uid);
446 fd->owner = g_strdup(user->pw_name);
449 group = getgrgid(st->st_gid);
452 fd->group = g_strdup_printf("%u", st->st_gid);
456 fd->group = g_strdup(group->gr_name);
459 fd->sym_link = get_symbolic_link(path_utf8);
461 if (disable_sidecars) fd->disable_grouping = TRUE;
463 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
468 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
470 gchar *path_utf8 = path_to_utf8(path);
471 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
477 FileData *file_data_new_simple(const gchar *path_utf8)
482 if (!stat_utf8(path_utf8, &st))
488 fd = g_hash_table_lookup(file_data_pool, path_utf8);
489 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
498 void read_exif_time_data(FileData *file)
500 if (file->exifdate > 0)
502 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
513 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
514 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), file, file->path);
519 uint year, month, day, hour, min, sec;
521 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
522 time_str.tm_year = year - 1900;
523 time_str.tm_mon = month - 1;
524 time_str.tm_mday = day;
525 time_str.tm_hour = hour;
526 time_str.tm_min = min;
527 time_str.tm_sec = sec;
528 time_str.tm_isdst = 0;
530 file->exifdate = mktime(&time_str);
536 void read_exif_time_digitized_data(FileData *file)
538 if (file->exifdate_digitized > 0)
540 DEBUG_1("%s set_exif_time_digitized_data: Already exists for %s", get_exec_time(), file->path);
551 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeDigitized");
552 DEBUG_2("%s set_exif_time_digitized_data: reading %p %s", get_exec_time(), file, file->path);
557 uint year, month, day, hour, min, sec;
559 sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
560 time_str.tm_year = year - 1900;
561 time_str.tm_mon = month - 1;
562 time_str.tm_mday = day;
563 time_str.tm_hour = hour;
564 time_str.tm_min = min;
565 time_str.tm_sec = sec;
566 time_str.tm_isdst = 0;
568 file->exifdate_digitized = mktime(&time_str);
574 void read_rating_data(FileData *file)
578 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
581 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);
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);
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);
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, 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", fd, fd->ref, fd->path, file, line);
684 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
689 static void file_data_free(FileData *fd)
691 g_assert(fd->magick == FD_MAGICK);
692 g_assert(fd->ref == 0);
693 g_assert(!fd->locked);
695 #ifdef DEBUG_FILEDATA
696 global_file_data_count--;
697 DEBUG_2("file data count--: %d", global_file_data_count);
700 metadata_cache_free(fd);
701 g_hash_table_remove(file_data_pool, fd->original_path);
704 g_free(fd->original_path);
705 g_free(fd->collate_key_name);
706 g_free(fd->collate_key_name_nocase);
707 g_free(fd->extended_extension);
708 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
709 histmap_free(fd->histmap);
712 g_free(fd->sym_link);
713 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
715 file_data_change_info_free(NULL, fd);
720 * \brief Checks if the FileData is referenced
722 * Checks the refcount and whether the FileData is locked.
724 static gboolean file_data_check_has_ref(FileData *fd)
726 return fd->ref > 0 || fd->locked;
730 * \brief Consider freeing a FileData.
732 * This function will free a FileData and its children provided that neither its parent nor it has
733 * a positive refcount, and provided that neither is locked.
735 static void file_data_consider_free(FileData *fd)
738 FileData *parent = fd->parent ? fd->parent : fd;
740 g_assert(fd->magick == FD_MAGICK);
741 if (file_data_check_has_ref(fd)) return;
742 if (file_data_check_has_ref(parent)) return;
744 work = parent->sidecar_files;
747 FileData *sfd = work->data;
748 if (file_data_check_has_ref(sfd)) return;
752 /* Neither the parent nor the siblings are referenced, so we can free everything */
753 DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
754 fd->path, fd->parent ? parent->path : "-");
756 work = parent->sidecar_files;
759 FileData *sfd = work->data;
764 g_list_free(parent->sidecar_files);
765 parent->sidecar_files = NULL;
767 file_data_free(parent);
770 #ifdef DEBUG_FILEDATA
771 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
773 void file_data_unref(FileData *fd)
776 if (fd == NULL) return;
777 if (fd->magick != FD_MAGICK)
778 #ifdef DEBUG_FILEDATA
779 log_printf("Error: fd magick mismatch @ %s:%d fd=%p", file, line, fd);
781 log_printf("Error: fd magick mismatch fd=%p", fd);
783 g_assert(fd->magick == FD_MAGICK);
786 #ifdef DEBUG_FILEDATA
787 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", fd, fd->ref, fd->locked, fd->path,
790 DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
793 // Free FileData if it's no longer ref'd
794 file_data_consider_free(fd);
798 * \brief Lock the FileData in memory.
800 * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
801 * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
802 * even if the code would continue to function properly even if the FileData were freed. Code that
803 * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
805 * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
806 * file_data_lock, a single call to file_data_unlock will unlock the FileData.
808 void file_data_lock(FileData *fd)
810 if (fd == NULL) return;
811 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", fd);
813 g_assert(fd->magick == FD_MAGICK);
816 DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
820 * \brief Reset the maintain-FileData-in-memory lock
822 * This again allows the FileData to be freed when its refcount drops to zero. Automatically frees
823 * the FileData if its refcount is already zero (which will happen if the lock is the only thing
824 * keeping it from being freed.
826 void file_data_unlock(FileData *fd)
828 if (fd == NULL) return;
829 if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", fd);
831 g_assert(fd->magick == FD_MAGICK);
834 // Free FileData if it's no longer ref'd
835 file_data_consider_free(fd);
839 * \brief Lock all of the FileDatas in the provided list
841 * \see file_data_lock(FileData)
843 void file_data_lock_list(GList *list)
850 FileData *fd = work->data;
857 * \brief Unlock all of the FileDatas in the provided list
859 * \see file_data_unlock(FileData)
861 void file_data_unlock_list(GList *list)
868 FileData *fd = work->data;
870 file_data_unlock(fd);
875 *-----------------------------------------------------------------------------
876 * sidecar file info struct
877 *-----------------------------------------------------------------------------
880 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
882 const FileData *fda = a;
883 const FileData *fdb = b;
885 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
886 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
888 return strcmp(fdb->extension, fda->extension);
892 static gint sidecar_file_priority(const gchar *extension)
897 if (extension == NULL)
900 work = sidecar_ext_get_list();
903 gchar *ext = work->data;
906 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
912 static void file_data_check_sidecars(const GList *basename_list)
914 /* basename_list contains the new group - first is the parent, then sorted sidecars */
915 /* all files in the list have ref count > 0 */
918 GList *s_work, *new_sidecars;
921 if (!basename_list) return;
924 DEBUG_2("basename start");
925 work = basename_list;
928 FileData *fd = work->data;
930 g_assert(fd->magick == FD_MAGICK);
931 DEBUG_2("basename: %p %s", fd, fd->name);
934 g_assert(fd->parent->magick == FD_MAGICK);
935 DEBUG_2(" parent: %p", fd->parent);
937 s_work = fd->sidecar_files;
940 FileData *sfd = s_work->data;
941 s_work = s_work->next;
942 g_assert(sfd->magick == FD_MAGICK);
943 DEBUG_2(" sidecar: %p %s", sfd, sfd->name);
946 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
949 parent_fd = basename_list->data;
951 /* check if the second and next entries of basename_list are already connected
952 as sidecars of the first entry (parent_fd) */
953 work = basename_list->next;
954 s_work = parent_fd->sidecar_files;
956 while (work && s_work)
958 if (work->data != s_work->data) break;
960 s_work = s_work->next;
963 if (!work && !s_work)
965 DEBUG_2("basename no change");
966 return; /* no change in grouping */
969 /* we have to regroup it */
971 /* first, disconnect everything and send notification*/
973 work = basename_list;
976 FileData *fd = work->data;
978 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
982 FileData *old_parent = fd->parent;
983 g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
984 file_data_ref(old_parent);
985 file_data_disconnect_sidecar_file(old_parent, fd);
986 file_data_send_notification(old_parent, NOTIFY_REREAD);
987 file_data_unref(old_parent);
990 while (fd->sidecar_files)
992 FileData *sfd = fd->sidecar_files->data;
993 g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
995 file_data_disconnect_sidecar_file(fd, sfd);
996 file_data_send_notification(sfd, NOTIFY_REREAD);
997 file_data_unref(sfd);
999 file_data_send_notification(fd, NOTIFY_GROUPING);
1001 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
1004 /* now we can form the new group */
1005 work = basename_list->next;
1006 new_sidecars = NULL;
1009 FileData *sfd = work->data;
1010 g_assert(sfd->magick == FD_MAGICK);
1011 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
1012 sfd->parent = parent_fd;
1013 new_sidecars = g_list_prepend(new_sidecars, sfd);
1016 g_assert(parent_fd->sidecar_files == NULL);
1017 parent_fd->sidecar_files = g_list_reverse(new_sidecars);
1018 DEBUG_1("basename group changed for %s", parent_fd->path);
1022 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
1024 g_assert(target->magick == FD_MAGICK);
1025 g_assert(sfd->magick == FD_MAGICK);
1026 g_assert(g_list_find(target->sidecar_files, sfd));
1028 file_data_ref(target);
1031 g_assert(sfd->parent == target);
1033 file_data_increment_version(sfd); /* increments both sfd and target */
1035 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
1037 g_free(sfd->extended_extension);
1038 sfd->extended_extension = NULL;
1040 file_data_unref(target);
1041 file_data_unref(sfd);
1044 /* disables / enables grouping for particular file, sends UPDATE notification */
1045 void file_data_disable_grouping(FileData *fd, gboolean disable)
1047 if (!fd->disable_grouping == !disable) return;
1049 fd->disable_grouping = !!disable;
1055 FileData *parent = file_data_ref(fd->parent);
1056 file_data_disconnect_sidecar_file(parent, fd);
1057 file_data_send_notification(parent, NOTIFY_GROUPING);
1058 file_data_unref(parent);
1060 else if (fd->sidecar_files)
1062 GList *sidecar_files = filelist_copy(fd->sidecar_files);
1063 GList *work = sidecar_files;
1066 FileData *sfd = work->data;
1068 file_data_disconnect_sidecar_file(fd, sfd);
1069 file_data_send_notification(sfd, NOTIFY_GROUPING);
1071 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
1072 filelist_free(sidecar_files);
1076 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
1081 file_data_increment_version(fd);
1082 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
1084 file_data_send_notification(fd, NOTIFY_GROUPING);
1087 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
1094 FileData *fd = work->data;
1096 file_data_disable_grouping(fd, disable);
1104 *-----------------------------------------------------------------------------
1106 *-----------------------------------------------------------------------------
1110 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
1113 if (!filelist_sort_ascend)
1120 switch (filelist_sort_method)
1125 if (fa->size < fb->size) return -1;
1126 if (fa->size > fb->size) return 1;
1127 /* fall back to name */
1130 if (fa->date < fb->date) return -1;
1131 if (fa->date > fb->date) return 1;
1132 /* fall back to name */
1135 if (fa->cdate < fb->cdate) return -1;
1136 if (fa->cdate > fb->cdate) return 1;
1137 /* fall back to name */
1140 if (fa->exifdate < fb->exifdate) return -1;
1141 if (fa->exifdate > fb->exifdate) return 1;
1142 /* fall back to name */
1144 case SORT_EXIFTIMEDIGITIZED:
1145 if (fa->exifdate_digitized < fb->exifdate_digitized) return -1;
1146 if (fa->exifdate_digitized > fb->exifdate_digitized) return 1;
1147 /* fall back to name */
1150 if (fa->rating < fb->rating) return -1;
1151 if (fa->rating > fb->rating) return 1;
1152 /* fall back to name */
1155 if (fa->format_class < fb->format_class) return -1;
1156 if (fa->format_class > fb->format_class) return 1;
1157 /* fall back to name */
1159 #ifdef HAVE_STRVERSCMP
1161 ret = strverscmp(fa->name, fb->name);
1162 if (ret != 0) return ret;
1169 if (options->file_sort.case_sensitive)
1170 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1172 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1174 if (ret != 0) return ret;
1176 /* do not return 0 unless the files are really the same
1177 file_data_pool ensures that original_path is unique
1179 return strcmp(fa->original_path, fb->original_path);
1182 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1184 filelist_sort_method = method;
1185 filelist_sort_ascend = ascend;
1186 return filelist_sort_compare_filedata(fa, fb);
1189 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1191 return filelist_sort_compare_filedata(a, b);
1194 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1196 filelist_sort_method = method;
1197 filelist_sort_ascend = ascend;
1198 return g_list_sort(list, cb);
1201 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1203 filelist_sort_method = method;
1204 filelist_sort_ascend = ascend;
1205 return g_list_insert_sorted(list, data, cb);
1208 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1210 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1213 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
1215 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1219 *-----------------------------------------------------------------------------
1220 * basename hash - grouping of sidecars in filelist
1221 *-----------------------------------------------------------------------------
1225 static GHashTable *file_data_basename_hash_new(void)
1227 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1230 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1233 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1235 list = g_hash_table_lookup(basename_hash, basename);
1239 DEBUG_1("TG: basename_hash not found for %s",fd->path);
1240 const gchar *parent_extension = registered_extension_from_path(basename);
1242 if (parent_extension)
1244 DEBUG_1("TG: parent extension %s",parent_extension);
1245 gchar *parent_basename = g_strndup(basename, parent_extension - basename);
1246 DEBUG_1("TG: parent basename %s",parent_basename);
1247 FileData *parent_fd = g_hash_table_lookup(file_data_pool, basename);
1250 DEBUG_1("TG: parent fd found");
1251 list = g_hash_table_lookup(basename_hash, parent_basename);
1252 if (!g_list_find(list, parent_fd))
1254 DEBUG_1("TG: parent fd doesn't fit");
1255 g_free(parent_basename);
1261 basename = parent_basename;
1262 fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
1268 if (!g_list_find(list, fd))
1270 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1271 g_hash_table_insert(basename_hash, basename, list);
1280 static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
1282 file_data_basename_hash_insert((GHashTable *)basename_hash, (FileData *)fd);
1285 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
1287 filelist_free((GList *)value);
1290 static void file_data_basename_hash_free(GHashTable *basename_hash)
1292 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1293 g_hash_table_destroy(basename_hash);
1297 *-----------------------------------------------------------------------------
1298 * handling sidecars in filelist
1299 *-----------------------------------------------------------------------------
1302 static GList *filelist_filter_out_sidecars(GList *flist)
1304 GList *work = flist;
1305 GList *flist_filtered = NULL;
1309 FileData *fd = work->data;
1312 if (fd->parent) /* remove fd's that are children */
1313 file_data_unref(fd);
1315 flist_filtered = g_list_prepend(flist_filtered, fd);
1319 return flist_filtered;
1322 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
1324 GList *basename_list = (GList *)value;
1325 file_data_check_sidecars(basename_list);
1329 static gboolean is_hidden_file(const gchar *name)
1331 if (name[0] != '.') return FALSE;
1332 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1337 *-----------------------------------------------------------------------------
1338 * the main filelist function
1339 *-----------------------------------------------------------------------------
1342 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1347 GList *dlist = NULL;
1348 GList *flist = NULL;
1349 GList *xmp_files = NULL;
1350 gint (*stat_func)(const gchar *path, struct stat *buf);
1351 GHashTable *basename_hash = NULL;
1353 g_assert(files || dirs);
1355 if (files) *files = NULL;
1356 if (dirs) *dirs = NULL;
1358 pathl = path_from_utf8(dir_path);
1359 if (!pathl) return FALSE;
1361 dp = opendir(pathl);
1368 if (files) basename_hash = file_data_basename_hash_new();
1370 if (follow_symlinks)
1375 while ((dir = readdir(dp)) != NULL)
1377 struct stat ent_sbuf;
1378 const gchar *name = dir->d_name;
1381 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1384 filepath = g_build_filename(pathl, name, NULL);
1385 if (stat_func(filepath, &ent_sbuf) >= 0)
1387 if (S_ISDIR(ent_sbuf.st_mode))
1389 /* we ignore the .thumbnails dir for cleanliness */
1391 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1392 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1393 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1394 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1396 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1401 if (files && filter_name_exists(name))
1403 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1404 flist = g_list_prepend(flist, fd);
1405 if (fd->sidecar_priority && !fd->disable_grouping)
1407 if (strcmp(fd->extension, ".xmp") != 0)
1408 file_data_basename_hash_insert(basename_hash, fd);
1410 xmp_files = g_list_append(xmp_files, fd);
1417 if (errno == EOVERFLOW)
1419 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1431 g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
1432 g_list_free(xmp_files);
1435 if (dirs) *dirs = dlist;
1439 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1441 *files = filelist_filter_out_sidecars(flist);
1443 if (basename_hash) file_data_basename_hash_free(basename_hash);
1448 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1450 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1453 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1455 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1458 FileData *file_data_new_group(const gchar *path_utf8)
1465 if (!stat_utf8(path_utf8, &st))
1471 if (S_ISDIR(st.st_mode))
1472 return file_data_new(path_utf8, &st, TRUE);
1474 dir = remove_level_from_path(path_utf8);
1476 filelist_read_real(dir, &files, NULL, TRUE);
1478 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1479 if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1485 filelist_free(files);
1491 void filelist_free(GList *list)
1498 file_data_unref((FileData *)work->data);
1506 GList *filelist_copy(GList *list)
1508 GList *new_list = NULL;
1519 new_list = g_list_prepend(new_list, file_data_ref(fd));
1522 return g_list_reverse(new_list);
1525 GList *filelist_from_path_list(GList *list)
1527 GList *new_list = NULL;
1538 new_list = g_list_prepend(new_list, file_data_new_group(path));
1541 return g_list_reverse(new_list);
1544 GList *filelist_to_path_list(GList *list)
1546 GList *new_list = NULL;
1557 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1560 return g_list_reverse(new_list);
1563 GList *filelist_filter(GList *list, gboolean is_dir_list)
1567 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1572 FileData *fd = (FileData *)(work->data);
1573 const gchar *name = fd->name;
1575 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1576 (!is_dir_list && !filter_name_exists(name)) ||
1577 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1578 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1582 list = g_list_remove_link(list, link);
1583 file_data_unref(fd);
1594 *-----------------------------------------------------------------------------
1595 * filelist recursive
1596 *-----------------------------------------------------------------------------
1599 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1601 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1604 GList *filelist_sort_path(GList *list)
1606 return g_list_sort(list, filelist_sort_path_cb);
1609 static void filelist_recursive_append(GList **list, GList *dirs)
1616 FileData *fd = (FileData *)(work->data);
1620 if (filelist_read(fd, &f, &d))
1622 f = filelist_filter(f, FALSE);
1623 f = filelist_sort_path(f);
1624 *list = g_list_concat(*list, f);
1626 d = filelist_filter(d, TRUE);
1627 d = filelist_sort_path(d);
1628 filelist_recursive_append(list, d);
1636 static void filelist_recursive_append_full(GList **list, GList *dirs, SortType method, gboolean ascend)
1643 FileData *fd = (FileData *)(work->data);
1647 if (filelist_read(fd, &f, &d))
1649 f = filelist_filter(f, FALSE);
1650 f = filelist_sort_full(f, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1651 *list = g_list_concat(*list, f);
1653 d = filelist_filter(d, TRUE);
1654 d = filelist_sort_path(d);
1655 filelist_recursive_append_full(list, d, method, ascend);
1663 GList *filelist_recursive(FileData *dir_fd)
1668 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1669 list = filelist_filter(list, FALSE);
1670 list = filelist_sort_path(list);
1672 d = filelist_filter(d, TRUE);
1673 d = filelist_sort_path(d);
1674 filelist_recursive_append(&list, d);
1680 GList *filelist_recursive_full(FileData *dir_fd, SortType method, gboolean ascend)
1685 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1686 list = filelist_filter(list, FALSE);
1687 list = filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1689 d = filelist_filter(d, TRUE);
1690 d = filelist_sort_path(d);
1691 filelist_recursive_append_full(&list, d, method, ascend);
1698 *-----------------------------------------------------------------------------
1699 * file modification support
1700 *-----------------------------------------------------------------------------
1704 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1706 if (!fdci && fd) fdci = fd->change;
1710 g_free(fdci->source);
1715 if (fd) fd->change = NULL;
1718 static gboolean file_data_can_write_directly(FileData *fd)
1720 return filter_name_is_writable(fd->extension);
1723 static gboolean file_data_can_write_sidecar(FileData *fd)
1725 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1728 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1730 gchar *sidecar_path = NULL;
1733 if (!file_data_can_write_sidecar(fd)) return NULL;
1735 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1736 gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1739 FileData *sfd = work->data;
1741 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1743 sidecar_path = g_strdup(sfd->path);
1747 g_free(extended_extension);
1749 if (!existing_only && !sidecar_path)
1751 if (options->metadata.sidecar_extended_name)
1752 sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1755 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1756 sidecar_path = g_strconcat(base, ".xmp", NULL);
1761 return sidecar_path;
1765 * marks and orientation
1768 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1769 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1770 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1771 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1773 gboolean file_data_get_mark(FileData *fd, gint n)
1775 gboolean valid = (fd->valid_marks & (1 << n));
1777 if (file_data_get_mark_func[n] && !valid)
1779 guint old = fd->marks;
1780 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1782 if (!value != !(fd->marks & (1 << n)))
1784 fd->marks = fd->marks ^ (1 << n);
1787 fd->valid_marks |= (1 << n);
1788 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1790 file_data_unref(fd);
1792 else if (!old && fd->marks)
1798 return !!(fd->marks & (1 << n));
1801 guint file_data_get_marks(FileData *fd)
1804 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1808 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1811 if (!value == !file_data_get_mark(fd, n)) return;
1813 if (file_data_set_mark_func[n])
1815 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1820 fd->marks = fd->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)
1831 file_data_increment_version(fd);
1832 file_data_send_notification(fd, NOTIFY_MARKS);
1835 gboolean file_data_filter_marks(FileData *fd, guint filter)
1838 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1839 return ((fd->marks & filter) == filter);
1842 GList *file_data_filter_marks_list(GList *list, guint filter)
1849 FileData *fd = work->data;
1853 if (!file_data_filter_marks(fd, filter))
1855 list = g_list_remove_link(list, link);
1856 file_data_unref(fd);
1864 gboolean file_data_filter_file_filter(FileData *fd, GRegex *filter)
1866 return g_regex_match(filter, fd->name, 0, NULL);
1869 GList *file_data_filter_file_filter_list(GList *list, GRegex *filter)
1876 FileData *fd = work->data;
1880 if (!file_data_filter_file_filter(fd, filter))
1882 list = g_list_remove_link(list, link);
1883 file_data_unref(fd);
1891 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1893 FileData *fd = value;
1894 file_data_increment_version(fd);
1895 file_data_send_notification(fd, NOTIFY_MARKS);
1898 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1900 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1902 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1904 file_data_get_mark_func[n] = get_mark_func;
1905 file_data_set_mark_func[n] = set_mark_func;
1906 file_data_mark_func_data[n] = data;
1907 file_data_destroy_mark_func[n] = notify;
1909 if (get_mark_func && file_data_pool)
1911 /* this effectively changes all known files */
1912 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1918 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1920 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1921 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1922 if (data) *data = file_data_mark_func_data[n];
1925 gint file_data_get_user_orientation(FileData *fd)
1927 return fd->user_orientation;
1930 void file_data_set_user_orientation(FileData *fd, gint value)
1932 if (fd->user_orientation == value) return;
1934 fd->user_orientation = value;
1935 file_data_increment_version(fd);
1936 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1941 * file_data - operates on the given fd
1942 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1946 /* return list of sidecar file extensions in a string */
1947 gchar *file_data_sc_list_to_string(FileData *fd)
1950 GString *result = g_string_new("");
1952 work = fd->sidecar_files;
1955 FileData *sfd = work->data;
1957 result = g_string_append(result, "+ ");
1958 result = g_string_append(result, sfd->extension);
1960 if (work) result = g_string_append_c(result, ' ');
1963 return g_string_free(result, FALSE);
1969 * add FileDataChangeInfo (see typedefs.h) for the given operation
1970 * uses file_data_add_change_info
1972 * fails if the fd->change already exists - change operations can't run in parallel
1973 * fd->change_info works as a lock
1975 * dest can be NULL - in this case the current name is used for now, it will
1980 FileDataChangeInfo types:
1982 MOVE - path is changed, name may be changed too
1983 RENAME - path remains unchanged, name is changed
1984 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1985 sidecar names are changed too, extensions are not changed
1987 UPDATE - file size, date or grouping has been changed
1990 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1992 FileDataChangeInfo *fdci;
1994 if (fd->change) return FALSE;
1996 fdci = g_new0(FileDataChangeInfo, 1);
2001 fdci->source = g_strdup(src);
2003 fdci->source = g_strdup(fd->path);
2006 fdci->dest = g_strdup(dest);
2013 static void file_data_planned_change_remove(FileData *fd)
2015 if (file_data_planned_change_hash &&
2016 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
2018 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
2020 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
2021 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
2022 file_data_unref(fd);
2023 if (g_hash_table_size(file_data_planned_change_hash) == 0)
2025 g_hash_table_destroy(file_data_planned_change_hash);
2026 file_data_planned_change_hash = NULL;
2027 DEBUG_1("planned change: empty");
2034 void file_data_free_ci(FileData *fd)
2036 FileDataChangeInfo *fdci = fd->change;
2040 file_data_planned_change_remove(fd);
2042 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
2044 g_free(fdci->source);
2052 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
2054 FileDataChangeInfo *fdci = fd->change;
2056 fdci->regroup_when_finished = enable;
2059 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
2063 if (fd->parent) fd = fd->parent;
2065 if (fd->change) return FALSE;
2067 work = fd->sidecar_files;
2070 FileData *sfd = work->data;
2072 if (sfd->change) return FALSE;
2076 file_data_add_ci(fd, type, NULL, NULL);
2078 work = fd->sidecar_files;
2081 FileData *sfd = work->data;
2083 file_data_add_ci(sfd, type, NULL, NULL);
2090 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
2094 if (fd->parent) fd = fd->parent;
2096 if (!fd->change || fd->change->type != type) return FALSE;
2098 work = fd->sidecar_files;
2101 FileData *sfd = work->data;
2103 if (!sfd->change || sfd->change->type != type) return FALSE;
2111 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
2113 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
2114 file_data_sc_update_ci_copy(fd, dest_path);
2118 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
2120 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
2121 file_data_sc_update_ci_move(fd, dest_path);
2125 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
2127 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
2128 file_data_sc_update_ci_rename(fd, dest_path);
2132 gboolean file_data_sc_add_ci_delete(FileData *fd)
2134 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
2137 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
2139 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
2140 file_data_sc_update_ci_unspecified(fd, dest_path);
2144 gboolean file_data_add_ci_write_metadata(FileData *fd)
2146 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
2149 void file_data_sc_free_ci(FileData *fd)
2153 if (fd->parent) fd = fd->parent;
2155 file_data_free_ci(fd);
2157 work = fd->sidecar_files;
2160 FileData *sfd = work->data;
2162 file_data_free_ci(sfd);
2167 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2170 gboolean ret = TRUE;
2175 FileData *fd = work->data;
2177 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2184 static void file_data_sc_revert_ci_list(GList *fd_list)
2191 FileData *fd = work->data;
2193 file_data_sc_free_ci(fd);
2198 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2205 FileData *fd = work->data;
2207 if (!func(fd, dest))
2209 file_data_sc_revert_ci_list(work->prev);
2218 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2220 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2223 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2225 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2228 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2230 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2233 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2235 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2238 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2241 gboolean ret = TRUE;
2246 FileData *fd = work->data;
2248 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2255 void file_data_free_ci_list(GList *fd_list)
2262 FileData *fd = work->data;
2264 file_data_free_ci(fd);
2269 void file_data_sc_free_ci_list(GList *fd_list)
2276 FileData *fd = work->data;
2278 file_data_sc_free_ci(fd);
2284 * update existing fd->change, it will be used from dialog callbacks for interactive editing
2285 * fails if fd->change does not exist or the change type does not match
2288 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2290 FileDataChangeType type = fd->change->type;
2292 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2296 if (!file_data_planned_change_hash)
2297 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2299 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2301 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2302 g_hash_table_remove(file_data_planned_change_hash, old_path);
2303 file_data_unref(fd);
2306 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
2311 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2312 g_hash_table_remove(file_data_planned_change_hash, new_path);
2313 file_data_unref(ofd);
2316 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2318 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2323 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2325 gchar *old_path = fd->change->dest;
2327 fd->change->dest = g_strdup(dest_path);
2328 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2332 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2334 const gchar *extension = registered_extension_from_path(fd->change->source);
2335 gchar *base = remove_extension_from_path(dest_path);
2336 gchar *old_path = fd->change->dest;
2338 fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2339 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2345 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2348 gchar *dest_path_full = NULL;
2350 if (fd->parent) fd = fd->parent;
2354 dest_path = fd->path;
2356 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2358 gchar *dir = remove_level_from_path(fd->path);
2360 dest_path_full = g_build_filename(dir, dest_path, NULL);
2362 dest_path = dest_path_full;
2364 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2366 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2367 dest_path = dest_path_full;
2370 file_data_update_ci_dest(fd, dest_path);
2372 work = fd->sidecar_files;
2375 FileData *sfd = work->data;
2377 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2381 g_free(dest_path_full);
2384 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2386 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2387 file_data_sc_update_ci(fd, dest_path);
2391 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2393 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2396 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2398 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2401 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2403 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2406 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2408 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2411 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2413 gboolean (*func)(FileData *, const gchar *))
2416 gboolean ret = TRUE;
2421 FileData *fd = work->data;
2423 if (!func(fd, dest)) ret = FALSE;
2430 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2432 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2435 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2437 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2440 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2442 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2447 * verify source and dest paths - dest image exists, etc.
2448 * it should detect all possible problems with the planned operation
2451 gint file_data_verify_ci(FileData *fd, GList *list)
2453 gint ret = CHANGE_OK;
2456 FileData *fd1 = NULL;
2460 DEBUG_1("Change checked: no change info: %s", fd->path);
2464 if (!isname(fd->path))
2466 /* this probably should not happen */
2467 ret |= CHANGE_NO_SRC;
2468 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2472 dir = remove_level_from_path(fd->path);
2474 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2475 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2476 fd->change->type != FILEDATA_CHANGE_RENAME &&
2477 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2480 ret |= CHANGE_WARN_UNSAVED_META;
2481 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2484 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2485 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2486 !access_file(fd->path, R_OK))
2488 ret |= CHANGE_NO_READ_PERM;
2489 DEBUG_1("Change checked: no read permission: %s", fd->path);
2491 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2492 !access_file(dir, W_OK))
2494 ret |= CHANGE_NO_WRITE_PERM_DIR;
2495 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2497 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2498 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2499 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2500 !access_file(fd->path, W_OK))
2502 ret |= CHANGE_WARN_NO_WRITE_PERM;
2503 DEBUG_1("Change checked: no write permission: %s", fd->path);
2505 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2506 - that means that there are no hard errors and warnings can be disabled
2507 - the destination is determined during the check
2509 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2511 /* determine destination file */
2512 gboolean have_dest = FALSE;
2513 gchar *dest_dir = NULL;
2515 if (options->metadata.save_in_image_file)
2517 if (file_data_can_write_directly(fd))
2519 /* we can write the file directly */
2520 if (access_file(fd->path, W_OK))
2526 if (options->metadata.warn_on_write_problems)
2528 ret |= CHANGE_WARN_NO_WRITE_PERM;
2529 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2533 else if (file_data_can_write_sidecar(fd))
2535 /* we can write sidecar */
2536 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2537 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2539 file_data_update_ci_dest(fd, sidecar);
2544 if (options->metadata.warn_on_write_problems)
2546 ret |= CHANGE_WARN_NO_WRITE_PERM;
2547 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2556 /* write private metadata file under ~/.geeqie */
2558 /* If an existing metadata file exists, we will try writing to
2559 * it's location regardless of the user's preference.
2561 gchar *metadata_path = NULL;
2563 /* but ignore XMP if we are not able to write it */
2564 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2566 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2568 if (metadata_path && !access_file(metadata_path, W_OK))
2570 g_free(metadata_path);
2571 metadata_path = NULL;
2578 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2579 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2581 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2583 metadata_path = g_build_filename(dest_dir, filename, NULL);
2587 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2589 file_data_update_ci_dest(fd, metadata_path);
2594 ret |= CHANGE_NO_WRITE_PERM_DEST;
2595 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2597 g_free(metadata_path);
2602 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2607 same = (strcmp(fd->path, fd->change->dest) == 0);
2611 const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2612 if (!dest_ext) dest_ext = "";
2613 if (!options->file_filter.disable_file_extension_checks)
2615 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2617 ret |= CHANGE_WARN_CHANGED_EXT;
2618 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2624 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2626 ret |= CHANGE_WARN_SAME;
2627 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2631 dest_dir = remove_level_from_path(fd->change->dest);
2633 if (!isdir(dest_dir))
2635 ret |= CHANGE_NO_DEST_DIR;
2636 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2638 else if (!access_file(dest_dir, W_OK))
2640 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2641 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2645 if (isfile(fd->change->dest))
2647 if (!access_file(fd->change->dest, W_OK))
2649 ret |= CHANGE_NO_WRITE_PERM_DEST;
2650 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2654 ret |= CHANGE_WARN_DEST_EXISTS;
2655 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2658 else if (isdir(fd->change->dest))
2660 ret |= CHANGE_DEST_EXISTS;
2661 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2668 /* During a rename operation, check if another planned destination file has
2671 if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2672 fd->change->type == FILEDATA_CHANGE_COPY ||
2673 fd->change->type == FILEDATA_CHANGE_MOVE)
2680 if (fd1 != NULL && fd != fd1 )
2682 if (!strcmp(fd->change->dest, fd1->change->dest))
2684 ret |= CHANGE_DUPLICATE_DEST;
2690 fd->change->error = ret;
2691 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2698 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2703 ret = file_data_verify_ci(fd, list);
2705 work = fd->sidecar_files;
2708 FileData *sfd = work->data;
2710 ret |= file_data_verify_ci(sfd, list);
2717 gchar *file_data_get_error_string(gint error)
2719 GString *result = g_string_new("");
2721 if (error & CHANGE_NO_SRC)
2723 if (result->len > 0) g_string_append(result, ", ");
2724 g_string_append(result, _("file or directory does not exist"));
2727 if (error & CHANGE_DEST_EXISTS)
2729 if (result->len > 0) g_string_append(result, ", ");
2730 g_string_append(result, _("destination already exists"));
2733 if (error & CHANGE_NO_WRITE_PERM_DEST)
2735 if (result->len > 0) g_string_append(result, ", ");
2736 g_string_append(result, _("destination can't be overwritten"));
2739 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2741 if (result->len > 0) g_string_append(result, ", ");
2742 g_string_append(result, _("destination directory is not writable"));
2745 if (error & CHANGE_NO_DEST_DIR)
2747 if (result->len > 0) g_string_append(result, ", ");
2748 g_string_append(result, _("destination directory does not exist"));
2751 if (error & CHANGE_NO_WRITE_PERM_DIR)
2753 if (result->len > 0) g_string_append(result, ", ");
2754 g_string_append(result, _("source directory is not writable"));
2757 if (error & CHANGE_NO_READ_PERM)
2759 if (result->len > 0) g_string_append(result, ", ");
2760 g_string_append(result, _("no read permission"));
2763 if (error & CHANGE_WARN_NO_WRITE_PERM)
2765 if (result->len > 0) g_string_append(result, ", ");
2766 g_string_append(result, _("file is readonly"));
2769 if (error & CHANGE_WARN_DEST_EXISTS)
2771 if (result->len > 0) g_string_append(result, ", ");
2772 g_string_append(result, _("destination already exists and will be overwritten"));
2775 if (error & CHANGE_WARN_SAME)
2777 if (result->len > 0) g_string_append(result, ", ");
2778 g_string_append(result, _("source and destination are the same"));
2781 if (error & CHANGE_WARN_CHANGED_EXT)
2783 if (result->len > 0) g_string_append(result, ", ");
2784 g_string_append(result, _("source and destination have different extension"));
2787 if (error & CHANGE_WARN_UNSAVED_META)
2789 if (result->len > 0) g_string_append(result, ", ");
2790 g_string_append(result, _("there are unsaved metadata changes for the file"));
2793 if (error & CHANGE_DUPLICATE_DEST)
2795 if (result->len > 0) g_string_append(result, ", ");
2796 g_string_append(result, _("another destination file has the same filename"));
2799 return g_string_free(result, FALSE);
2802 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2805 gint all_errors = 0;
2806 gint common_errors = ~0;
2811 if (!list) return 0;
2813 num = g_list_length(list);
2814 errors = g_new(int, num);
2825 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2826 all_errors |= error;
2827 common_errors &= error;
2834 if (desc && all_errors)
2837 GString *result = g_string_new("");
2841 gchar *str = file_data_get_error_string(common_errors);
2842 g_string_append(result, str);
2843 g_string_append(result, "\n");
2857 error = errors[i] & ~common_errors;
2861 gchar *str = file_data_get_error_string(error);
2862 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2867 *desc = g_string_free(result, FALSE);
2876 * perform the change described by FileFataChangeInfo
2877 * it is used for internal operations,
2878 * this function actually operates with files on the filesystem
2879 * it should implement safe delete
2882 static gboolean file_data_perform_move(FileData *fd)
2884 g_assert(!strcmp(fd->change->source, fd->path));
2885 return move_file(fd->change->source, fd->change->dest);
2888 static gboolean file_data_perform_copy(FileData *fd)
2890 g_assert(!strcmp(fd->change->source, fd->path));
2891 return copy_file(fd->change->source, fd->change->dest);
2894 static gboolean file_data_perform_delete(FileData *fd)
2896 if (isdir(fd->path) && !islink(fd->path))
2897 return rmdir_utf8(fd->path);
2899 if (options->file_ops.safe_delete_enable)
2900 return file_util_safe_unlink(fd->path);
2902 return unlink_file(fd->path);
2905 gboolean file_data_perform_ci(FileData *fd)
2907 FileDataChangeType type = fd->change->type;
2911 case FILEDATA_CHANGE_MOVE:
2912 return file_data_perform_move(fd);
2913 case FILEDATA_CHANGE_COPY:
2914 return file_data_perform_copy(fd);
2915 case FILEDATA_CHANGE_RENAME:
2916 return file_data_perform_move(fd); /* the same as move */
2917 case FILEDATA_CHANGE_DELETE:
2918 return file_data_perform_delete(fd);
2919 case FILEDATA_CHANGE_WRITE_METADATA:
2920 return metadata_write_perform(fd);
2921 case FILEDATA_CHANGE_UNSPECIFIED:
2922 /* nothing to do here */
2930 gboolean file_data_sc_perform_ci(FileData *fd)
2933 gboolean ret = TRUE;
2934 FileDataChangeType type = fd->change->type;
2936 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2938 work = fd->sidecar_files;
2941 FileData *sfd = work->data;
2943 if (!file_data_perform_ci(sfd)) ret = FALSE;
2947 if (!file_data_perform_ci(fd)) ret = FALSE;
2953 * updates FileData structure according to FileDataChangeInfo
2956 gboolean file_data_apply_ci(FileData *fd)
2958 FileDataChangeType type = fd->change->type;
2961 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2963 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2964 file_data_planned_change_remove(fd);
2966 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2968 /* this change overwrites another file which is already known to other modules
2969 renaming fd would create duplicate FileData structure
2970 the best thing we can do is nothing
2971 FIXME: maybe we could copy stuff like marks
2973 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2977 file_data_set_path(fd, fd->change->dest);
2980 file_data_increment_version(fd);
2981 file_data_send_notification(fd, NOTIFY_CHANGE);
2986 gboolean file_data_sc_apply_ci(FileData *fd)
2989 FileDataChangeType type = fd->change->type;
2991 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2993 work = fd->sidecar_files;
2996 FileData *sfd = work->data;
2998 file_data_apply_ci(sfd);
3002 file_data_apply_ci(fd);
3007 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
3010 if (fd->parent) fd = fd->parent;
3011 if (!g_list_find(list, fd)) return FALSE;
3013 work = fd->sidecar_files;
3016 if (!g_list_find(list, work->data)) return FALSE;
3022 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
3027 /* change partial groups to independent files */
3032 FileData *fd = work->data;
3035 if (!file_data_list_contains_whole_group(list, fd))
3037 file_data_disable_grouping(fd, TRUE);
3040 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
3046 /* remove sidecars from the list,
3047 they can be still acessed via main_fd->sidecar_files */
3051 FileData *fd = work->data;
3055 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
3057 out = g_list_prepend(out, file_data_ref(fd));
3061 filelist_free(list);
3062 out = g_list_reverse(out);
3072 * notify other modules about the change described by FileDataChangeInfo
3075 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
3076 FIXME do we need the ignore_list? It looks like a workaround for ineffective
3077 implementation in view_file_list.c */
3080 typedef struct _NotifyIdleData NotifyIdleData;
3082 struct _NotifyIdleData {
3088 typedef struct _NotifyData NotifyData;
3090 struct _NotifyData {
3091 FileDataNotifyFunc func;
3093 NotifyPriority priority;
3096 static GList *notify_func_list = NULL;
3098 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
3100 NotifyData *nda = (NotifyData *)a;
3101 NotifyData *ndb = (NotifyData *)b;
3103 if (nda->priority < ndb->priority) return -1;
3104 if (nda->priority > ndb->priority) return 1;
3108 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
3111 GList *work = notify_func_list;
3115 NotifyData *nd = (NotifyData *)work->data;
3117 if (nd->func == func && nd->data == data)
3119 g_warning("Notify func already registered");
3125 nd = g_new(NotifyData, 1);
3128 nd->priority = priority;
3130 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
3131 DEBUG_2("Notify func registered: %p", nd);
3136 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
3138 GList *work = notify_func_list;
3142 NotifyData *nd = (NotifyData *)work->data;
3144 if (nd->func == func && nd->data == data)
3146 notify_func_list = g_list_delete_link(notify_func_list, work);
3148 DEBUG_2("Notify func unregistered: %p", nd);
3154 g_warning("Notify func not found");
3159 gboolean file_data_send_notification_idle_cb(gpointer data)
3161 NotifyIdleData *nid = (NotifyIdleData *)data;
3162 GList *work = notify_func_list;
3166 NotifyData *nd = (NotifyData *)work->data;
3168 nd->func(nid->fd, nid->type, nd->data);
3171 file_data_unref(nid->fd);
3176 void file_data_send_notification(FileData *fd, NotifyType type)
3178 GList *work = notify_func_list;
3182 NotifyData *nd = (NotifyData *)work->data;
3184 nd->func(fd, type, nd->data);
3188 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3189 nid->fd = file_data_ref(fd);
3191 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3195 static GHashTable *file_data_monitor_pool = NULL;
3196 static guint realtime_monitor_id = 0; /* event source id */
3198 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
3202 file_data_check_changed_files(fd);
3204 DEBUG_1("monitor %s", fd->path);
3207 static gboolean realtime_monitor_cb(gpointer data)
3209 if (!options->update_on_time_change) return TRUE;
3210 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
3214 gboolean file_data_register_real_time_monitor(FileData *fd)
3220 if (!file_data_monitor_pool)
3221 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3223 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3225 DEBUG_1("Register realtime %d %s", count, fd->path);
3228 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3230 if (!realtime_monitor_id)
3232 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
3238 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3242 g_assert(file_data_monitor_pool);
3244 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3246 DEBUG_1("Unregister realtime %d %s", count, fd->path);
3248 g_assert(count > 0);
3253 g_hash_table_remove(file_data_monitor_pool, fd);
3255 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3257 file_data_unref(fd);
3259 if (g_hash_table_size(file_data_monitor_pool) == 0)
3261 g_source_remove(realtime_monitor_id);
3262 realtime_monitor_id = 0;
3270 *-----------------------------------------------------------------------------
3271 * Saving marks list, clearing marks
3272 * Uses file_data_pool
3273 *-----------------------------------------------------------------------------
3276 static void marks_get_files(gpointer key, gpointer value, gpointer userdata)
3278 gchar *file_name = key;
3279 GString *result = userdata;
3282 if (isfile(file_name))
3285 if (fd && fd->marks > 0)
3287 g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks);
3292 gboolean marks_list_load(const gchar *path)
3300 pathl = path_from_utf8(path);
3301 f = fopen(pathl, "r");
3303 if (!f) return FALSE;
3305 /* first line must start with Marks comment */
3306 if (!fgets(s_buf, sizeof(s_buf), f) ||
3307 strncmp(s_buf, "#Marks", 6) != 0)
3313 while (fgets(s_buf, sizeof(s_buf), f))
3315 if (s_buf[0]=='#') continue;
3316 file_path = strtok(s_buf, ",");
3317 marks_value = strtok(NULL, ",");
3318 if (isfile(file_path))
3320 FileData *fd = file_data_new_group(file_path);
3325 gint mark_no = 1 << n;
3326 if (atoi(marks_value) & mark_no)
3328 file_data_set_mark(fd, n , 1);
3339 gboolean marks_list_save(gchar *path, gboolean save)
3341 SecureSaveInfo *ssi;
3343 GString *marks = g_string_new("");
3345 pathl = path_from_utf8(path);
3346 ssi = secure_open(pathl);
3350 log_printf(_("Error: Unable to write marks lists to: %s\n"), path);
3354 secure_fprintf(ssi, "#Marks lists\n");
3358 g_hash_table_foreach(file_data_pool, marks_get_files, marks);
3360 secure_fprintf(ssi, "%s", marks->str);
3361 g_string_free(marks, FALSE);
3363 secure_fprintf(ssi, "#end\n");
3364 return (secure_close(ssi) == 0);
3367 static void marks_clear(gpointer key, gpointer value, gpointer userdata)
3369 gchar *file_name = key;
3374 if (isfile(file_name))
3377 if (fd && fd->marks > 0)
3383 if (fd->marks & mark_no)
3385 file_data_set_mark(fd, n , 0);
3393 void marks_clear_all()
3395 g_hash_table_foreach(file_data_pool, marks_clear, NULL);
3397 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */