4 * Copyright (C) 2008 The Geeqie Team
8 * This software is released under the GNU General Public License (GNU GPL).
9 * Please read the included file COPYING for more information.
10 * This software comes with no warranty of any kind, use at your own risk!
17 #include "filefilter.h"
20 #include "secure_save.h"
21 #include "thumb_standard.h"
22 #include "ui_fileops.h"
25 static GHashTable *file_data_pool = NULL;
27 static gint sidecar_file_priority(const gchar *path);
31 *-----------------------------------------------------------------------------
32 * text conversion utils
33 *-----------------------------------------------------------------------------
36 gchar *text_from_size(gint64 size)
42 /* what I would like to use is printf("%'d", size)
43 * BUT: not supported on every libc :(
47 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
48 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
52 a = g_strdup_printf("%d", (guint)size);
58 b = g_new(gchar, l + n + 1);
83 gchar *text_from_size_abrev(gint64 size)
85 if (size < (gint64)1024)
87 return g_strdup_printf(_("%d bytes"), (gint)size);
89 if (size < (gint64)1048576)
91 return g_strdup_printf(_("%.1f K"), (double)size / 1024.0);
93 if (size < (gint64)1073741824)
95 return g_strdup_printf(_("%.1f MB"), (double)size / 1048576.0);
98 /* to avoid overflowing the double, do division in two steps */
100 return g_strdup_printf(_("%.1f GB"), (double)size / 1024.0);
103 /* note: returned string is valid until next call to text_from_time() */
104 const gchar *text_from_time(time_t t)
106 static gchar *ret = NULL;
110 GError *error = NULL;
112 btime = localtime(&t);
114 /* the %x warning about 2 digit years is not an error */
115 buflen = strftime(buf, sizeof(buf), "%x %H:%M", btime);
116 if (buflen < 1) return "";
119 ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
122 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
131 *-----------------------------------------------------------------------------
133 *-----------------------------------------------------------------------------
136 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source);
137 static void file_data_check_sidecars(FileData *fd);
138 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
141 void file_data_increment_version(FileData *fd)
144 if (fd->parent) fd->parent->version++;
147 static void file_data_set_collate_keys(FileData *fd)
149 gchar *caseless_name;
151 g_assert(g_utf8_validate(fd->name, -1, NULL));
153 caseless_name = g_utf8_casefold(fd->name, -1);
155 g_free(fd->collate_key_name);
156 g_free(fd->collate_key_name_nocase);
158 #if GLIB_CHECK_VERSION(2, 8, 0)
159 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
160 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
162 fd->collate_key_name = g_utf8_collate_key(fd->name, -1);
163 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
165 g_free(caseless_name);
168 static void file_data_set_path(FileData *fd, const gchar *path)
170 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
171 g_assert(file_data_pool);
175 if (fd->original_path)
177 g_hash_table_remove(file_data_pool, fd->original_path);
178 g_free(fd->original_path);
180 fd->original_path = g_strdup(path);
181 g_hash_table_insert(file_data_pool, fd->original_path, fd);
183 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
185 fd->path = g_strdup(path);
187 fd->extension = fd->name + 1;
188 file_data_set_collate_keys(fd);
192 fd->path = g_strdup(path);
193 fd->name = filename_from_path(fd->path);
195 if (strcmp(fd->name, "..") == 0)
197 gchar *dir = remove_level_from_path(path);
199 fd->path = remove_level_from_path(dir);
202 fd->extension = fd->name + 2;
203 file_data_set_collate_keys(fd);
206 else if (strcmp(fd->name, ".") == 0)
209 fd->path = remove_level_from_path(path);
211 fd->extension = fd->name + 1;
212 file_data_set_collate_keys(fd);
216 fd->extension = extension_from_path(fd->path);
217 if (fd->extension == NULL)
218 fd->extension = fd->name + strlen(fd->name);
220 file_data_set_collate_keys(fd);
223 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
225 gboolean ret = FALSE;
228 if (fd->size != st->st_size ||
229 fd->date != st->st_mtime)
231 fd->size = st->st_size;
232 fd->date = st->st_mtime;
233 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
234 fd->thumb_pixbuf = NULL;
235 file_data_increment_version(fd);
236 file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
240 work = fd->sidecar_files;
243 FileData *sfd = work->data;
247 if (!stat_utf8(sfd->path, &st))
251 file_data_disconnect_sidecar_file(fd, sfd);
256 ret |= file_data_check_changed_files_recursive(sfd, &st);
262 gboolean file_data_check_changed_files(FileData *fd)
264 gboolean ret = FALSE;
267 if (fd->parent) fd = fd->parent;
269 if (!stat_utf8(fd->path, &st))
272 FileData *sfd = NULL;
274 /* parent is missing, we have to rebuild whole group */
279 work = fd->sidecar_files;
285 file_data_disconnect_sidecar_file(fd, sfd);
287 if (sfd) file_data_check_sidecars(sfd); /* this will group the sidecars back together */
288 file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
292 ret |= file_data_check_changed_files_recursive(fd, &st);
298 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean check_sidecars)
302 DEBUG_2("file_data_new: '%s' %d", path_utf8, check_sidecars);
305 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
307 fd = g_hash_table_lookup(file_data_pool, path_utf8);
313 changed = file_data_check_changed_files(fd);
315 changed = file_data_check_changed_files_recursive(fd, st);
316 if (changed && check_sidecars && sidecar_file_priority(fd->extension))
317 file_data_check_sidecars(fd);
318 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
320 return file_data_ref(fd);
323 fd = g_new0(FileData, 1);
327 fd->collate_key_name = NULL;
328 fd->collate_key_name_nocase = NULL;
329 fd->original_path = NULL;
331 fd->size = st->st_size;
332 fd->date = st->st_mtime;
333 fd->thumb_pixbuf = NULL;
334 fd->sidecar_files = NULL;
336 fd->magick = 0x12345678;
338 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
341 file_data_check_sidecars(fd);
346 static void file_data_check_sidecars(FileData *fd)
350 FileData *parent_fd = NULL;
353 if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
356 base_len = fd->extension - fd->path;
357 fname = g_string_new_len(fd->path, base_len);
358 work = sidecar_ext_get_list();
362 /* check for possible sidecar files;
363 the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
364 they have fd->ref set to 0 and file_data unref must chack and free them all together
365 (using fd->ref would cause loops and leaks)
369 gchar *ext = work->data;
373 if (strcmp(ext, fd->extension) == 0)
375 new_fd = fd; /* processing the original file */
380 g_string_truncate(fname, base_len);
381 g_string_append(fname, ext);
383 if (!stat_utf8(fname->str, &nst))
386 new_fd = file_data_new(fname->str, &nst, FALSE);
388 if (new_fd->disable_grouping)
390 file_data_unref(new_fd);
394 new_fd->ref--; /* do not use ref here */
398 parent_fd = new_fd; /* parent is the one with the highest prio, found first */
400 file_data_merge_sidecar_files(parent_fd, new_fd);
402 g_string_free(fname, TRUE);
406 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars)
408 gchar *path_utf8 = path_to_utf8(path);
409 FileData *ret = file_data_new(path_utf8, st, check_sidecars);
415 FileData *file_data_new_simple(const gchar *path_utf8)
419 if (!stat_utf8(path_utf8, &st))
425 return file_data_new(path_utf8, &st, TRUE);
428 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
430 sfd->parent = target;
431 if (!g_list_find(target->sidecar_files, sfd))
432 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
433 file_data_increment_version(sfd); /* increments both sfd and target */
438 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
442 file_data_add_sidecar_file(target, source);
444 work = source->sidecar_files;
447 FileData *sfd = work->data;
448 file_data_add_sidecar_file(target, sfd);
452 g_list_free(source->sidecar_files);
453 source->sidecar_files = NULL;
455 target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
460 #ifdef DEBUG_FILEDATA
461 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
463 FileData *file_data_ref(FileData *fd)
466 if (fd == NULL) return NULL;
467 #ifdef DEBUG_FILEDATA
468 if (fd->magick != 0x12345678)
469 DEBUG_0("fd magick mismatch at %s:%d", file, line);
471 g_assert(fd->magick == 0x12345678);
474 #ifdef DEBUG_FILEDATA
475 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
477 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
482 static void file_data_free(FileData *fd)
484 g_assert(fd->magick == 0x12345678);
485 g_assert(fd->ref == 0);
487 g_hash_table_remove(file_data_pool, fd->original_path);
490 g_free(fd->original_path);
491 g_free(fd->collate_key_name);
492 g_free(fd->collate_key_name_nocase);
493 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
495 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
497 file_data_change_info_free(NULL, fd);
501 #ifdef DEBUG_FILEDATA
502 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
504 void file_data_unref(FileData *fd)
507 if (fd == NULL) return;
508 #ifdef DEBUG_FILEDATA
509 if (fd->magick != 0x12345678)
510 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
512 g_assert(fd->magick == 0x12345678);
515 #ifdef DEBUG_FILEDATA
516 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
519 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
524 FileData *parent = fd->parent ? fd->parent : fd;
529 work = parent->sidecar_files;
532 FileData *sfd = work->data;
538 /* none of parent/children is referenced, we can free everything */
540 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
542 work = parent->sidecar_files;
545 FileData *sfd = work->data;
550 g_list_free(parent->sidecar_files);
551 parent->sidecar_files = NULL;
553 file_data_free(parent);
557 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
559 sfd->parent = target;
560 g_assert(g_list_find(target->sidecar_files, sfd));
562 file_data_increment_version(sfd); /* increments both sfd and target */
564 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
576 /* disables / enables grouping for particular file, sends UPDATE notification */
577 void file_data_disable_grouping(FileData *fd, gboolean disable)
579 if (!fd->disable_grouping == !disable) return;
580 fd->disable_grouping = !!disable;
586 FileData *parent = file_data_ref(fd->parent);
587 file_data_disconnect_sidecar_file(parent, fd);
588 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
589 file_data_send_notification(parent, NOTIFY_TYPE_INTERNAL);
590 file_data_unref(parent);
592 else if (fd->sidecar_files)
594 GList *sidecar_files = filelist_copy(fd->sidecar_files);
595 GList *work = sidecar_files;
598 FileData *sfd = work->data;
600 file_data_disconnect_sidecar_file(fd, sfd);
601 file_data_send_notification(sfd, NOTIFY_TYPE_INTERNAL);
603 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
604 file_data_check_sidecars((FileData *)sidecar_files->data); /* this will group the sidecars back together */
605 filelist_free(sidecar_files);
610 file_data_check_sidecars(fd);
611 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
615 /* compare name without extension */
616 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
618 size_t len1 = fd1->extension - fd1->name;
619 size_t len2 = fd2->extension - fd2->name;
621 if (len1 < len2) return -1;
622 if (len1 > len2) return 1;
624 return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
627 gboolean file_data_add_change_info(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
629 FileDataChangeInfo *fdci;
631 if (fd->change) return FALSE;
633 fdci = g_new0(FileDataChangeInfo, 1);
637 fdci->source = g_strdup(src);
639 fdci->source = g_strdup(fd->path);
642 fdci->dest = g_strdup(dest);
649 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
657 g_free(fdci->source);
670 *-----------------------------------------------------------------------------
671 * sidecar file info struct
672 *-----------------------------------------------------------------------------
677 static gint sidecar_file_priority(const gchar *path)
679 const char *extension = extension_from_path(path);
683 if (extension == NULL)
686 work = sidecar_ext_get_list();
689 gchar *ext = work->data;
692 if (strcmp(extension, ext) == 0) return i;
700 *-----------------------------------------------------------------------------
702 *-----------------------------------------------------------------------------
705 static SortType filelist_sort_method = SORT_NONE;
706 static gint filelist_sort_ascend = TRUE;
709 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
711 if (!filelist_sort_ascend)
718 switch (filelist_sort_method)
723 if (fa->size < fb->size) return -1;
724 if (fa->size > fb->size) return 1;
725 /* fall back to name */
728 if (fa->date < fb->date) return -1;
729 if (fa->date > fb->date) return 1;
730 /* fall back to name */
732 #ifdef HAVE_STRVERSCMP
734 return strverscmp(fa->name, fb->name);
741 if (options->file_sort.case_sensitive)
742 return strcmp(fa->collate_key_name, fb->collate_key_name);
744 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
747 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gint ascend)
749 filelist_sort_method = method;
750 filelist_sort_ascend = ascend;
751 return filelist_sort_compare_filedata(fa, fb);
754 static gint filelist_sort_file_cb(void *a, void *b)
756 return filelist_sort_compare_filedata(a, b);
759 GList *filelist_sort_full(GList *list, SortType method, gint ascend, GCompareFunc cb)
761 filelist_sort_method = method;
762 filelist_sort_ascend = ascend;
763 return g_list_sort(list, cb);
766 GList *filelist_insert_sort_full(GList *list, void *data, SortType method, gint ascend, GCompareFunc cb)
768 filelist_sort_method = method;
769 filelist_sort_ascend = ascend;
770 return g_list_insert_sorted(list, data, cb);
773 GList *filelist_sort(GList *list, SortType method, gint ascend)
775 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
778 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gint ascend)
780 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
784 static GList *filelist_filter_out_sidecars(GList *flist)
787 GList *flist_filtered = NULL;
791 FileData *fd = work->data;
794 if (fd->parent) /* remove fd's that are children */
797 flist_filtered = g_list_prepend(flist_filtered, fd);
801 return flist_filtered;
804 static gint filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gint follow_symlinks)
811 int (*stat_func)(const char *path, struct stat *buf);
813 g_assert(files || dirs);
815 if (files) *files = NULL;
816 if (dirs) *dirs = NULL;
818 pathl = path_from_utf8(dir_fd->path);
819 if (!pathl) return FALSE;
833 while ((dir = readdir(dp)) != NULL)
835 struct stat ent_sbuf;
836 const gchar *name = dir->d_name;
839 if (!options->file_filter.show_hidden_files && ishidden(name))
842 filepath = g_build_filename(pathl, name, NULL);
843 if (stat_func(filepath, &ent_sbuf) >= 0)
845 if (S_ISDIR(ent_sbuf.st_mode))
847 /* we ignore the .thumbnails dir for cleanliness */
849 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
850 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
851 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
852 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
854 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE));
859 if (files && filter_name_exists(name))
861 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE));
872 if (dirs) *dirs = dlist;
873 if (files) *files = filelist_filter_out_sidecars(flist);
878 gint filelist_read(FileData *dir_fd, GList **files, GList **dirs)
880 return filelist_read_real(dir_fd, files, dirs, TRUE);
883 gint filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
885 return filelist_read_real(dir_fd, files, dirs, FALSE);
888 void filelist_free(GList *list)
895 file_data_unref((FileData *)work->data);
903 GList *filelist_copy(GList *list)
905 GList *new_list = NULL;
916 new_list = g_list_prepend(new_list, file_data_ref(fd));
919 return g_list_reverse(new_list);
922 GList *filelist_from_path_list(GList *list)
924 GList *new_list = NULL;
935 new_list = g_list_prepend(new_list, file_data_new_simple(path));
938 return g_list_reverse(new_list);
941 GList *filelist_to_path_list(GList *list)
943 GList *new_list = NULL;
954 new_list = g_list_prepend(new_list, g_strdup(fd->path));
957 return g_list_reverse(new_list);
960 GList *filelist_filter(GList *list, gint is_dir_list)
964 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
969 FileData *fd = (FileData *)(work->data);
970 const gchar *name = fd->name;
972 if ((!options->file_filter.show_hidden_files && ishidden(name)) ||
973 (!is_dir_list && !filter_name_exists(name)) ||
974 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
975 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
979 list = g_list_remove_link(list, link);
991 *-----------------------------------------------------------------------------
993 *-----------------------------------------------------------------------------
996 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
998 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1001 GList *filelist_sort_path(GList *list)
1003 return g_list_sort(list, filelist_sort_path_cb);
1006 static void filelist_recursive_append(GList **list, GList *dirs)
1013 FileData *fd = (FileData *)(work->data);
1017 if (filelist_read(fd, &f, &d))
1019 f = filelist_filter(f, FALSE);
1020 f = filelist_sort_path(f);
1021 *list = g_list_concat(*list, f);
1023 d = filelist_filter(d, TRUE);
1024 d = filelist_sort_path(d);
1025 filelist_recursive_append(list, d);
1033 GList *filelist_recursive(FileData *dir_fd)
1038 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1039 list = filelist_filter(list, FALSE);
1040 list = filelist_sort_path(list);
1042 d = filelist_filter(d, TRUE);
1043 d = filelist_sort_path(d);
1044 filelist_recursive_append(&list, d);
1052 * marks and orientation
1056 gboolean file_data_get_mark(FileData *fd, gint n)
1058 return !!(fd->marks & (1 << n));
1061 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1063 if (!value == !(fd->marks & (1 << n))) return;
1065 fd->marks = fd->marks ^ (1 << n);
1066 file_data_increment_version(fd);
1067 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1070 gint file_data_get_user_orientation(FileData *fd)
1072 return fd->user_orientation;
1075 void file_data_set_user_orientation(FileData *fd, gint value)
1077 if (fd->user_orientation == value) return;
1079 fd->user_orientation = value;
1080 file_data_increment_version(fd);
1081 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1087 * file_data - operates on the given fd
1088 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1092 /* return list of sidecar file extensions in a string */
1093 gchar *file_data_sc_list_to_string(FileData *fd)
1096 GString *result = g_string_new("");
1098 work = fd->sidecar_files;
1101 FileData *sfd = work->data;
1103 result = g_string_append(result, "+ ");
1104 result = g_string_append(result, sfd->extension);
1106 if (work) result = g_string_append_c(result, ' ');
1109 return g_string_free(result, FALSE);
1115 * add FileDataChangeInfo (see typedefs.h) for the given operation
1116 * uses file_data_add_change_info
1118 * fails if the fd->change already exists - change operations can't run in parallel
1119 * fd->change_info works as a lock
1121 * dest can be NULL - in this case the current name is used for now, it will
1126 FileDataChangeInfo types:
1128 MOVE - patch is changed, name may be changed too
1129 RENAME - path remains unchanged, name is changed
1130 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1131 sidecar names are changed too, extensions are not changed
1133 UPDATE - file size, date or grouping has been changed
1136 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1138 FileDataChangeInfo *fdci;
1140 if (fd->change) return FALSE;
1142 fdci = g_new0(FileDataChangeInfo, 1);
1147 fdci->source = g_strdup(src);
1149 fdci->source = g_strdup(fd->path);
1152 fdci->dest = g_strdup(dest);
1159 void file_data_free_ci(FileData *fd)
1161 FileDataChangeInfo *fdci = fd->change;
1166 g_free(fdci->source);
1175 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1179 if (fd->parent) fd = fd->parent;
1181 if (fd->change) return FALSE;
1183 work = fd->sidecar_files;
1186 FileData *sfd = work->data;
1188 if (sfd->change) return FALSE;
1192 file_data_add_ci(fd, type, NULL, NULL);
1194 work = fd->sidecar_files;
1197 FileData *sfd = work->data;
1199 file_data_add_ci(sfd, type, NULL, NULL);
1206 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1210 if (fd->parent) fd = fd->parent;
1212 if (!fd->change || fd->change->type != type) return FALSE;
1214 work = fd->sidecar_files;
1217 FileData *sfd = work->data;
1219 if (!sfd->change || sfd->change->type != type) return FALSE;
1227 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1229 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1230 file_data_sc_update_ci_copy(fd, dest_path);
1234 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1236 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1237 file_data_sc_update_ci_move(fd, dest_path);
1241 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1243 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1244 file_data_sc_update_ci_rename(fd, dest_path);
1248 gboolean file_data_sc_add_ci_delete(FileData *fd)
1250 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1253 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1255 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1256 file_data_sc_update_ci_unspecified(fd, dest_path);
1260 void file_data_sc_free_ci(FileData *fd)
1264 if (fd->parent) fd = fd->parent;
1266 file_data_free_ci(fd);
1268 work = fd->sidecar_files;
1271 FileData *sfd = work->data;
1273 file_data_free_ci(sfd);
1278 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1281 gboolean ret = TRUE;
1286 FileData *fd = work->data;
1288 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1295 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1298 gboolean ret = TRUE;
1303 FileData *fd = work->data;
1305 if (!file_data_sc_add_ci_copy(fd, dest)) ret = FALSE;
1312 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1315 gboolean ret = TRUE;
1320 FileData *fd = work->data;
1322 if (!file_data_sc_add_ci_move(fd, dest)) ret = FALSE;
1329 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1332 gboolean ret = TRUE;
1337 FileData *fd = work->data;
1339 if (!file_data_sc_add_ci_rename(fd, dest)) ret = FALSE;
1346 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1349 gboolean ret = TRUE;
1354 FileData *fd = work->data;
1356 if (!file_data_sc_add_ci_unspecified(fd, dest)) ret = FALSE;
1363 void file_data_sc_free_ci_list(GList *fd_list)
1370 FileData *fd = work->data;
1372 file_data_sc_free_ci(fd);
1378 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1379 * fails if fd->change does not exist or the change type does not match
1382 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1384 g_free(fd->change->dest);
1385 fd->change->dest = g_strdup(dest_path);
1388 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1390 const char *extension = extension_from_path(fd->change->source);
1391 gchar *base = remove_extension_from_path(dest_path);
1393 g_free(fd->change->dest);
1394 fd->change->dest = g_strdup_printf("%s%s", base, extension);
1399 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1402 gchar *dest_path_full = NULL;
1404 if (fd->parent) fd = fd->parent;
1406 if (!dest_path) dest_path = fd->path;
1408 if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1410 gchar *dir = remove_level_from_path(fd->path);
1412 dest_path_full = g_build_filename(dir, dest_path, NULL);
1414 dest_path = dest_path_full;
1417 if (isdir(dest_path))
1419 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1420 dest_path = dest_path_full;
1423 file_data_update_ci_dest(fd, dest_path);
1425 work = fd->sidecar_files;
1428 FileData *sfd = work->data;
1430 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1434 g_free(dest_path_full);
1437 gint file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1439 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1440 file_data_sc_update_ci(fd, dest_path);
1444 gint file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1446 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1447 file_data_sc_update_ci(fd, dest_path);
1451 gint file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1453 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1454 file_data_sc_update_ci(fd, dest_path);
1458 gint file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1460 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1461 file_data_sc_update_ci(fd, dest_path);
1466 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1469 gboolean ret = TRUE;
1474 FileData *fd = work->data;
1476 if (!file_data_sc_update_ci_move(fd, dest)) ret = FALSE;
1483 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1486 gboolean ret = TRUE;
1491 FileData *fd = work->data;
1493 if (!file_data_sc_update_ci_copy(fd, dest)) ret = FALSE;
1500 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1503 gboolean ret = TRUE;
1508 FileData *fd = work->data;
1510 if (!file_data_sc_update_ci_unspecified(fd, dest)) ret = FALSE;
1519 * check dest paths - dest image exists, etc.
1521 * it should detect all possible problems with the planned operation
1524 gint file_data_sc_check_ci_dest(FileData *fd)
1532 * perform the change described by FileFataChangeInfo
1533 * it is used for internal operations,
1534 * this function actually operates with files on the filesystem
1535 * it should implement safe delete
1538 static gboolean file_data_perform_move(FileData *fd)
1540 g_assert(!strcmp(fd->change->source, fd->path));
1541 return move_file(fd->change->source, fd->change->dest);
1544 static gboolean file_data_perform_copy(FileData *fd)
1546 g_assert(!strcmp(fd->change->source, fd->path));
1547 return copy_file(fd->change->source, fd->change->dest);
1550 static gboolean file_data_perform_delete(FileData *fd)
1552 return unlink_file(fd->path);
1555 static gboolean file_data_perform_ci(FileData *fd)
1557 FileDataChangeType type = fd->change->type;
1560 case FILEDATA_CHANGE_MOVE:
1561 return file_data_perform_move(fd);
1562 case FILEDATA_CHANGE_COPY:
1563 return file_data_perform_copy(fd);
1564 case FILEDATA_CHANGE_RENAME:
1565 return file_data_perform_move(fd); /* the same as move */
1566 case FILEDATA_CHANGE_DELETE:
1567 return file_data_perform_delete(fd);
1568 case FILEDATA_CHANGE_UNSPECIFIED:
1569 /* nothing to do here */
1577 gboolean file_data_sc_perform_ci(FileData *fd)
1580 gboolean ret = TRUE;
1581 FileDataChangeType type = fd->change->type;
1583 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1585 work = fd->sidecar_files;
1588 FileData *sfd = work->data;
1590 if (!file_data_perform_ci(sfd)) ret = FALSE;
1594 if (!file_data_perform_ci(fd)) ret = FALSE;
1600 * updates FileData structure according to FileDataChangeInfo
1603 static void file_data_apply_ci(FileData *fd)
1605 FileDataChangeType type = fd->change->type;
1608 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1610 file_data_set_path(fd, fd->change->dest);
1612 file_data_increment_version(fd);
1613 file_data_send_notification(fd, NOTIFY_TYPE_CHANGE);
1616 gint file_data_sc_apply_ci(FileData *fd)
1619 FileDataChangeType type = fd->change->type;
1621 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1623 work = fd->sidecar_files;
1626 FileData *sfd = work->data;
1628 file_data_apply_ci(sfd);
1632 file_data_apply_ci(fd);
1639 * notify other modules about the change described by FileFataChangeInfo
1642 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
1643 FIXME do we need the ignore_list? It looks like a workaround for ineffective
1644 implementation in view_file_list.c */
1649 typedef struct _NotifyData NotifyData;
1651 struct _NotifyData {
1652 FileDataNotifyFunc func;
1654 NotifyPriority priority;
1657 static GList *notify_func_list = NULL;
1659 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
1661 NotifyData *nda = (NotifyData *)a;
1662 NotifyData *ndb = (NotifyData *)b;
1664 if (nda->priority < ndb->priority) return -1;
1665 if (nda->priority > ndb->priority) return 1;
1669 gint file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
1673 nd = g_new(NotifyData, 1);
1676 nd->priority = priority;
1678 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
1679 DEBUG_1("Notify func registered: %p", nd);
1684 gint file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
1686 GList *work = notify_func_list;
1690 NotifyData *nd = (NotifyData *)work->data;
1692 if (nd->func == func && nd->data == data)
1694 notify_func_list = g_list_delete_link(notify_func_list, work);
1696 DEBUG_1("Notify func unregistered: %p", nd);
1706 void file_data_send_notification(FileData *fd, NotifyType type)
1708 GList *work = notify_func_list;
1712 NotifyData *nd = (NotifyData *)work->data;
1714 DEBUG_1("Notify func calling: %p %s", nd, fd->path);
1715 nd->func(fd, type, nd->data);
1720 static GHashTable *file_data_monitor_pool = NULL;
1721 static gint realtime_monitor_id = -1;
1723 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
1727 file_data_check_changed_files(fd);
1729 DEBUG_1("monitor %s", fd->path);
1732 static gboolean realtime_monitor_cb(gpointer data)
1734 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
1738 gint file_data_register_real_time_monitor(FileData *fd)
1744 if (!file_data_monitor_pool)
1745 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
1747 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
1749 DEBUG_1("Register realtime %d %s", count, fd->path);
1752 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
1754 if (realtime_monitor_id == -1)
1756 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
1762 gint file_data_unregister_real_time_monitor(FileData *fd)
1766 g_assert(file_data_monitor_pool);
1768 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
1770 DEBUG_1("Unregister realtime %d %s", count, fd->path);
1772 g_assert(count > 0);
1777 g_hash_table_remove(file_data_monitor_pool, fd);
1779 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
1781 file_data_unref(fd);
1783 if (g_hash_table_size(file_data_monitor_pool) == 0)
1785 g_source_remove(realtime_monitor_id);
1786 realtime_monitor_id = -1;