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);
315 changed = file_data_check_changed_files(fd);
317 changed = file_data_check_changed_files_recursive(fd, st);
318 if (changed && check_sidecars && sidecar_file_priority(fd->extension))
319 file_data_check_sidecars(fd);
320 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
325 fd = g_new0(FileData, 1);
329 fd->collate_key_name = NULL;
330 fd->collate_key_name_nocase = NULL;
331 fd->original_path = NULL;
333 fd->size = st->st_size;
334 fd->date = st->st_mtime;
335 fd->thumb_pixbuf = NULL;
336 fd->sidecar_files = NULL;
338 fd->magick = 0x12345678;
340 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
343 file_data_check_sidecars(fd);
348 static void file_data_check_sidecars(FileData *fd)
352 FileData *parent_fd = NULL;
355 if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
358 base_len = fd->extension - fd->path;
359 fname = g_string_new_len(fd->path, base_len);
360 work = sidecar_ext_get_list();
364 /* check for possible sidecar files;
365 the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
366 they have fd->ref set to 0 and file_data unref must chack and free them all together
367 (using fd->ref would cause loops and leaks)
371 gchar *ext = work->data;
375 if (strcmp(ext, fd->extension) == 0)
377 new_fd = fd; /* processing the original file */
382 g_string_truncate(fname, base_len);
383 g_string_append(fname, ext);
385 if (!stat_utf8(fname->str, &nst))
388 new_fd = file_data_new(fname->str, &nst, FALSE);
390 if (new_fd->disable_grouping)
392 file_data_unref(new_fd);
396 new_fd->ref--; /* do not use ref here */
400 parent_fd = new_fd; /* parent is the one with the highest prio, found first */
402 file_data_merge_sidecar_files(parent_fd, new_fd);
404 g_string_free(fname, TRUE);
408 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars)
410 gchar *path_utf8 = path_to_utf8(path);
411 FileData *ret = file_data_new(path_utf8, st, check_sidecars);
417 FileData *file_data_new_simple(const gchar *path_utf8)
421 if (!stat_utf8(path_utf8, &st))
427 return file_data_new(path_utf8, &st, TRUE);
430 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
432 sfd->parent = target;
433 if (!g_list_find(target->sidecar_files, sfd))
434 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
435 file_data_increment_version(sfd); /* increments both sfd and target */
440 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
444 file_data_add_sidecar_file(target, source);
446 work = source->sidecar_files;
449 FileData *sfd = work->data;
450 file_data_add_sidecar_file(target, sfd);
454 g_list_free(source->sidecar_files);
455 source->sidecar_files = NULL;
457 target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
462 #ifdef DEBUG_FILEDATA
463 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
465 FileData *file_data_ref(FileData *fd)
468 if (fd == NULL) return NULL;
469 #ifdef DEBUG_FILEDATA
470 if (fd->magick != 0x12345678)
471 DEBUG_0("fd magick mismatch at %s:%d", file, line);
473 g_assert(fd->magick == 0x12345678);
476 #ifdef DEBUG_FILEDATA
477 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
479 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
484 static void file_data_free(FileData *fd)
486 g_assert(fd->magick == 0x12345678);
487 g_assert(fd->ref == 0);
489 g_hash_table_remove(file_data_pool, fd->original_path);
492 g_free(fd->original_path);
493 g_free(fd->collate_key_name);
494 g_free(fd->collate_key_name_nocase);
495 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
497 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
499 file_data_change_info_free(NULL, fd);
503 #ifdef DEBUG_FILEDATA
504 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
506 void file_data_unref(FileData *fd)
509 if (fd == NULL) return;
510 #ifdef DEBUG_FILEDATA
511 if (fd->magick != 0x12345678)
512 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
514 g_assert(fd->magick == 0x12345678);
517 #ifdef DEBUG_FILEDATA
518 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
521 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
526 FileData *parent = fd->parent ? fd->parent : fd;
531 work = parent->sidecar_files;
534 FileData *sfd = work->data;
540 /* none of parent/children is referenced, we can free everything */
542 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
544 work = parent->sidecar_files;
547 FileData *sfd = work->data;
552 g_list_free(parent->sidecar_files);
553 parent->sidecar_files = NULL;
555 file_data_free(parent);
559 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
561 sfd->parent = target;
562 g_assert(g_list_find(target->sidecar_files, sfd));
564 file_data_increment_version(sfd); /* increments both sfd and target */
566 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
578 /* disables / enables grouping for particular file, sends UPDATE notification */
579 void file_data_disable_grouping(FileData *fd, gboolean disable)
581 if (!fd->disable_grouping == !disable) return;
582 fd->disable_grouping = !!disable;
588 FileData *parent = file_data_ref(fd->parent);
589 file_data_disconnect_sidecar_file(parent, fd);
590 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
591 file_data_send_notification(parent, NOTIFY_TYPE_INTERNAL);
592 file_data_unref(parent);
594 else if (fd->sidecar_files)
596 GList *sidecar_files = filelist_copy(fd->sidecar_files);
597 GList *work = sidecar_files;
600 FileData *sfd = work->data;
602 file_data_disconnect_sidecar_file(fd, sfd);
603 file_data_send_notification(sfd, NOTIFY_TYPE_INTERNAL);
605 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
606 file_data_check_sidecars((FileData *)sidecar_files->data); /* this will group the sidecars back together */
607 filelist_free(sidecar_files);
612 file_data_check_sidecars(fd);
613 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
617 /* compare name without extension */
618 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
620 size_t len1 = fd1->extension - fd1->name;
621 size_t len2 = fd2->extension - fd2->name;
623 if (len1 < len2) return -1;
624 if (len1 > len2) return 1;
626 return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
629 gboolean file_data_add_change_info(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
631 FileDataChangeInfo *fdci;
633 if (fd->change) return FALSE;
635 fdci = g_new0(FileDataChangeInfo, 1);
639 fdci->source = g_strdup(src);
641 fdci->source = g_strdup(fd->path);
644 fdci->dest = g_strdup(dest);
651 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
659 g_free(fdci->source);
672 *-----------------------------------------------------------------------------
673 * sidecar file info struct
674 *-----------------------------------------------------------------------------
679 static gint sidecar_file_priority(const gchar *path)
681 const char *extension = extension_from_path(path);
685 if (extension == NULL)
688 work = sidecar_ext_get_list();
691 gchar *ext = work->data;
694 if (strcmp(extension, ext) == 0) return i;
702 *-----------------------------------------------------------------------------
704 *-----------------------------------------------------------------------------
707 static SortType filelist_sort_method = SORT_NONE;
708 static gint filelist_sort_ascend = TRUE;
711 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
713 if (!filelist_sort_ascend)
720 switch (filelist_sort_method)
725 if (fa->size < fb->size) return -1;
726 if (fa->size > fb->size) return 1;
727 /* fall back to name */
730 if (fa->date < fb->date) return -1;
731 if (fa->date > fb->date) return 1;
732 /* fall back to name */
734 #ifdef HAVE_STRVERSCMP
736 return strverscmp(fa->name, fb->name);
743 if (options->file_sort.case_sensitive)
744 return strcmp(fa->collate_key_name, fb->collate_key_name);
746 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
749 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gint ascend)
751 filelist_sort_method = method;
752 filelist_sort_ascend = ascend;
753 return filelist_sort_compare_filedata(fa, fb);
756 static gint filelist_sort_file_cb(void *a, void *b)
758 return filelist_sort_compare_filedata(a, b);
761 GList *filelist_sort_full(GList *list, SortType method, gint ascend, GCompareFunc cb)
763 filelist_sort_method = method;
764 filelist_sort_ascend = ascend;
765 return g_list_sort(list, cb);
768 GList *filelist_insert_sort_full(GList *list, void *data, SortType method, gint ascend, GCompareFunc cb)
770 filelist_sort_method = method;
771 filelist_sort_ascend = ascend;
772 return g_list_insert_sorted(list, data, cb);
775 GList *filelist_sort(GList *list, SortType method, gint ascend)
777 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
780 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gint ascend)
782 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
786 static GList *filelist_filter_out_sidecars(GList *flist)
789 GList *flist_filtered = NULL;
793 FileData *fd = work->data;
796 if (fd->parent) /* remove fd's that are children */
799 flist_filtered = g_list_prepend(flist_filtered, fd);
803 return flist_filtered;
806 static gint filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gint follow_symlinks)
813 int (*stat_func)(const char *path, struct stat *buf);
815 g_assert(files || dirs);
817 if (files) *files = NULL;
818 if (dirs) *dirs = NULL;
820 pathl = path_from_utf8(dir_fd->path);
821 if (!pathl) return FALSE;
835 while ((dir = readdir(dp)) != NULL)
837 struct stat ent_sbuf;
838 const gchar *name = dir->d_name;
841 if (!options->file_filter.show_hidden_files && ishidden(name))
844 filepath = g_build_filename(pathl, name, NULL);
845 if (stat_func(filepath, &ent_sbuf) >= 0)
847 if (S_ISDIR(ent_sbuf.st_mode))
849 /* we ignore the .thumbnails dir for cleanliness */
851 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
852 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
853 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
854 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
856 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE));
861 if (files && filter_name_exists(name))
863 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE));
874 if (dirs) *dirs = dlist;
875 if (files) *files = filelist_filter_out_sidecars(flist);
880 gint filelist_read(FileData *dir_fd, GList **files, GList **dirs)
882 return filelist_read_real(dir_fd, files, dirs, TRUE);
885 gint filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
887 return filelist_read_real(dir_fd, files, dirs, FALSE);
890 void filelist_free(GList *list)
897 file_data_unref((FileData *)work->data);
905 GList *filelist_copy(GList *list)
907 GList *new_list = NULL;
918 new_list = g_list_prepend(new_list, file_data_ref(fd));
921 return g_list_reverse(new_list);
924 GList *filelist_from_path_list(GList *list)
926 GList *new_list = NULL;
937 new_list = g_list_prepend(new_list, file_data_new_simple(path));
940 return g_list_reverse(new_list);
943 GList *filelist_to_path_list(GList *list)
945 GList *new_list = NULL;
956 new_list = g_list_prepend(new_list, g_strdup(fd->path));
959 return g_list_reverse(new_list);
962 GList *filelist_filter(GList *list, gint is_dir_list)
966 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
971 FileData *fd = (FileData *)(work->data);
972 const gchar *name = fd->name;
974 if ((!options->file_filter.show_hidden_files && ishidden(name)) ||
975 (!is_dir_list && !filter_name_exists(name)) ||
976 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
977 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
981 list = g_list_remove_link(list, link);
993 *-----------------------------------------------------------------------------
995 *-----------------------------------------------------------------------------
998 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1000 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1003 GList *filelist_sort_path(GList *list)
1005 return g_list_sort(list, filelist_sort_path_cb);
1008 static void filelist_recursive_append(GList **list, GList *dirs)
1015 FileData *fd = (FileData *)(work->data);
1019 if (filelist_read(fd, &f, &d))
1021 f = filelist_filter(f, FALSE);
1022 f = filelist_sort_path(f);
1023 *list = g_list_concat(*list, f);
1025 d = filelist_filter(d, TRUE);
1026 d = filelist_sort_path(d);
1027 filelist_recursive_append(list, d);
1035 GList *filelist_recursive(FileData *dir_fd)
1040 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1041 list = filelist_filter(list, FALSE);
1042 list = filelist_sort_path(list);
1044 d = filelist_filter(d, TRUE);
1045 d = filelist_sort_path(d);
1046 filelist_recursive_append(&list, d);
1054 * marks and orientation
1058 gboolean file_data_get_mark(FileData *fd, gint n)
1060 return !!(fd->marks & (1 << n));
1063 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1065 if (!value == !(fd->marks & (1 << n))) return;
1067 fd->marks = fd->marks ^ (1 << n);
1068 file_data_increment_version(fd);
1069 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1072 gint file_data_get_user_orientation(FileData *fd)
1074 return fd->user_orientation;
1077 void file_data_set_user_orientation(FileData *fd, gint value)
1079 if (fd->user_orientation == value) return;
1081 fd->user_orientation = value;
1082 file_data_increment_version(fd);
1083 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1089 * file_data - operates on the given fd
1090 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1094 /* return list of sidecar file extensions in a string */
1095 gchar *file_data_sc_list_to_string(FileData *fd)
1098 GString *result = g_string_new("");
1100 work = fd->sidecar_files;
1103 FileData *sfd = work->data;
1105 result = g_string_append(result, "+ ");
1106 result = g_string_append(result, sfd->extension);
1108 if (work) result = g_string_append_c(result, ' ');
1111 return g_string_free(result, FALSE);
1117 * add FileDataChangeInfo (see typedefs.h) for the given operation
1118 * uses file_data_add_change_info
1120 * fails if the fd->change already exists - change operations can't run in parallel
1121 * fd->change_info works as a lock
1123 * dest can be NULL - in this case the current name is used for now, it will
1128 FileDataChangeInfo types:
1130 MOVE - patch is changed, name may be changed too
1131 RENAME - path remains unchanged, name is changed
1132 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1133 sidecar names are changed too, extensions are not changed
1135 UPDATE - file size, date or grouping has been changed
1138 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1140 FileDataChangeInfo *fdci;
1142 if (fd->change) return FALSE;
1144 fdci = g_new0(FileDataChangeInfo, 1);
1149 fdci->source = g_strdup(src);
1151 fdci->source = g_strdup(fd->path);
1154 fdci->dest = g_strdup(dest);
1161 void file_data_free_ci(FileData *fd)
1163 FileDataChangeInfo *fdci = fd->change;
1168 g_free(fdci->source);
1177 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1181 if (fd->parent) fd = fd->parent;
1183 if (fd->change) return FALSE;
1185 work = fd->sidecar_files;
1188 FileData *sfd = work->data;
1190 if (sfd->change) return FALSE;
1194 file_data_add_ci(fd, type, NULL, NULL);
1196 work = fd->sidecar_files;
1199 FileData *sfd = work->data;
1201 file_data_add_ci(sfd, type, NULL, NULL);
1208 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1212 if (fd->parent) fd = fd->parent;
1214 if (!fd->change || fd->change->type != type) return FALSE;
1216 work = fd->sidecar_files;
1219 FileData *sfd = work->data;
1221 if (!sfd->change || sfd->change->type != type) return FALSE;
1229 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1231 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1232 file_data_sc_update_ci_copy(fd, dest_path);
1236 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1238 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1239 file_data_sc_update_ci_move(fd, dest_path);
1243 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1245 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1246 file_data_sc_update_ci_rename(fd, dest_path);
1250 gboolean file_data_sc_add_ci_delete(FileData *fd)
1252 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1255 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1257 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1258 file_data_sc_update_ci_unspecified(fd, dest_path);
1262 void file_data_sc_free_ci(FileData *fd)
1266 if (fd->parent) fd = fd->parent;
1268 file_data_free_ci(fd);
1270 work = fd->sidecar_files;
1273 FileData *sfd = work->data;
1275 file_data_free_ci(sfd);
1280 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1283 gboolean ret = TRUE;
1288 FileData *fd = work->data;
1290 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1297 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1300 gboolean ret = TRUE;
1305 FileData *fd = work->data;
1307 if (!file_data_sc_add_ci_copy(fd, dest)) ret = FALSE;
1314 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1317 gboolean ret = TRUE;
1322 FileData *fd = work->data;
1324 if (!file_data_sc_add_ci_move(fd, dest)) ret = FALSE;
1331 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1334 gboolean ret = TRUE;
1339 FileData *fd = work->data;
1341 if (!file_data_sc_add_ci_rename(fd, dest)) ret = FALSE;
1348 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1351 gboolean ret = TRUE;
1356 FileData *fd = work->data;
1358 if (!file_data_sc_add_ci_unspecified(fd, dest)) ret = FALSE;
1365 void file_data_sc_free_ci_list(GList *fd_list)
1372 FileData *fd = work->data;
1374 file_data_sc_free_ci(fd);
1380 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1381 * fails if fd->change does not exist or the change type does not match
1384 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1386 g_free(fd->change->dest);
1387 fd->change->dest = g_strdup(dest_path);
1390 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1392 const char *extension = extension_from_path(fd->change->source);
1393 gchar *base = remove_extension_from_path(dest_path);
1395 g_free(fd->change->dest);
1396 fd->change->dest = g_strdup_printf("%s%s", base, extension);
1401 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1404 gchar *dest_path_full = NULL;
1406 if (fd->parent) fd = fd->parent;
1408 if (!dest_path) dest_path = fd->path;
1410 if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1412 gchar *dir = remove_level_from_path(fd->path);
1414 dest_path_full = g_build_filename(dir, dest_path, NULL);
1416 dest_path = dest_path_full;
1419 if (isdir(dest_path))
1421 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1422 dest_path = dest_path_full;
1425 file_data_update_ci_dest(fd, dest_path);
1427 work = fd->sidecar_files;
1430 FileData *sfd = work->data;
1432 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1436 g_free(dest_path_full);
1439 gint file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1441 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1442 file_data_sc_update_ci(fd, dest_path);
1446 gint file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1448 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1449 file_data_sc_update_ci(fd, dest_path);
1453 gint file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1455 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1456 file_data_sc_update_ci(fd, dest_path);
1460 gint file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1462 if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1463 file_data_sc_update_ci(fd, dest_path);
1468 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1471 gboolean ret = TRUE;
1476 FileData *fd = work->data;
1478 if (!file_data_sc_update_ci_move(fd, dest)) ret = FALSE;
1485 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1488 gboolean ret = TRUE;
1493 FileData *fd = work->data;
1495 if (!file_data_sc_update_ci_copy(fd, dest)) ret = FALSE;
1502 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1505 gboolean ret = TRUE;
1510 FileData *fd = work->data;
1512 if (!file_data_sc_update_ci_unspecified(fd, dest)) ret = FALSE;
1521 * check dest paths - dest image exists, etc.
1523 * it should detect all possible problems with the planned operation
1526 gint file_data_sc_check_ci_dest(FileData *fd)
1534 * perform the change described by FileFataChangeInfo
1535 * it is used for internal operations,
1536 * this function actually operates with files on the filesystem
1537 * it should implement safe delete
1540 static gboolean file_data_perform_move(FileData *fd)
1542 g_assert(!strcmp(fd->change->source, fd->path));
1543 return move_file(fd->change->source, fd->change->dest);
1546 static gboolean file_data_perform_copy(FileData *fd)
1548 g_assert(!strcmp(fd->change->source, fd->path));
1549 return copy_file(fd->change->source, fd->change->dest);
1552 static gboolean file_data_perform_delete(FileData *fd)
1554 if (isdir(fd->path) && !islink(fd->path))
1555 return rmdir_utf8(fd->path);
1557 return unlink_file(fd->path);
1560 static gboolean file_data_perform_ci(FileData *fd)
1562 FileDataChangeType type = fd->change->type;
1565 case FILEDATA_CHANGE_MOVE:
1566 return file_data_perform_move(fd);
1567 case FILEDATA_CHANGE_COPY:
1568 return file_data_perform_copy(fd);
1569 case FILEDATA_CHANGE_RENAME:
1570 return file_data_perform_move(fd); /* the same as move */
1571 case FILEDATA_CHANGE_DELETE:
1572 return file_data_perform_delete(fd);
1573 case FILEDATA_CHANGE_UNSPECIFIED:
1574 /* nothing to do here */
1582 gboolean file_data_sc_perform_ci(FileData *fd)
1585 gboolean ret = TRUE;
1586 FileDataChangeType type = fd->change->type;
1588 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1590 work = fd->sidecar_files;
1593 FileData *sfd = work->data;
1595 if (!file_data_perform_ci(sfd)) ret = FALSE;
1599 if (!file_data_perform_ci(fd)) ret = FALSE;
1605 * updates FileData structure according to FileDataChangeInfo
1608 static void file_data_apply_ci(FileData *fd)
1610 FileDataChangeType type = fd->change->type;
1613 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1615 file_data_set_path(fd, fd->change->dest);
1617 file_data_increment_version(fd);
1618 file_data_send_notification(fd, NOTIFY_TYPE_CHANGE);
1621 gint file_data_sc_apply_ci(FileData *fd)
1624 FileDataChangeType type = fd->change->type;
1626 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1628 work = fd->sidecar_files;
1631 FileData *sfd = work->data;
1633 file_data_apply_ci(sfd);
1637 file_data_apply_ci(fd);
1644 * notify other modules about the change described by FileFataChangeInfo
1647 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
1648 FIXME do we need the ignore_list? It looks like a workaround for ineffective
1649 implementation in view_file_list.c */
1654 typedef struct _NotifyData NotifyData;
1656 struct _NotifyData {
1657 FileDataNotifyFunc func;
1659 NotifyPriority priority;
1662 static GList *notify_func_list = NULL;
1664 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
1666 NotifyData *nda = (NotifyData *)a;
1667 NotifyData *ndb = (NotifyData *)b;
1669 if (nda->priority < ndb->priority) return -1;
1670 if (nda->priority > ndb->priority) return 1;
1674 gint file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
1678 nd = g_new(NotifyData, 1);
1681 nd->priority = priority;
1683 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
1684 DEBUG_1("Notify func registered: %p", nd);
1689 gint file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
1691 GList *work = notify_func_list;
1695 NotifyData *nd = (NotifyData *)work->data;
1697 if (nd->func == func && nd->data == data)
1699 notify_func_list = g_list_delete_link(notify_func_list, work);
1701 DEBUG_1("Notify func unregistered: %p", nd);
1711 void file_data_send_notification(FileData *fd, NotifyType type)
1713 GList *work = notify_func_list;
1717 NotifyData *nd = (NotifyData *)work->data;
1719 DEBUG_1("Notify func calling: %p %s", nd, fd->path);
1720 nd->func(fd, type, nd->data);
1725 static GHashTable *file_data_monitor_pool = NULL;
1726 static gint realtime_monitor_id = -1;
1728 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
1732 file_data_check_changed_files(fd);
1734 DEBUG_1("monitor %s", fd->path);
1737 static gboolean realtime_monitor_cb(gpointer data)
1739 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
1743 gint file_data_register_real_time_monitor(FileData *fd)
1749 if (!file_data_monitor_pool)
1750 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
1752 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
1754 DEBUG_1("Register realtime %d %s", count, fd->path);
1757 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
1759 if (realtime_monitor_id == -1)
1761 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
1767 gint file_data_unregister_real_time_monitor(FileData *fd)
1771 g_assert(file_data_monitor_pool);
1773 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
1775 DEBUG_1("Unregister realtime %d %s", count, fd->path);
1777 g_assert(count > 0);
1782 g_hash_table_remove(file_data_monitor_pool, fd);
1784 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
1786 file_data_unref(fd);
1788 if (g_hash_table_size(file_data_monitor_pool) == 0)
1790 g_source_remove(realtime_monitor_id);
1791 realtime_monitor_id = -1;