2 * Copyright (C) 2004 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.
23 #include "collect-io.h"
27 #include "layout_util.h"
29 #include "secure_save.h"
31 #include "ui_fileops.h"
33 #define GQ_COLLECTION_MARKER "#" GQ_APPNAME
35 #define GQ_COLLECTION_FAIL_MIN 300
36 #define GQ_COLLECTION_FAIL_PERCENT 98
37 #define GQ_COLLECTION_READ_BUFSIZE 4096
39 typedef struct _CollectManagerEntry CollectManagerEntry;
41 static void collection_load_thumb_step(CollectionData *cd);
42 static gboolean collection_save_private(CollectionData *cd, const gchar *path);
44 static CollectManagerEntry *collect_manager_get_entry(const gchar *path);
45 static void collect_manager_entry_reset(CollectManagerEntry *entry);
46 static gint collect_manager_process_action(CollectManagerEntry *entry, gchar **path_ptr);
49 static gboolean scan_geometry(gchar *buffer, gint *x, gint *y, gint *w, gint *h)
53 if (sscanf(buffer, "%d %d %d %d", &nx, &ny, &nw, &nh) != 4) return FALSE;
63 static gboolean collection_load_private(CollectionData *cd, const gchar *path, CollectionLoadFlags flags)
65 gchar s_buf[GQ_COLLECTION_READ_BUFSIZE];
68 gboolean limit_failures = TRUE;
69 gboolean success = TRUE;
70 gboolean has_official_header = FALSE;
71 gboolean has_geometry_header = FALSE;
72 gboolean has_gqview_header = FALSE;
73 gboolean need_header = TRUE;
76 gboolean changed = FALSE;
77 CollectManagerEntry *entry = NULL;
78 guint flush = !!(flags & COLLECTION_LOAD_FLUSH);
79 guint append = !!(flags & COLLECTION_LOAD_APPEND);
80 guint only_geometry = !!(flags & COLLECTION_LOAD_GEOMETRY);
81 gboolean reading_extended_filename = FALSE;
82 GString *extended_filename_buffer = g_string_new(NULL);
87 collection_load_stop(cd);
90 collect_manager_flush();
92 entry = collect_manager_get_entry(path);
96 collection_list_free(cd->list);
101 if (!path && !cd->path) return FALSE;
103 if (!path) path = cd->path;
105 pathl = path_from_utf8(path);
107 DEBUG_1("collection load: append=%d flush=%d only_geometry=%d path=%s",
108 append, flush, only_geometry, pathl);
111 f = fopen(pathl, "r");
115 log_printf("Failed to open collection file: \"%s\"\n", path);
119 while (fgets(s_buf, sizeof(s_buf), f))
124 if (!reading_extended_filename)
126 /* Skip whitespaces and empty lines */
127 while (*p && g_ascii_isspace(*p)) p++;
128 if (*p == '\n' || *p == '\r') continue;
133 if (!need_header) continue;
134 if (g_ascii_strncasecmp(p, GQ_COLLECTION_MARKER, strlen(GQ_COLLECTION_MARKER)) == 0)
136 /* Looks like an official collection, allow unchecked input.
137 * All this does is allow adding files that may not exist,
138 * which is needed for the collection manager to work.
139 * Also unofficial files abort after too many invalid entries.
141 has_official_header = TRUE;
142 limit_failures = FALSE;
144 else if (strncmp(p, "#geometry:", 10 ) == 0 &&
145 scan_geometry(p + 10, &cd->window_x, &cd->window_y, &cd->window_w, &cd->window_h))
147 has_geometry_header = TRUE;
148 cd->window_read = TRUE;
149 if (only_geometry) break;
151 else if (g_ascii_strncasecmp(p, "#GQview collection", strlen("#GQview collection")) == 0)
153 /* As 2008/04/15 there is no difference between our collection file format
154 * and GQview 2.1.5 collection file format so ignore failures as well. */
155 has_gqview_header = TRUE;
156 limit_failures = FALSE;
158 need_header = (!has_official_header && !has_gqview_header) || !has_geometry_header;
162 if (only_geometry) continue;
166 /** @todo This is not safe! */
167 /* Updated: anything within double quotes is considered a filename */
168 if (!reading_extended_filename)
170 /* not yet reading filename */
171 while (*p && *p != '"') p++;
173 /* start of filename read */
175 while (*p && *p != '"') p++;
178 /* first part of extended filename */
179 g_string_append(extended_filename_buffer, buf);
180 reading_extended_filename = TRUE;
187 while (*p && *p != '"') p++;
190 /* end of extended filename still not found */
191 g_string_append(extended_filename_buffer, buf);
196 /* end of extended filename found */
197 g_string_append_len(extended_filename_buffer, buf, p - buf);
198 reading_extended_filename = FALSE;
202 if (strlen(extended_filename_buffer->str) > 0)
204 buffer2 = g_strdup(extended_filename_buffer->str);
205 g_string_erase(extended_filename_buffer, 0, -1);
210 buffer2 = g_strdup(buf);
218 changed |= collect_manager_process_action(entry, &buffer2);
220 valid = (buffer2[0] == G_DIR_SEPARATOR && collection_add_check(cd, file_data_new_simple(buffer2), FALSE, TRUE));
221 if (!valid) DEBUG_1("collection invalid file: %s", buffer2);
227 if (limit_failures &&
228 fail > GQ_COLLECTION_FAIL_MIN &&
229 fail * 100 / total > GQ_COLLECTION_FAIL_PERCENT)
231 log_printf("%d invalid filenames in unofficial collection file, closing: %s\n", fail, path);
240 g_string_free(extended_filename_buffer, TRUE);
242 DEBUG_1("collection files: total = %d fail = %d official=%d gqview=%d geometry=%d",
243 total, fail, has_official_header, has_gqview_header, has_geometry_header);
246 if (only_geometry) return has_geometry_header;
251 while (collect_manager_process_action(entry, &buf))
253 collection_add_check(cd, file_data_new_group(buf), FALSE, TRUE);
260 cd->list = collection_list_sort(cd->list, cd->sort_method);
262 if (!flush && changed && success)
263 collection_save_private(cd, path);
266 collect_manager_entry_reset(entry);
268 if (!append) cd->changed = FALSE;
273 gboolean collection_load(CollectionData *cd, const gchar *path, CollectionLoadFlags flags)
275 if (collection_load_private(cd, path, flags | COLLECTION_LOAD_FLUSH))
277 layout_recent_add_path(cd->path);
284 static void collection_load_thumb_do(CollectionData *cd)
288 if (!cd->thumb_loader || !g_list_find(cd->list, cd->thumb_info)) return;
290 pixbuf = thumb_loader_get_pixbuf(cd->thumb_loader);
291 collection_info_set_thumb(cd->thumb_info, pixbuf);
292 g_object_unref(pixbuf);
294 if (cd->info_updated_func) cd->info_updated_func(cd, cd->thumb_info, cd->info_updated_data);
297 static void collection_load_thumb_error_cb(ThumbLoader *tl, gpointer data)
299 CollectionData *cd = data;
301 collection_load_thumb_do(cd);
302 collection_load_thumb_step(cd);
305 static void collection_load_thumb_done_cb(ThumbLoader *tl, gpointer data)
307 CollectionData *cd = data;
309 collection_load_thumb_do(cd);
310 collection_load_thumb_step(cd);
313 static void collection_load_thumb_step(CollectionData *cd)
320 collection_load_stop(cd);
327 /* find first unloaded thumb */
328 while (work && ci->pixbuf)
334 if (!ci || ci->pixbuf)
337 collection_load_stop(cd);
339 /* send a NULL CollectInfo to notify end */
340 if (cd->info_updated_func) cd->info_updated_func(cd, NULL, cd->info_updated_data);
345 /* setup loader and call it */
347 thumb_loader_free(cd->thumb_loader);
348 cd->thumb_loader = thumb_loader_new(options->thumbnails.max_width, options->thumbnails.max_height);
349 thumb_loader_set_callbacks(cd->thumb_loader,
350 collection_load_thumb_done_cb,
351 collection_load_thumb_error_cb,
356 if (!thumb_loader_start(cd->thumb_loader, ci->fd))
358 /* error, handle it, do next */
359 DEBUG_1("error loading thumb for %s", ci->fd->path);
360 collection_load_thumb_do(cd);
361 collection_load_thumb_step(cd);
365 void collection_load_thumb_idle(CollectionData *cd)
367 if (!cd->thumb_loader) collection_load_thumb_step(cd);
370 gboolean collection_load_begin(CollectionData *cd, const gchar *path, CollectionLoadFlags flags)
372 if (!collection_load(cd, path, flags)) return FALSE;
374 collection_load_thumb_idle(cd);
379 void collection_load_stop(CollectionData *cd)
381 if (!cd->thumb_loader) return;
383 thumb_loader_free(cd->thumb_loader);
384 cd->thumb_loader = NULL;
387 static gboolean collection_save_private(CollectionData *cd, const gchar *path)
393 if (!path && !cd->path) return FALSE;
401 pathl = path_from_utf8(path);
402 ssi = secure_open(pathl);
406 log_printf(_("failed to open collection (write) \"%s\"\n"), path);
410 secure_fprintf(ssi, "%s collection\n", GQ_COLLECTION_MARKER);
411 secure_fprintf(ssi, "#created with %s version %s\n", GQ_APPNAME, VERSION);
413 collection_update_geometry(cd);
416 secure_fprintf(ssi, "#geometry: %d %d %d %d\n", cd->window_x, cd->window_y, cd->window_w, cd->window_h);
420 while (work && secsave_errno == SS_ERR_NONE)
422 CollectInfo *ci = work->data;
423 secure_fprintf(ssi, "\"%s\"\n", ci->fd->path);
427 secure_fprintf(ssi, "#end\n");
429 if (secure_close(ssi))
431 log_printf(_("error saving collection file: %s\nerror: %s\n"), path,
432 secsave_strerror(secsave_errno));
436 if (!cd->path || strcmp(path, cd->path) != 0)
438 gchar *buf = cd->path;
439 cd->path = g_strdup(path);
444 cd->name = g_strdup(filename_from_path(cd->path));
446 collection_path_changed(cd);
454 gboolean collection_save(CollectionData *cd, const gchar *path)
456 if (collection_save_private(cd, path))
458 layout_recent_add_path(cd->path);
465 gboolean collection_load_only_geometry(CollectionData *cd, const gchar *path)
467 return collection_load(cd, path, COLLECTION_LOAD_GEOMETRY);
472 *-------------------------------------------------------------------
474 *-------------------------------------------------------------------
477 #define COLLECT_MANAGER_ACTIONS_PER_IDLE 1000
478 #define COLLECT_MANAGER_FLUSH_DELAY 10000
480 struct _CollectManagerEntry
484 GHashTable *oldpath_hash;
485 GHashTable *newpath_hash;
490 COLLECTION_MANAGER_UPDATE,
491 COLLECTION_MANAGER_ADD,
492 COLLECTION_MANAGER_REMOVE
493 } CollectManagerType;
495 typedef struct _CollectManagerAction CollectManagerAction;
496 struct _CollectManagerAction
501 CollectManagerType type;
507 static GList *collection_manager_entry_list = NULL;
508 static GList *collection_manager_action_list = NULL;
509 static GList *collection_manager_action_tail = NULL;
510 static guint collection_manager_timer_id = 0; /* event source id */
513 static CollectManagerAction *collect_manager_action_new(const gchar *oldpath, const gchar *newpath,
514 CollectManagerType type)
516 CollectManagerAction *action;
518 action = g_new0(CollectManagerAction, 1);
521 action->oldpath = g_strdup(oldpath);
522 action->newpath = g_strdup(newpath);
529 static void collect_manager_action_ref(CollectManagerAction *action)
534 static void collect_manager_action_unref(CollectManagerAction *action)
538 if (action->ref > 0) return;
540 g_free(action->oldpath);
541 g_free(action->newpath);
545 static void collect_manager_entry_free_data(CollectManagerEntry *entry)
549 work = entry->add_list;
552 CollectManagerAction *action;
557 collect_manager_action_unref(action);
559 g_list_free(entry->add_list);
560 if (g_hash_table_size(entry->oldpath_hash) > 0)
561 g_hash_table_destroy(entry->oldpath_hash);
563 g_hash_table_unref(entry->oldpath_hash);
564 if (g_hash_table_size(entry->newpath_hash) > 0)
565 g_hash_table_destroy(entry->newpath_hash);
567 g_hash_table_unref(entry->newpath_hash);
570 static void collect_manager_entry_init_data(CollectManagerEntry *entry)
572 entry->add_list = NULL;
573 entry->oldpath_hash = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify) collect_manager_action_unref);
574 entry->newpath_hash = g_hash_table_new(g_str_hash, g_str_equal);
579 static CollectManagerEntry *collect_manager_entry_new(const gchar *path)
581 CollectManagerEntry *entry;
583 entry = g_new0(CollectManagerEntry, 1);
584 entry->path = g_strdup(path);
585 collect_manager_entry_init_data(entry);
587 collection_manager_entry_list = g_list_append(collection_manager_entry_list, entry);
593 static void collect_manager_entry_free(CollectManagerEntry *entry)
595 collection_manager_entry_list = g_list_remove(collection_manager_entry_list, entry);
597 collect_manager_entry_free_data(entry);
603 static void collect_manager_entry_reset(CollectManagerEntry *entry)
605 collect_manager_entry_free_data(entry);
606 collect_manager_entry_init_data(entry);
609 static CollectManagerEntry *collect_manager_get_entry(const gchar *path)
613 work = collection_manager_entry_list;
616 CollectManagerEntry *entry;
620 if (strcmp(entry->path, path) == 0)
629 static void collect_manager_entry_add_action(CollectManagerEntry *entry, CollectManagerAction *action)
632 CollectManagerAction *orig_action;
634 entry->empty = FALSE;
636 if (action->oldpath == NULL)
639 if (action->newpath == NULL)
644 orig_action = g_hash_table_lookup(entry->newpath_hash, action->newpath);
647 /* target already exists */
648 log_printf("collection manager failed to add another action for target %s in collection %s\n",
649 action->newpath, entry->path);
652 entry->add_list = g_list_append(entry->add_list, action);
653 g_hash_table_insert(entry->newpath_hash, action->newpath, action);
654 collect_manager_action_ref(action);
658 orig_action = g_hash_table_lookup(entry->newpath_hash, action->oldpath);
661 /* new action with the same file */
662 CollectManagerAction *new_action = collect_manager_action_new(orig_action->oldpath, action->newpath, action->type);
664 if (new_action->oldpath)
666 g_hash_table_steal(entry->oldpath_hash, orig_action->oldpath);
667 g_hash_table_insert(entry->oldpath_hash, new_action->oldpath, new_action);
671 GList *work = g_list_find(entry->add_list, orig_action);
672 work->data = new_action;
675 g_hash_table_steal(entry->newpath_hash, orig_action->newpath);
676 if (new_action->newpath)
678 g_hash_table_insert(entry->newpath_hash, new_action->newpath, new_action);
680 collect_manager_action_unref(orig_action);
685 orig_action = g_hash_table_lookup(entry->oldpath_hash, action->oldpath);
688 /* another action for the same source, ignore */
689 log_printf("collection manager failed to add another action for source %s in collection %s\n",
690 action->oldpath, entry->path);
694 g_hash_table_insert(entry->oldpath_hash, action->oldpath, action);
697 g_hash_table_insert(entry->newpath_hash, action->newpath, action);
699 collect_manager_action_ref(action);
702 static gboolean collect_manager_process_action(CollectManagerEntry *entry, gchar **path_ptr)
704 gchar *path = *path_ptr;
705 CollectManagerAction *action;
712 action = entry->add_list->data;
713 g_assert(action->oldpath == NULL);
714 entry->add_list = g_list_remove(entry->add_list, action);
715 path = g_strdup(action->newpath);
716 g_hash_table_remove(entry->newpath_hash, path);
717 collect_manager_action_unref(action);
720 return (path != NULL);
723 action = g_hash_table_lookup(entry->oldpath_hash, path);
727 strcpy(*path_ptr, action->newpath);
731 return FALSE; /* no change */
734 static void collect_manager_refresh(void)
740 dir_fd = file_data_new_dir(get_collections_dir());
741 filelist_read(dir_fd, &list, NULL);
742 file_data_unref(dir_fd);
744 work = collection_manager_entry_list;
747 CollectManagerEntry *entry;
754 while (list_step && entry)
758 fd = list_step->data;
759 list_step = list_step->next;
761 if (strcmp(fd->path, entry->path) == 0)
763 list = g_list_remove(list, fd);
770 collect_manager_entry_free(entry);
785 collect_manager_entry_new(fd->path);
791 static void collect_manager_process_actions(gint max)
793 if (collection_manager_action_list) DEBUG_1("collection manager processing actions");
795 while (collection_manager_action_list != NULL && max > 0)
797 CollectManagerAction *action;
800 action = collection_manager_action_list->data;
801 work = collection_manager_entry_list;
804 CollectManagerEntry *entry;
809 if (action->type == COLLECTION_MANAGER_UPDATE)
811 collect_manager_entry_add_action(entry, action);
813 else if (action->oldpath && action->newpath &&
814 strcmp(action->newpath, entry->path) == 0)
816 /* convert action to standard add format */
817 g_free(action->newpath);
818 if (action->type == COLLECTION_MANAGER_ADD)
820 action->newpath = action->oldpath;
821 action->oldpath = NULL;
823 else if (action->type == COLLECTION_MANAGER_REMOVE)
825 action->newpath = NULL;
827 collect_manager_entry_add_action(entry, action);
833 if (action->type != COLLECTION_MANAGER_UPDATE &&
834 action->oldpath && action->newpath)
836 log_printf("collection manager failed to %s %s for collection %s\n",
837 (action->type == COLLECTION_MANAGER_ADD) ? "add" : "remove",
838 action->oldpath, action->newpath);
841 if (collection_manager_action_tail == collection_manager_action_list)
843 collection_manager_action_tail = NULL;
845 collection_manager_action_list = g_list_remove(collection_manager_action_list, action);
846 collect_manager_action_unref(action);
850 static gboolean collect_manager_process_entry(CollectManagerEntry *entry)
854 if (entry->empty) return FALSE;
856 cd = collection_new(entry->path);
857 (void) collection_load_private(cd, entry->path, COLLECTION_LOAD_NONE);
859 collection_unref(cd);
864 static gboolean collect_manager_process_entry_list(void)
868 work = collection_manager_entry_list;
871 CollectManagerEntry *entry;
875 if (collect_manager_process_entry(entry)) return TRUE;
883 static gboolean collect_manager_process_cb(gpointer data)
885 if (collection_manager_action_list) collect_manager_refresh();
886 collect_manager_process_actions(COLLECT_MANAGER_ACTIONS_PER_IDLE);
887 if (collection_manager_action_list) return TRUE;
889 if (collect_manager_process_entry_list()) return TRUE;
891 DEBUG_1("collection manager is up to date");
895 static gboolean collect_manager_timer_cb(gpointer data)
897 DEBUG_1("collection manager timer expired");
899 g_idle_add_full(G_PRIORITY_LOW, collect_manager_process_cb, NULL, NULL);
901 collection_manager_timer_id = 0;
905 static void collect_manager_timer_push(gint stop)
907 if (collection_manager_timer_id)
911 g_source_remove(collection_manager_timer_id);
912 collection_manager_timer_id = 0;
917 collection_manager_timer_id = g_timeout_add(COLLECT_MANAGER_FLUSH_DELAY,
918 collect_manager_timer_cb, NULL);
919 DEBUG_1("collection manager timer started");
923 static void collect_manager_add_action(CollectManagerAction *action)
927 /* we keep track of the list's tail to keep this a n(1) operation */
929 if (collection_manager_action_tail)
931 collection_manager_action_tail = g_list_append(collection_manager_action_tail, action);
932 collection_manager_action_tail = collection_manager_action_tail->next;
936 collection_manager_action_list = g_list_append(collection_manager_action_list, action);
937 collection_manager_action_tail = collection_manager_action_list;
940 collect_manager_timer_push(FALSE);
943 void collect_manager_moved(FileData *fd)
945 CollectManagerAction *action;
946 const gchar *oldpath = fd->change->source;
947 const gchar *newpath = fd->change->dest;
949 action = collect_manager_action_new(oldpath, newpath, COLLECTION_MANAGER_UPDATE);
950 collect_manager_add_action(action);
953 void collect_manager_add(FileData *fd, const gchar *collection)
955 CollectManagerAction *action;
958 if (!fd || !collection) return;
960 cw = collection_window_find_by_path(collection);
963 if (collection_list_find_fd(cw->cd->list, fd) == NULL)
965 collection_add(cw->cd, fd, FALSE);
970 action = collect_manager_action_new(fd->path, collection, COLLECTION_MANAGER_ADD);
971 collect_manager_add_action(action);
974 void collect_manager_remove(FileData *fd, const gchar *collection)
976 CollectManagerAction *action;
979 if (!fd || !collection) return;
981 cw = collection_window_find_by_path(collection);
984 while (collection_remove(cw->cd, fd));
988 action = collect_manager_action_new(fd->path, collection, COLLECTION_MANAGER_REMOVE);
989 collect_manager_add_action(action);
992 void collect_manager_flush(void)
994 collect_manager_timer_push(TRUE);
996 DEBUG_1("collection manager flushing");
997 while (collect_manager_process_cb(NULL));
1000 void collect_manager_notify_cb(FileData *fd, NotifyType type, gpointer data)
1002 if (!(type & NOTIFY_CHANGE) || !fd->change) return;
1004 DEBUG_1("Notify collect_manager: %s %04x", fd->path, type);
1005 switch (fd->change->type)
1007 case FILEDATA_CHANGE_MOVE:
1008 collect_manager_moved(fd);
1010 case FILEDATA_CHANGE_COPY:
1012 case FILEDATA_CHANGE_RENAME:
1013 collect_manager_moved(fd);
1015 case FILEDATA_CHANGE_DELETE:
1016 case FILEDATA_CHANGE_UNSPECIFIED:
1017 case FILEDATA_CHANGE_WRITE_METADATA:
1022 static gint collection_manager_sort_cb(gconstpointer a, gconstpointer b)
1024 const gchar *char_a = a;
1025 const gchar *char_b = b;
1027 return g_strcmp0(char_a, char_b);
1031 * @brief Creates sorted list of collections
1032 * @param[out] names_exc sorted list of collections names excluding extension
1033 * @param[out] names_inc sorted list of collections names including extension
1034 * @param[out] paths sorted list of collection paths
1036 * Lists of type gchar.
1037 * Used lists must be freed with string_list_free()
1039 void collect_manager_list(GList **names_exc, GList **names_inc, GList **paths)
1047 if (names_exc == NULL && names_inc == NULL && paths == NULL)
1052 dir_fd = file_data_new_dir((get_collections_dir()));
1054 filelist_read(dir_fd, &list, NULL);
1059 filename = g_strdup(filename_from_path((gchar *)fd->path));
1061 if (file_extension_match(filename, GQ_COLLECTION_EXT))
1063 name = remove_extension_from_path(filename);
1065 if (names_exc != NULL)
1067 *names_exc = g_list_insert_sorted(*names_exc, g_strdup(name),
1068 collection_manager_sort_cb);
1069 *names_exc = g_list_first(*names_exc);
1071 if (names_inc != NULL)
1073 *names_inc = g_list_insert_sorted(*names_inc,filename,
1074 collection_manager_sort_cb);
1075 *names_inc = g_list_first(*names_inc);
1079 *paths = g_list_insert_sorted(*paths,fd->path,
1080 collection_manager_sort_cb);
1081 *paths = g_list_first(*paths);
1089 filelist_free(list);
1091 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */