7 * This software is released under the GNU General Public License (GNU GPL).
8 * Please read the included file COPYING for more information.
9 * This software comes with no warranty of any kind, use at your own risk!
14 #include "collect-io.h"
18 #include "layout_util.h"
20 #include "secure_save.h"
22 #include "ui_fileops.h"
25 #define GQ_COLLECTION_MARKER "#" GQ_APPNAME
27 #define GQ_COLLECTION_FAIL_MIN 300
28 #define GQ_COLLECTION_FAIL_PERCENT 98
30 typedef struct _CollectManagerEntry CollectManagerEntry;
32 static void collection_load_thumb_step(CollectionData *cd);
33 static gint collection_save_private(CollectionData *cd, const gchar *path);
35 static CollectManagerEntry *collect_manager_get_entry(const gchar *path);
36 static void collect_manager_entry_reset(CollectManagerEntry *entry);
37 static gint collect_manager_process_action(CollectManagerEntry *entry, gchar **path_ptr);
40 static gint scan_geometry(gchar *buffer, gint *x, gint *y, gint *w, gint *h)
44 if(sscanf(buffer, "%d %d %d %d", &nx, &ny, &nw, &nh) != 4) return FALSE;
54 static gint collection_load_private(CollectionData *cd, const gchar *path, gint append, gint flush)
59 gint official = FALSE;
63 gboolean changed = FALSE;
64 CollectManagerEntry *entry = NULL;
66 collection_load_stop(cd);
69 collect_manager_flush();
71 entry = collect_manager_get_entry(path);
75 collection_list_free(cd->list);
79 if (!path && !cd->path) return FALSE;
81 if (!path) path = cd->path;
84 pathl = path_from_utf8(path);
85 f = fopen(pathl, "r");
89 printf("Failed to open collection file: \"%s\"\n", path);
93 while (fgets(s_buf, sizeof(s_buf), f))
98 if (strncasecmp(s_buf, GQ_COLLECTION_MARKER, strlen(GQ_COLLECTION_MARKER)) == 0)
100 /* Looks like an official collection, allow unchecked input.
101 * All this does is allow adding files that may not exist,
102 * which is needed for the collection manager to work.
103 * Also unofficial files abort after too many invalid entries.
107 else if (strncmp(s_buf, "#geometry:", 10 ) == 0 &&
108 scan_geometry(s_buf + 10, &cd->window_x, &cd->window_y, &cd->window_w, &cd->window_h) )
110 cd->window_read = TRUE;
114 if (s_buf[0]=='\n') continue;
116 buf = quoted_value(s_buf, NULL);
122 changed |= collect_manager_process_action(entry, &buf);
124 valid = (buf[0] == '/' && collection_add_check(cd, file_data_new_simple(buf), FALSE, TRUE));
128 if (!valid && !official)
131 if (fail > GQ_COLLECTION_FAIL_MIN &&
132 fail * 100 / total > GQ_COLLECTION_FAIL_PERCENT)
134 printf("Too many invalid filenames in unoffical collection file, closing: %s\n", path);
147 while (collect_manager_process_action(entry, &buf))
149 collection_add_check(cd, file_data_new_simple(buf), FALSE, TRUE);
155 cd->list = collection_list_sort(cd->list, cd->sort_method);
157 if (!flush && changed && success)
158 collection_save_private(cd, path);
161 collect_manager_entry_reset(entry);
163 if (!append) cd->changed = FALSE;
168 gint collection_load(CollectionData *cd, const gchar *path, gint append)
170 if (collection_load_private(cd, path, append, TRUE))
172 layout_recent_add_path(cd->path);
179 static void collection_load_thumb_do(CollectionData *cd)
183 if (!cd->thumb_loader || !g_list_find(cd->list, cd->thumb_info)) return;
185 pixbuf = thumb_loader_get_pixbuf(cd->thumb_loader, TRUE);
186 collection_info_set_thumb(cd->thumb_info, pixbuf);
187 g_object_unref(pixbuf);
189 if (cd->info_updated_func) cd->info_updated_func(cd, cd->thumb_info, cd->info_updated_data);
192 static void collection_load_thumb_error_cb(ThumbLoader *tl, gpointer data)
194 CollectionData *cd = data;
196 collection_load_thumb_do(cd);
197 collection_load_thumb_step(cd);
200 static void collection_load_thumb_done_cb(ThumbLoader *tl, gpointer data)
202 CollectionData *cd = data;
204 collection_load_thumb_do(cd);
205 collection_load_thumb_step(cd);
208 static void collection_load_thumb_step(CollectionData *cd)
215 collection_load_stop(cd);
222 /* find first unloaded thumb */
223 while (work && ci->pixbuf)
229 if (!ci || ci->pixbuf)
232 collection_load_stop(cd);
234 /* send a NULL CollectInfo to notify end */
235 if (cd->info_updated_func) cd->info_updated_func(cd, NULL, cd->info_updated_data);
240 /* setup loader and call it */
242 thumb_loader_free(cd->thumb_loader);
243 cd->thumb_loader = thumb_loader_new(options->thumb_max_width, options->thumb_max_height);
244 thumb_loader_set_callbacks(cd->thumb_loader,
245 collection_load_thumb_done_cb,
246 collection_load_thumb_error_cb,
251 if (!thumb_loader_start(cd->thumb_loader, ci->fd->path))
253 /* error, handle it, do next */
254 if (debug) printf("error loading thumb for %s\n", ci->fd->path);
255 collection_load_thumb_do(cd);
256 collection_load_thumb_step(cd);
260 void collection_load_thumb_idle(CollectionData *cd)
262 if (!cd->thumb_loader) collection_load_thumb_step(cd);
265 gint collection_load_begin(CollectionData *cd, const gchar *path, gint append)
267 if (!collection_load(cd, path, append)) return FALSE;
269 collection_load_thumb_idle(cd);
274 void collection_load_stop(CollectionData *cd)
276 if (!cd->thumb_loader) return;
278 thumb_loader_free(cd->thumb_loader);
279 cd->thumb_loader = NULL;
282 static gint collection_save_private(CollectionData *cd, const gchar *path)
288 if (!path && !cd->path) return FALSE;
296 pathl = path_from_utf8(path);
297 ssi = secure_open(pathl);
303 buf = g_strdup_printf(_("failed to open collection (write) \"%s\"\n"), path);
309 secure_fprintf(ssi, "%s collection\n", GQ_COLLECTION_MARKER);
310 secure_fprintf(ssi, "#created with %s version %s\n", GQ_APPNAME, VERSION);
312 collection_update_geometry(cd);
315 secure_fprintf(ssi, "#geometry: %d %d %d %d\n", cd->window_x, cd->window_y, cd->window_w, cd->window_h);
319 while (work && secsave_errno == SS_ERR_NONE)
321 CollectInfo *ci = work->data;
322 secure_fprintf(ssi, "\"%s\"\n", ci->fd->path);
326 secure_fprintf(ssi, "#end\n");
328 if (secure_close(ssi))
332 buf = g_strdup_printf(_("error saving collection file: %s\nerror: %s\n"), path,
333 secsave_strerror(secsave_errno));
339 if (!cd->path || strcmp(path, cd->path) != 0)
341 gchar *buf = cd->path;
342 cd->path = g_strdup(path);
347 cd->name = g_strdup(filename_from_path(cd->path));
349 collection_path_changed(cd);
357 gint collection_save(CollectionData *cd, const gchar *path)
359 if (collection_save_private(cd, path))
361 layout_recent_add_path(cd->path);
368 gint collection_load_only_geometry(CollectionData *cd, const gchar *path)
374 if (!path && !cd->path) return FALSE;
376 if (!path) path = cd->path;
379 pathl = path_from_utf8(path);
380 f = fopen(pathl, "r");
382 if (!f) return FALSE;
384 while (fgets(s_buf, sizeof(s_buf), f))
387 strncmp(s_buf, "#geometry:", 10 ) == 0 &&
388 scan_geometry(s_buf + 10, &cd->window_x, &cd->window_y, &cd->window_w, &cd->window_h) )
390 cd->window_read = TRUE;
401 *-------------------------------------------------------------------
403 *-------------------------------------------------------------------
406 #define COLLECT_MANAGER_ACTIONS_PER_IDLE 1000
407 #define COLLECT_MANAGER_FLUSH_DELAY 10000
409 struct _CollectManagerEntry
413 GHashTable *oldpath_hash;
414 GHashTable *newpath_hash;
419 COLLECTION_MANAGER_UPDATE,
420 COLLECTION_MANAGER_ADD,
421 COLLECTION_MANAGER_REMOVE
422 } CollectManagerType;
424 typedef struct _CollectManagerAction CollectManagerAction;
425 struct _CollectManagerAction
430 CollectManagerType type;
436 static GList *collection_manager_entry_list = NULL;
437 static GList *collection_manager_action_list = NULL;
438 static GList *collection_manager_action_tail = NULL;
439 static gint collection_manager_timer_id = -1;
442 static CollectManagerAction *collect_manager_action_new(const gchar *oldpath, const gchar *newpath,
443 CollectManagerType type)
445 CollectManagerAction *action;
447 action = g_new0(CollectManagerAction, 1);
450 action->oldpath = g_strdup(oldpath);
451 action->newpath = g_strdup(newpath);
458 static void collect_manager_action_ref(CollectManagerAction *action)
463 static void collect_manager_action_unref(CollectManagerAction *action)
467 if (action->ref > 0) return;
469 g_free(action->oldpath);
470 g_free(action->newpath);
474 static void collect_manager_entry_free_data(CollectManagerEntry *entry)
478 work = entry->add_list;
481 CollectManagerAction *action;
486 collect_manager_action_unref(action);
488 g_list_free(entry->add_list);
489 g_hash_table_destroy(entry->oldpath_hash);
490 g_hash_table_destroy(entry->newpath_hash);
493 static void collect_manager_entry_init_data(CollectManagerEntry *entry)
495 entry->add_list = NULL;
496 entry->oldpath_hash = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify) collect_manager_action_unref);
497 entry->newpath_hash = g_hash_table_new(g_str_hash, g_str_equal);
502 static CollectManagerEntry *collect_manager_entry_new(const gchar *path)
504 CollectManagerEntry *entry;
506 entry = g_new0(CollectManagerEntry, 1);
507 entry->path = g_strdup(path);
508 collect_manager_entry_init_data(entry);
510 collection_manager_entry_list = g_list_append(collection_manager_entry_list, entry);
516 static void collect_manager_entry_free(CollectManagerEntry *entry)
518 collection_manager_entry_list = g_list_remove(collection_manager_entry_list, entry);
520 collect_manager_entry_free_data(entry);
526 static void collect_manager_entry_reset(CollectManagerEntry *entry)
528 collect_manager_entry_free_data(entry);
529 collect_manager_entry_init_data(entry);
532 static CollectManagerEntry *collect_manager_get_entry(const gchar *path)
536 work = collection_manager_entry_list;
539 CollectManagerEntry *entry;
543 if (strcmp(entry->path, path) == 0)
552 static void collect_manager_entry_add_action(CollectManagerEntry *entry, CollectManagerAction *action)
555 CollectManagerAction *orig_action;
557 entry->empty = FALSE;
559 if (action->oldpath == NULL)
562 if (action->newpath == NULL)
567 orig_action = g_hash_table_lookup(entry->newpath_hash, action->newpath);
570 /* target already exists */
571 printf("collection manager failed to add another action for target %s in collection %s\n",
572 action->newpath, entry->path);
575 entry->add_list = g_list_append(entry->add_list, action);
576 g_hash_table_insert(entry->newpath_hash, action->newpath, action);
577 collect_manager_action_ref(action);
581 orig_action = g_hash_table_lookup(entry->newpath_hash, action->oldpath);
584 /* new action with the same file */
585 CollectManagerAction *new_action = collect_manager_action_new(orig_action->oldpath, action->newpath, action->type);
587 if (new_action->oldpath)
589 g_hash_table_steal(entry->oldpath_hash, orig_action->oldpath);
590 g_hash_table_insert(entry->oldpath_hash, new_action->oldpath, new_action);
594 GList *work = g_list_find(entry->add_list, orig_action);
595 work->data = new_action;
598 g_hash_table_steal(entry->newpath_hash, orig_action->newpath);
599 if (new_action->newpath)
601 g_hash_table_insert(entry->newpath_hash, new_action->newpath, new_action);
603 collect_manager_action_unref(orig_action);
608 orig_action = g_hash_table_lookup(entry->oldpath_hash, action->oldpath);
611 /* another action for the same source, ignore */
612 printf("collection manager failed to add another action for source %s in collection %s\n",
613 action->oldpath, entry->path);
617 g_hash_table_insert(entry->oldpath_hash, action->oldpath, action);
620 g_hash_table_insert(entry->newpath_hash, action->newpath, action);
622 collect_manager_action_ref(action);
625 static gint collect_manager_process_action(CollectManagerEntry *entry, gchar **path_ptr)
627 gchar *path = *path_ptr;
628 CollectManagerAction *action;
635 action = entry->add_list->data;
636 g_assert(action->oldpath == NULL);
637 entry->add_list = g_list_remove(entry->add_list, action);
638 path = g_strdup(action->newpath);
639 g_hash_table_remove(entry->newpath_hash, path);
640 collect_manager_action_unref(action);
643 return (path != NULL);
646 action = g_hash_table_lookup(entry->oldpath_hash, path);
651 path = g_strdup(action->newpath);
656 return FALSE; /* no change */
659 static void collect_manager_refresh(void)
665 base = g_strconcat(homedir(), "/", GQ_RC_DIR_COLLECTIONS, NULL);
666 path_list(base, &list, NULL);
669 work = collection_manager_entry_list;
672 CollectManagerEntry *entry;
679 while (list_step && entry)
683 path = list_step->data;
684 list_step = list_step->next;
686 if (strcmp(path, entry->path) == 0)
688 list = g_list_remove(list, path);
695 collect_manager_entry_free(entry);
708 collect_manager_entry_new(path);
715 static void collect_manager_process_actions(gint max)
717 if (debug && collection_manager_action_list)
719 printf("collection manager processing actions\n");
722 while (collection_manager_action_list != NULL && max > 0)
724 CollectManagerAction *action;
727 action = collection_manager_action_list->data;
728 work = collection_manager_entry_list;
731 CollectManagerEntry *entry;
736 if (action->type == COLLECTION_MANAGER_UPDATE)
738 collect_manager_entry_add_action(entry, action);
740 else if (action->oldpath && action->newpath &&
741 strcmp(action->newpath, entry->path) == 0)
743 /* convert action to standard add format */
744 g_free(action->newpath);
745 if (action->type == COLLECTION_MANAGER_ADD)
747 action->newpath = action->oldpath;
748 action->oldpath = NULL;
750 else if (action->type == COLLECTION_MANAGER_REMOVE)
752 action->newpath = NULL;
754 collect_manager_entry_add_action(entry, action);
760 if (action->type != COLLECTION_MANAGER_UPDATE &&
761 action->oldpath && action->newpath)
763 printf("collection manager failed to %s %s for collection %s\n",
764 (action->type == COLLECTION_MANAGER_ADD) ? "add" : "remove",
765 action->oldpath, action->newpath);
768 if (collection_manager_action_tail == collection_manager_action_list)
770 collection_manager_action_tail = NULL;
772 collection_manager_action_list = g_list_remove(collection_manager_action_list, action);
773 collect_manager_action_unref(action);
777 static gint collect_manager_process_entry(CollectManagerEntry *entry)
782 if (entry->empty) return FALSE;
784 cd = collection_new(entry->path);
785 success = collection_load_private(cd, entry->path, FALSE, FALSE);
787 collection_unref(cd);
792 static gint collect_manager_process_entry_list(void)
796 work = collection_manager_entry_list;
799 CollectManagerEntry *entry;
803 if (collect_manager_process_entry(entry)) return TRUE;
811 static gint collect_manager_process_cb(gpointer data)
813 if (collection_manager_action_list) collect_manager_refresh();
814 collect_manager_process_actions(COLLECT_MANAGER_ACTIONS_PER_IDLE);
815 if (collection_manager_action_list) return TRUE;
817 if (collect_manager_process_entry_list()) return TRUE;
819 if (debug) printf("collection manager is up to date\n");
823 static gint collect_manager_timer_cb(gpointer data)
825 if (debug) printf("collection manager timer expired\n");
827 g_idle_add_full(G_PRIORITY_LOW, collect_manager_process_cb, NULL, NULL);
829 collection_manager_timer_id = -1;
833 static void collect_manager_timer_push(gint stop)
835 if (collection_manager_timer_id != -1)
839 g_source_remove(collection_manager_timer_id);
840 collection_manager_timer_id = -1;
845 collection_manager_timer_id = g_timeout_add(COLLECT_MANAGER_FLUSH_DELAY,
846 collect_manager_timer_cb, NULL);
847 if (debug) printf("collection manager timer started\n");
851 static void collect_manager_add_action(CollectManagerAction *action)
855 /* we keep track of the list's tail to keep this a n(1) operation */
857 if (collection_manager_action_tail)
859 collection_manager_action_tail = g_list_append(collection_manager_action_tail, action);
860 collection_manager_action_tail = collection_manager_action_tail->next;
864 collection_manager_action_list = g_list_append(collection_manager_action_list, action);
865 collection_manager_action_tail = collection_manager_action_list;
868 collect_manager_timer_push(FALSE);
871 void collect_manager_moved(FileData *fd)
873 CollectManagerAction *action;
874 const gchar *oldpath = fd->change->source;
875 const gchar *newpath = fd->change->dest;
877 action = collect_manager_action_new(oldpath, newpath, COLLECTION_MANAGER_UPDATE);
878 collect_manager_add_action(action);
881 void collect_manager_add(FileData *fd, const gchar *collection)
883 CollectManagerAction *action;
886 if (!fd || !collection) return;
888 cw = collection_window_find_by_path(collection);
891 if (collection_list_find(cw->cd->list, fd->path) == NULL)
893 collection_add(cw->cd, fd, FALSE);
898 action = collect_manager_action_new(fd->path, collection, COLLECTION_MANAGER_ADD);
899 collect_manager_add_action(action);
902 void collect_manager_remove(FileData *fd, const gchar *collection)
904 CollectManagerAction *action;
907 if (!fd || !collection) return;
909 cw = collection_window_find_by_path(collection);
912 while (collection_remove(cw->cd, fd));
916 action = collect_manager_action_new(fd->path, collection, COLLECTION_MANAGER_REMOVE);
917 collect_manager_add_action(action);
920 void collect_manager_flush(void)
922 collect_manager_timer_push(TRUE);
924 if (debug) printf("collection manager flushing\n");
925 while (collect_manager_process_cb(NULL));