/* This file is part of GNUnet. (C) 2010, 2011, 2012 Christian Grothoff (and other contributing authors) GNUnet is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNUnet; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /** * @file src/fs/gnunet-fs-gtk-event_handler.c * @brief Main event handler for file-sharing * @author Christian Grothoff */ #include "gnunet-fs-gtk.h" #include "gnunet-fs-gtk-download.h" #include "gnunet-fs-gtk-event_handler.h" #include static struct SearchTab *search_tab_head; static struct SearchTab *search_tab_tail; static struct SearchTab *uri_tab; struct PublishTab { /** * Frame for the tab. */ GtkWidget *frame; /** * Associated builder. */ GtkBuilder *builder; /** * Associated tree store. */ GtkTreeStore *ts; }; struct PublishEntry { /** * Associated FS publish operation. */ struct GNUNET_FS_PublishContext *pc; /** * Tab storing this entry. */ struct PublishTab *tab; /** * Where in the tab is this entry? */ GtkTreeRowReference *rr; /** * URI of the file (set after completion). */ struct GNUNET_FS_Uri *uri; int is_top; }; struct SearchResult { /** * Where in the tab is this result? */ GtkTreeRowReference *rr; /** * Tab storing this result. */ struct SearchTab *tab; /** * Search result for top-level results and * namespace-update results. */ struct GNUNET_FS_SearchResult *result; /** * Associated download, or NULL for none. */ struct DownloadEntry *download; }; struct StartDownloadContext { struct SearchTab *tab; gboolean recursive; }; static struct PublishTab *publish_tab; /** * Row reference for the current search context menu. */ static GtkTreeRowReference *current_context_row_reference; /** * Search tab used for the current search context menu. */ static struct SearchTab *current_context_search_tab; static void start_download_ctx_menu (GtkMenuItem *item, gpointer user_data); static void start_download_recursively_ctx_menu (GtkMenuItem *item, gpointer user_data); static void abort_download_ctx_menu (GtkMenuItem *item, gpointer user_data); static void copy_uri_to_clipboard_ctx_menu (GtkMenuItem *item, gpointer user_data); static void free_search_result (struct SearchResult *sr); void search_list_popup_selection_done (GtkMenuShell *menushell, gpointer user_data) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Item selected in menu shell %x\n", menushell); gtk_widget_destroy (GTK_WIDGET (menushell)); } static gboolean search_list_popup (GtkTreeView *tv, struct SearchTab *tab, GdkEventButton *event_button) { GtkMenu *menu; GtkWidget *child; GtkTreeSelection *sel; GtkTreePath *path; GtkTreeModel *tm; GtkTreeIter iter; struct SearchResult *sr; struct GNUNET_FS_Uri *uri; gboolean got_selection; gint init_button; guint32 event_time; current_context_search_tab = tab; if (current_context_row_reference != NULL) { gtk_tree_row_reference_free (current_context_row_reference); current_context_row_reference = NULL; } path = NULL; if (event_button != NULL) { got_selection = gtk_tree_view_get_path_at_pos (tv, event_button->x, event_button->y, &path, NULL, NULL, NULL); if (got_selection) { tm = gtk_tree_view_get_model (tv); got_selection = gtk_tree_model_get_iter (tm, &iter, path); current_context_row_reference = gtk_tree_row_reference_new (tm, path); gtk_tree_path_free (path); } init_button = event_button->button; event_time = event_button->time; } else { sel = gtk_tree_view_get_selection (tv); got_selection = gtk_tree_selection_get_selected (sel, &tm, &iter); path = gtk_tree_model_get_path (tm, &iter); current_context_row_reference = gtk_tree_row_reference_new (tm, path); gtk_tree_path_free (path); init_button = 0; event_time = gtk_get_current_event_time (); } if (!got_selection) { /* nothing selected or model empty? */ current_context_search_tab = NULL; return FALSE; } gtk_tree_model_get (tm, &iter, 1, &uri, 9, &sr, -1); menu = GTK_MENU (gtk_menu_new ()); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Creating a menu for SR=%p, DE=%p\n", sr, sr->download); if (sr->download == NULL) { if (NULL != uri) { /* only display download menus if there is a URI */ child = gtk_menu_item_new_with_label (_("_Download")); g_signal_connect (child, "activate", G_CALLBACK (start_download_ctx_menu), NULL); gtk_label_set_use_underline (GTK_LABEL (gtk_bin_get_child (GTK_BIN (child))), TRUE); gtk_widget_show (child); gtk_menu_shell_append (GTK_MENU_SHELL (menu), child); child = gtk_menu_item_new_with_label (_("Download _recursively")); g_signal_connect (child, "activate", G_CALLBACK (start_download_recursively_ctx_menu), NULL); gtk_label_set_use_underline (GTK_LABEL (gtk_bin_get_child (GTK_BIN (child))), TRUE); gtk_widget_show (child); gtk_menu_shell_append (GTK_MENU_SHELL (menu), child); } } else { child = gtk_menu_item_new_with_label (_("_Abort download")); g_signal_connect (child, "activate", G_CALLBACK (abort_download_ctx_menu), sr->download); gtk_label_set_use_underline (GTK_LABEL (gtk_bin_get_child (GTK_BIN (child))), TRUE); gtk_widget_show (child); gtk_menu_shell_append (GTK_MENU_SHELL (menu), child); } child = gtk_menu_item_new_with_label (_("_Copy URI to Clipboard")); g_signal_connect (child, "activate", G_CALLBACK (copy_uri_to_clipboard_ctx_menu), NULL); gtk_label_set_use_underline (GTK_LABEL (gtk_bin_get_child (GTK_BIN (child))), TRUE); gtk_widget_show (child); g_signal_connect (menu, "selection-done", G_CALLBACK (search_list_popup_selection_done), NULL); gtk_menu_shell_append (GTK_MENU_SHELL (menu), child); gtk_menu_popup (menu, NULL, NULL, NULL, NULL, init_button, event_time); return TRUE; } static void closure_notify_free (gpointer data, GClosure *closure) { GNUNET_free (data); } static struct DownloadEntry * change_download_colour (struct DownloadEntry *de, const char *colour) { GtkTreeIter iter; GtkTreePath *path; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Changing download DE=%p color to %s\n", de, colour); path = gtk_tree_row_reference_get_path (de->rr); GNUNET_assert (NULL != path); if (TRUE != gtk_tree_model_get_iter (GTK_TREE_MODEL (de->ts), &iter, path)) { GNUNET_break (0); gtk_tree_path_free (path); return de; } gtk_tree_path_free (path); gtk_tree_store_set (de->ts, &iter, 8, colour, -1); return de; } static struct PublishEntry * change_publish_colour (struct PublishEntry *pe, const char *colour) { GtkTreeIter iter; GtkTreePath *path; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Changing publish PE=%p color to %s\n", pe, colour); if (pe == NULL) { GNUNET_break (0); return NULL; } path = gtk_tree_row_reference_get_path (pe->rr); if (TRUE != gtk_tree_model_get_iter (GTK_TREE_MODEL (pe->tab->ts), &iter, path)) { GNUNET_break (0); gtk_tree_path_free (path); return pe; } gtk_tree_path_free (path); gtk_tree_store_set (pe->tab->ts, &iter, 2, colour, -1); return pe; } static void stop_download (struct DownloadEntry *de, int is_suspend) { GtkTreeIter iter; GtkTreePath *path; GtkTreeModel *tm; struct SearchResult *search_result; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Stopping download DE=%p, %s\n", de, is_suspend ? "temporarily" : "permanently"); path = gtk_tree_row_reference_get_path (de->rr); tm = gtk_tree_row_reference_get_model (de->rr); if (path != NULL) { if (TRUE != gtk_tree_model_get_iter (tm, &iter, path)) GNUNET_break (0); else { gtk_tree_model_get (tm, &iter, 9, &search_result, -1); /*Always fails on downloads started by Download URI */ /*GNUNET_assert (search_result->download == de); */ search_result->download = NULL; if (NULL == search_result->result) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Removing it from the tree\n"); (void) gtk_tree_store_remove (GTK_TREE_STORE (tm), &iter); } else change_download_colour (de, "white"); } gtk_tree_path_free (path); } gtk_tree_row_reference_free (de->rr); GNUNET_FS_uri_destroy (de->uri); GNUNET_CONTAINER_meta_data_destroy (de->meta); GNUNET_free (de); } /** * Closure for 'add_directory_entry'. */ struct AddDirectoryEntryContext { /** * */ struct DownloadEntry *de; /** * Row reference of parent (the directory). */ GtkTreeRowReference *prr; /** * Do we need to check if the given entry already exists to * avoid adding it twice? Set to YES if 'add_directory_entry' * is called upon directory completion (so we might see all * entries again) and to NO if this is the initial download * and we're calling during a 'PROGRESS' event. */ int check_duplicates; }; /** * Function used to process entries in a directory. * * @param cls closure, our 'struct AddDirectoryEntryContext*' * @param filename name of the file in the directory * @param uri URI of the file * @param metadata metadata for the file; metadata for * the directory if everything else is NULL/zero * @param length length of the available data for the file * (of type size_t since data must certainly fit * into memory; if files are larger than size_t * permits, then they will certainly not be * embedded with the directory itself). * @param data data available for the file (length bytes) */ static void add_directory_entry (void *cls, const char *filename, const struct GNUNET_FS_Uri *uri, const struct GNUNET_CONTAINER_MetaData *meta, size_t length, const void *data) { struct AddDirectoryEntryContext *ade = cls; GtkTreeIter iter; GtkTreeIter piter; GtkTreePath *path; GtkTreeModel *tm; struct GNUNET_FS_Uri *xuri; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Adding directory entry `%s'\n", filename); if (uri == NULL) { /* directory meta data itself */ /* FIXME: consider merging it in... */ return; } if (ade->check_duplicates == GNUNET_YES) { path = gtk_tree_row_reference_get_path (ade->prr); tm = gtk_tree_row_reference_get_model (ade->prr); if (TRUE != gtk_tree_model_get_iter (tm, &piter, path)) { GNUNET_break (0); gtk_tree_path_free (path); return; } gtk_tree_path_free (path); if (TRUE == gtk_tree_model_iter_children (tm, &iter, &piter)) { do { gtk_tree_model_get (tm, &iter, 1, &xuri, -1); if (GNUNET_YES == GNUNET_FS_uri_test_equal (xuri, uri)) return; /* already present */ } while (TRUE == gtk_tree_model_iter_next (tm, &iter)); } } GNUNET_GTK_add_search_result (ade->de->tab, &iter, ade->prr, uri, meta, NULL, 0); } static struct DownloadEntry * mark_download_progress (struct DownloadEntry *de, uint64_t size, uint64_t completed, const void *block_data, uint64_t offset, uint64_t block_size, unsigned int depth) { GtkTreeIter iter; GtkTreePath *path; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Marking download progress for DE=%p, %llu/%llu, %llu@%llu depth=%u\n", de, completed, size, block_size, offset, depth); path = gtk_tree_row_reference_get_path (de->rr); if (TRUE != gtk_tree_model_get_iter (GTK_TREE_MODEL (de->ts), &iter, path)) { GNUNET_break (0); gtk_tree_path_free (path); return de; } gtk_tree_path_free (path); gtk_tree_store_set (de->ts, &iter, 4, (guint) ((size > 0) ? (100 * completed / size) : 100) /* progress */ , 14, completed, -1); if ((depth == 0) && (block_size > 0) && (GNUNET_YES == GNUNET_FS_meta_data_test_for_directory (de->meta))) { struct AddDirectoryEntryContext ade; ade.de = de; ade.prr = de->rr; ade.check_duplicates = GNUNET_NO; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "It is a directory, list its contents\n"); if (GNUNET_SYSERR == GNUNET_FS_directory_list_contents ((size_t) block_size, block_data, offset, &add_directory_entry, &ade)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Metadata wrongly claims that this is a GNUnet directory!\n")); } } return de; } static struct DownloadEntry * mark_download_error (struct DownloadEntry *de, const char *emsg) { GtkTreeIter iter; GtkTreePath *path; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Marking download error for DE=%p: %s\n", de, emsg); de = change_download_colour (de, "red"); de->is_done = GNUNET_YES; path = gtk_tree_row_reference_get_path (de->rr); if (TRUE != gtk_tree_model_get_iter (GTK_TREE_MODEL (de->tab->ts), &iter, path)) { GNUNET_break (0); gtk_tree_path_free (path); return de; } gtk_tree_path_free (path); gtk_tree_store_set (de->tab->ts, &iter, 4, 0, 7, emsg, -1); return de; } static struct DownloadEntry * mark_download_completed (struct DownloadEntry *de, uint64_t size, const char *filename) { struct AddDirectoryEntryContext ade; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Marking download completed for DE=%p, %llu-byte `%s'\n", de, size, filename); de->is_done = GNUNET_YES; (void) mark_download_progress (de, size, size, NULL, 0, 0, 0); if ((GNUNET_YES == GNUNET_FS_meta_data_test_for_directory (de->meta)) && (filename != NULL)) { ade.de = de; ade.prr = de->rr; ade.check_duplicates = GNUNET_YES; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "It is a directory, scan its contents\n"); GNUNET_FS_GTK_mmap_and_scan (filename, &add_directory_entry, &ade); } (void) change_download_colour (de, "green"); return de; } static struct PublishEntry * mark_publish_progress (struct PublishEntry *pe, uint64_t size, uint64_t completed) { GtkTreeIter iter; GtkTreePath *path; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Marking publicaation progress for PE=%p, %llu/%llu\n", pe, completed, size); path = gtk_tree_row_reference_get_path (pe->rr); if (TRUE != gtk_tree_model_get_iter (GTK_TREE_MODEL (pe->tab->ts), &iter, path)) { GNUNET_break (0); gtk_tree_path_free (path); return pe; } gtk_tree_path_free (path); gtk_tree_store_set (pe->tab->ts, &iter, 3, (guint) ((size > 0) ? (100 * completed / size) : 100) /* progress */ , -1); return pe; } /** * Move (aka copy) all of the children of 'src_iter' from the 'src_model' * to become children of 'dst_iter' in the 'dst_model'. * * The models are both 'GNUNET_GTK_file_sharing_result_tree_store' models. * * Note that we also need to update the 'struct SearchResult' * and (if it exists) the respective 'struct DownloadEntry' * to refer to the new model. */ static void move_children (GtkTreeModel * src_model, GtkTreeIter * src_iter, GtkTreeModel * dst_model, GtkTreeIter * dst_iter) { GtkTreeIter src_child; GtkTreeIter dst_child; GtkTreePath *path; struct GNUNET_CONTAINER_MetaData *meta; struct GNUNET_FS_Uri *uri; guint64 filesize, completed; GdkPixbuf *preview; guint percent_progress; guint percent_availability; gchar *filename; gchar *uri_as_string; gchar *status_colour; struct SearchResult *search_result; gchar *mimetype; guint applicability_rank; guint availability_certainty; gint availability_rank; gchar *downloaded_filename; gint downloaded_anonymity; if (TRUE == gtk_tree_model_iter_children (src_model, &src_child, src_iter)) { do { gtk_tree_model_get (src_model, &src_child, 0, &meta, 1, &uri, 2, &filesize, 3, &preview, 4, &percent_progress, 5, &percent_availability, 6, &filename, 7, &uri_as_string, 8, &status_colour, 9, &search_result, 10, &mimetype, 11, &applicability_rank, 12, &availability_certainty, 13, &availability_rank, 14, &completed, 15, &downloaded_filename, 16, &downloaded_anonymity, -1); gtk_tree_store_insert_with_values (GTK_TREE_STORE (dst_model), &dst_child, dst_iter, G_MAXINT, 0, meta, 1, uri, 2, filesize, 3, preview, 4, percent_progress, 5, percent_availability, 6, filename, 7, uri_as_string, 8, status_colour, 9, search_result, 10, mimetype, 11, applicability_rank, 12, availability_certainty, 13, availability_rank, 14, completed, 15, downloaded_filename, 16, downloaded_anonymity, -1); g_free (filename); g_free (downloaded_filename); g_free (uri_as_string); g_free (status_colour); g_free (mimetype); if (preview != NULL) g_object_unref (preview); gtk_tree_row_reference_free (search_result->rr); path = gtk_tree_model_get_path (dst_model, &dst_child); search_result->rr = gtk_tree_row_reference_new (dst_model, path); search_result->result = NULL; gtk_tree_path_free (path); if (search_result->download != NULL) { search_result->download->ts = GTK_TREE_STORE (dst_model); gtk_tree_row_reference_free (search_result->download->rr); search_result->download->rr = gtk_tree_row_reference_copy (search_result->rr); } move_children (src_model, &src_child, dst_model, &dst_child); } while (TRUE == gtk_tree_model_iter_next (src_model, &src_child)); } } /** * Delete the entire given subtree from the model. * Does not free anything inside of the respective * model's fields (since they have been moved). */ static void delete_stale_subtree (GtkTreeModel * model, GtkTreeIter * iter) { GtkTreeIter child; while (TRUE == gtk_tree_model_iter_children (model, &child, iter)) delete_stale_subtree (model, &child); gtk_tree_store_remove (GTK_TREE_STORE (model), iter); } /** * Handle the case where an active download lost its * search parent by moving it to the URI tab. */ static struct DownloadEntry * download_lost_parent (struct DownloadEntry *de, uint64_t size, uint64_t completed, int is_active) { GtkTreeIter iter; GtkTreePath *path; struct SearchTab *tab; GtkTreeRowReference *rr_old; GtkTreeModel *tm_old; GtkTreeIter iter_old; GtkTreeIter child; GtkTreeModel *model; rr_old = de->rr; tab = GNUNET_GTK_add_to_uri_tab (&iter, &de->sr, de->meta, de->uri); de->sr->download = de; de->ts = tab->ts; model = GTK_TREE_MODEL (de->ts); path = gtk_tree_model_get_path (model, &iter); de->rr = gtk_tree_row_reference_new (model, path); gtk_tree_path_free (path); mark_download_progress (de, size, completed, NULL, 0, 0, 0); tm_old = gtk_tree_row_reference_get_model (rr_old); path = gtk_tree_row_reference_get_path (rr_old); gtk_tree_row_reference_free (rr_old); if (NULL == path) { GNUNET_break (0); return NULL; } if (TRUE != gtk_tree_model_get_iter (tm_old, &iter_old, path)) { GNUNET_break (0); gtk_tree_path_free (path); return NULL; } gtk_tree_path_free (path); move_children (tm_old, &iter_old, model, &iter); while (TRUE == gtk_tree_model_iter_children (model, &child, &iter)) delete_stale_subtree (model, &child); if (size > completed) { if (is_active) change_download_colour (de, "yellow"); else change_download_colour (de, "blue"); } else { change_download_colour (de, "green"); } return de; } /** * Setup a new download entry. * * @param de existing download entry for the download, or NULL * @param pde parent download entry, or NULL * @param sr search result, or NULL * @param dc download context (for stopping) * @param uri the URI * @param meta metadata * @param size total size * @param completed current progress */ static struct DownloadEntry * setup_download (struct DownloadEntry *de, struct DownloadEntry *pde, struct SearchResult *sr, struct GNUNET_FS_DownloadContext *dc, const struct GNUNET_FS_Uri *uri, const char *filename, const struct GNUNET_CONTAINER_MetaData *meta, uint64_t size, uint64_t completed) { GtkTreeIter iter; GtkTreePath *path; struct SearchResult *srp; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Setting up download, initially DE=%p, PDE=%p for %p & %p into %llu/%llu `%s'\n", de, pde, sr, dc, completed, size, filename); GNUNET_assert (NULL != uri); if (de == NULL) { de = GNUNET_malloc (sizeof (struct DownloadEntry)); de->uri = GNUNET_FS_uri_dup (uri); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Allocated DE=%p\n", de); } de->dc = dc; de->sr = sr; if (NULL != sr) { GNUNET_assert (sr->download == NULL); sr->download = de; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "SR=%p now refers to DE=%p\n", sr, de); } de->pde = pde; if ((meta != NULL) && (de->meta == NULL)) de->meta = GNUNET_CONTAINER_meta_data_duplicate (meta); if (sr != NULL) { de->rr = gtk_tree_row_reference_copy (sr->rr); de->ts = sr->tab->ts; de->tab = sr->tab; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "It's started from a search result, store a copy of a reference to rr=%p (%p), ts=%p and tab=%p\n", sr->rr, de->rr, de->ts, de->tab); } else if (de->rr == NULL) { de->tab = GNUNET_GTK_add_to_uri_tab (&iter, &srp, meta, uri); de->ts = de->tab->ts; path = gtk_tree_model_get_path (GTK_TREE_MODEL (de->ts), &iter); de->rr = gtk_tree_row_reference_new (GTK_TREE_MODEL (de->ts), path); gtk_tree_path_free (path); srp->download = de; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "It's a standalone download, added it to uri tab=%p, ts=%p and rr=%p\n", de->tab, de->ts, de->rr); } else { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "It's a child download, leaving its rr, ts and tab unset\n"); } path = gtk_tree_row_reference_get_path (de->rr); if (TRUE != gtk_tree_model_get_iter (GTK_TREE_MODEL (de->ts), &iter, path)) { GNUNET_break (0); gtk_tree_path_free (path); return de; } gtk_tree_path_free (path); gtk_tree_store_set (de->ts, &iter, 4, (guint) ((size > 0) ? (100 * completed / size) : 100) /* progress */ , 6, filename /* filename/description */ , 8, "blue" /* status colour: pending */ , -1); gtk_tree_store_set (de->ts, &iter, 14, completed, -1); return de; } /** * This should get the default download directory * (so that GNUnet won't offer the user to download files * to the 'bin' subdirectory, or whatever is the cwd). * Returns NULL on failure (such as non-existend directory). * Should also preserve the last setting (so if the user * saves files somewhere else, next time we default to * somewhere else, at least until application restart, or maybe even * between application restarts). * Fills the @buffer up to @size bytes, returns a pointer to it. */ static char * get_default_download_directory (char *buffer, size_t size) { return NULL; } /** * Called recursively to build a suggested filename by * prepending suggested names for its parent directories (if any). * Might return NULL. Returned name might be absolute. * local_parents is set to GNUNET_YES if all parents are directories, * and are downloaded. */ static char * get_suggested_filename_anonymity (GtkTreeModel *tm, GtkTreeIter *iter, int top, int *local_parents, int *anonymity, int *filename_is_absolute) { char *result; char *dirname; char *local_filename, *filename; int downloaded_anonymity; int have_a_parent; struct GNUNET_CONTAINER_MetaData *meta; GtkTreeIter parent; gtk_tree_model_get (tm, iter, 0, &meta, 15, &local_filename, 16, &downloaded_anonymity, -1); if (local_filename == NULL && !top) *local_parents = GNUNET_NO; if (downloaded_anonymity != -1 && *anonymity == -1 && !top) *anonymity = downloaded_anonymity; if (gtk_tree_model_iter_parent (tm, &parent, iter)) { have_a_parent = GNUNET_YES; dirname = get_suggested_filename_anonymity (tm, &parent, GNUNET_NO, local_parents, anonymity, filename_is_absolute); } else { have_a_parent = GNUNET_NO; dirname = NULL; if (top) *local_parents = GNUNET_NO; } if (local_filename == NULL) filename = GNUNET_FS_meta_data_suggest_filename (meta); else { /* This directory was downloaded as /foo/bar/baz/somedirname * Hopefully, "somedirname" is actually "somedir.gnd" * We need to strip the ".gnd" part to get "somedir", which is * what we're going to use instead of suggested original filename * Without the .gnd extension we're going to just use a copy * of the directory file name - and that would fail. Sad. */ const char *basename; if (dirname == NULL && !have_a_parent) { /* This is the ealderlest parent directory. Use absolute path. */ basename = (const char *) local_filename; *filename_is_absolute = GNUNET_YES; } else basename = GNUNET_STRINGS_get_short_name (local_filename); if (basename != NULL && strlen (basename) > 0) { char *dot; filename = GNUNET_strdup (basename); dot = strrchr (filename, '.'); if (dot) *dot = '\0'; } else filename = GNUNET_FS_meta_data_suggest_filename (meta); } if (dirname && filename) { GNUNET_asprintf (&result, "%s%s%s", dirname, DIR_SEPARATOR_STR, filename); GNUNET_free (filename); GNUNET_free (dirname); return result; } else if (filename) return filename; else return NULL; } /** * Tell FS to start a download. Begins by opening the * "save as" window. */ static void start_download (GtkTreeView * tree_view, GtkTreePath * path, GtkTreeViewColumn * column, gpointer user_data) { struct StartDownloadContext *sdc = user_data; struct SearchTab *tab = sdc->tab; GtkTreeModel *tm; GtkTreeIter iter; struct GNUNET_FS_Uri *uri; struct GNUNET_CONTAINER_MetaData *meta; struct SearchResult *sr; gchar *mime; struct DownloadContext *dc; char *buf = NULL; char *tmp; size_t tmplen; char cwd[FILENAME_MAX]; char *download_directory; char *filename; int local_parents; int have_a_suggestion; int anonymity; int filename_is_absolute; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Starting a %sdownload\n", sdc->recursive ? "recursive " : ""); GNUNET_assert (tab != NULL); tm = gtk_tree_view_get_model (tree_view); if (TRUE != gtk_tree_model_get_iter (tm, &iter, path)) { GNUNET_break (0); GNUNET_free (sdc); return; } gtk_tree_model_get (tm, &iter, 0, &meta, 1, &uri, 9, &sr, 10, &mime, -1); if (NULL == uri) { /* user clicked on directory that was opened (not downloaded!), so we have no URI and downloading makes no sense. Ignore! */ if (NULL != mime) g_free (mime); return; } if (!(GNUNET_FS_uri_test_chk (uri) || GNUNET_FS_uri_test_loc (uri))) { /* can only download chk/loc URIs, ignore */ g_free (mime); return; } download_directory = get_default_download_directory (cwd, sizeof (cwd)); /* If no download directory is known, try working directory */ if (download_directory == NULL) download_directory = getcwd (cwd, sizeof (cwd)); /* Calculate suggested filename */ local_parents = GNUNET_YES; anonymity = -1; filename_is_absolute = GNUNET_NO; filename = get_suggested_filename_anonymity (tm, &iter, GNUNET_YES, &local_parents, &anonymity, &filename_is_absolute); have_a_suggestion = GNUNET_NO; if (NULL != download_directory) { if (NULL == filename) { buf = GNUNET_strdup (download_directory); } else { have_a_suggestion = GNUNET_YES; if (filename_is_absolute) GNUNET_asprintf (&tmp, "%s", filename); else GNUNET_asprintf (&tmp, "%s%s%s", download_directory, DIR_SEPARATOR_STR, filename); tmplen = strlen (tmp); /* now, if we have a directory, replace trailing '/' with ".gnd" */ if (GNUNET_YES == GNUNET_FS_meta_data_test_for_directory (meta)) { if ( (tmp[tmplen-1] == '/') || (tmp[tmplen-1] == '\\') ) tmp[tmplen-1] = '\0'; GNUNET_asprintf (&buf, "%s%s", tmp, GNUNET_FS_DIRECTORY_EXT); GNUNET_free (tmp); } else { buf = tmp; } } } GNUNET_free_non_null (filename); /* now setup everything for the save-as dialog */ dc = GNUNET_malloc (sizeof (struct DownloadContext)); dc->uri = GNUNET_FS_uri_dup (uri); dc->mime = mime; dc->filename = buf; dc->meta = GNUNET_CONTAINER_meta_data_duplicate (meta); dc->rr = gtk_tree_row_reference_new (tm, path); dc->sr = sr->result; dc->anonymity = anonymity; dc->is_recursive = sdc->recursive; dc->tab = tab; if (local_parents && have_a_suggestion) /* Skip the dialog, call directly */ GNUNET_FS_GTK_download_context_start_download (dc); else GNUNET_FS_GTK_open_download_as_dialog (dc); } /** * "Download" was selected in the current search context menu. */ static void start_download_ctx_menu (GtkMenuItem *item, gpointer user_data) { GtkTreePath *path; GtkTreeView *tv; struct StartDownloadContext sdc; if (current_context_row_reference == NULL) { GNUNET_break (0); return; } path = gtk_tree_row_reference_get_path (current_context_row_reference); gtk_tree_row_reference_free (current_context_row_reference); current_context_row_reference = NULL; tv = GTK_TREE_VIEW (gtk_builder_get_object (current_context_search_tab->builder, "_search_result_frame")); sdc.tab = current_context_search_tab; sdc.recursive = FALSE; start_download (tv, path, NULL, &sdc); gtk_tree_path_free (path); current_context_search_tab = NULL; } /** * "Download recursively" was selected in the current search context menu. */ static void start_download_recursively_ctx_menu (GtkMenuItem *item, gpointer user_data) { GtkTreePath *path; GtkTreeView *tv; struct StartDownloadContext sdc; if (current_context_row_reference == NULL) { GNUNET_break (0); return; } path = gtk_tree_row_reference_get_path (current_context_row_reference); gtk_tree_row_reference_free (current_context_row_reference); current_context_row_reference = NULL; tv = GTK_TREE_VIEW (gtk_builder_get_object (current_context_search_tab->builder, "_search_result_frame")); sdc.tab = current_context_search_tab; sdc.recursive = TRUE; start_download (tv, path, NULL, &sdc); gtk_tree_path_free (path); current_context_search_tab = NULL; } /** * Download was selected in the current search context menu. */ static void abort_download_ctx_menu (GtkMenuItem *item, gpointer user_data) { struct DownloadEntry *de = user_data; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Aborting a download DE=%p\n", de); GNUNET_assert (de->dc != NULL); GNUNET_FS_download_stop (de->dc, GNUNET_YES); current_context_search_tab = NULL; } /** * Copy current URI to clipboard. */ static void copy_uri_to_clipboard_ctx_menu (GtkMenuItem *item, gpointer user_data) { GtkTreePath *path; GtkTreeView *tv; GtkTreeModel *tm; GtkTreeIter iter; struct GNUNET_FS_Uri *uri; char *uris; GtkClipboard *cb; if (current_context_row_reference == NULL) { GNUNET_break (0); return; } path = gtk_tree_row_reference_get_path (current_context_row_reference); gtk_tree_row_reference_free (current_context_row_reference); current_context_row_reference = NULL; tv = GTK_TREE_VIEW (gtk_builder_get_object (current_context_search_tab->builder, "_search_result_frame")); tm = gtk_tree_view_get_model (tv); if (TRUE != gtk_tree_model_get_iter (tm, &iter, path)) { GNUNET_break (0); gtk_tree_path_free (path); return; } gtk_tree_model_get (tm, &iter, 1, &uri, -1); gtk_tree_path_free (path); current_context_search_tab = NULL; if (uri == NULL) { GNUNET_break (0); return; } uris = GNUNET_FS_uri_to_string (uri); cb = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); gtk_clipboard_set_text (cb, uris, -1); gtk_clipboard_store (cb); GNUNET_free (uris); } gboolean search_list_on_popup (GtkWidget *widget, gpointer user_data) { GtkTreeView *tv; struct SearchTab *tab = user_data; tv = GTK_TREE_VIEW (widget); return search_list_popup (tv, tab, NULL); } /** * We got a right-click on the search result list. Display the context * menu. */ static int search_list_on_menu (GtkWidget * widget, GdkEvent * event, gpointer user_data) { GdkEventButton *event_button; struct SearchTab *tab = user_data; GtkTreeView *tv; tv = GTK_TREE_VIEW (widget); if (event->type == GDK_BUTTON_PRESS) { event_button = (GdkEventButton *) event; if (event_button->button == 3) { return search_list_popup (tv, tab, event_button); } } return FALSE; } /** * Selected row has changed, update preview and metadata * areas. */ static void update_meta_data_views (GtkTreeView * tv, gpointer user_data) { struct SearchTab *tab = user_data; GtkImage *image; GtkListStore *ms; GtkTreeSelection *sel; GtkTreeModel *model; GtkTreeIter iter; struct GNUNET_CONTAINER_MetaData *meta; GdkPixbuf *pixbuf; GNUNET_assert (tab->query_txt != NULL); image = GTK_IMAGE (GNUNET_FS_GTK_get_main_window_object ("GNUNET_GTK_main_window_preview_image")); ms = GTK_LIST_STORE (GNUNET_FS_GTK_get_main_window_object ("GNUNET_GTK_meta_data_list_store")); sel = gtk_tree_view_get_selection (tv); gtk_list_store_clear (ms); if (TRUE != gtk_tree_selection_get_selected (sel, &model, &iter)) { gtk_image_clear (image); return; } meta = NULL; pixbuf = NULL; gtk_tree_model_get (model, &iter, 0, &meta, 3, &pixbuf, -1); if (pixbuf != NULL) { gtk_image_set_from_pixbuf (image, pixbuf); g_object_unref (G_OBJECT (pixbuf)); } if (meta != NULL) { GNUNET_CONTAINER_meta_data_iterate (meta, &GNUNET_FS_GTK_add_meta_data_to_list_store, ms); } } /** * Update the label for a search */ static void update_search_label (struct SearchTab *tab) { char *name; while (tab->parent != NULL) tab = tab->parent->tab; if (tab->num_results > 0) GNUNET_asprintf (&name, "%.*s%s (%u)", 20, tab->query_txt, strlen (tab->query_txt) > 20 ? "..." : "", tab->num_results); else GNUNET_asprintf (&name, "%.*s%s", 20, tab->query_txt, strlen (tab->query_txt) > 20 ? "..." : ""); gtk_label_set_text (tab->label, name); gtk_widget_set_tooltip_text (GTK_WIDGET (tab->label), tab->query_txt); GNUNET_free (name); } /** * Close a search tab and free associated state. */ static void close_search_tab (struct SearchTab *tab) { GtkNotebook *notebook; int index; int i; if (tab->parent != NULL) { /* not a top-level search, do not close tab here! */ GNUNET_free (tab); return; } notebook = GTK_NOTEBOOK (GNUNET_FS_GTK_get_main_window_object ("GNUNET_GTK_main_window_notebook")); index = -1; for (i = gtk_notebook_get_n_pages (notebook) - 1; i >= 0; i--) if (tab->frame == gtk_notebook_get_nth_page (notebook, i)) index = i; gtk_notebook_remove_page (notebook, index); g_object_unref (tab->builder); GNUNET_free (tab->query_txt); GNUNET_CONTAINER_DLL_remove (search_tab_head, search_tab_tail, tab); if (tab == uri_tab) uri_tab = NULL; GNUNET_free (tab); } /** * Close a publish tab and free associated state. */ static struct PublishEntry * handle_publish_completed (struct PublishEntry *ent, const struct GNUNET_FS_Uri *uri) { ent->uri = GNUNET_FS_uri_dup (uri); return change_publish_colour (ent, "green"); } /** * Handle error. */ static struct PublishEntry * handle_publish_error (struct PublishEntry *ent, const char *emsg) { GNUNET_break (0); return change_publish_colour (ent, "red"); } /** * Close free an entry in the publish tab and free associated state. */ static void handle_publish_stop (struct PublishEntry *ent) { GtkTreeIter iter; GtkTreePath *path; GtkTreeModel *tm; if (ent == NULL) { GNUNET_break (0); return; } if (NULL != ent->pc) { /* get piter from parent */ path = gtk_tree_row_reference_get_path (ent->rr); tm = gtk_tree_row_reference_get_model (ent->rr); /* This is a child of a directory, and we've had that directory * freed already */ if (path != NULL) { if (TRUE != gtk_tree_model_get_iter (tm, &iter, path)) GNUNET_break (0); else (void) gtk_tree_store_remove (GTK_TREE_STORE (tm), &iter); gtk_tree_path_free (path); } } gtk_tree_row_reference_free (ent->rr); if (ent->uri != NULL) { GNUNET_FS_uri_destroy (ent->uri); ent->uri = NULL; } GNUNET_free (ent); } /** * Tell FS to stop a search. */ static void stop_search (GtkButton * button, gpointer user_data) { struct SearchTab *tab = user_data; struct GNUNET_FS_SearchContext *sc; if (NULL != (sc = tab->sc)) { tab->sc = NULL; GNUNET_FS_search_stop (sc); } } /** * Stop completed downloads (or those that failed). Should * iterate over the underlying tree store and stop all * completed entries. Furthermore, if the resulting tree * store is empty and has no search associated with it, * the tab should be closed. */ static void clear_downloads (GtkButton * button, gpointer user_data) { struct SearchTab *tab = user_data; struct SearchResult *sr; GtkTreeModel *tm; GtkTreeIter iter; tm = GTK_TREE_MODEL (tab->ts); if (TRUE != gtk_tree_model_get_iter_first (tm, &iter)) return; do { gtk_tree_model_get (tm, &iter, 9, &sr, -1); if (sr->download != NULL) { if (sr->download->is_done == GNUNET_YES) GNUNET_FS_download_stop (sr->download->dc, GNUNET_YES); } else if (sr->result == NULL) free_search_result (sr); } while (TRUE == gtk_tree_model_iter_next (tm, &iter)); } /** * Tell FS to pause a search. */ static void pause_search (GtkButton * button, gpointer user_data) { struct SearchTab *tab = user_data; if (tab->sc != NULL) { GNUNET_FS_search_pause (tab->sc); gtk_widget_show (tab->play_button); gtk_widget_hide (tab->pause_button); } } /** * Tell FS to resume a search. */ static void continue_search (GtkButton * button, gpointer user_data) { struct SearchTab *tab = user_data; if (tab->sc != NULL) { GNUNET_FS_search_continue (tab->sc); gtk_widget_show (tab->pause_button); gtk_widget_hide (tab->play_button); } } /** * Setup a new search tab. * * @param sc context with FS for the search * @param query the query * @param anonymity anonymity level */ static struct SearchTab * setup_search (struct GNUNET_FS_SearchContext *sc, const struct GNUNET_FS_Uri *query) { struct SearchTab *tab; GtkTreeView *tv; GtkNotebook *notebook; GtkWindow *sf; gint pages; struct StartDownloadContext *sdc; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Setting up a search for %p\n", sc); tab = GNUNET_malloc (sizeof (struct SearchTab)); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Allocated a tab %p\n", tab); GNUNET_CONTAINER_DLL_insert (search_tab_head, search_tab_tail, tab); tab->sc = sc; if (query == NULL) { tab->query_txt = GNUNET_strdup ("*"); } else { /* FS_uri functions should produce UTF-8, so let them be */ if (GNUNET_FS_uri_test_ksk (query)) tab->query_txt = GNUNET_FS_uri_ksk_to_string_fancy (query); else tab->query_txt = GNUNET_FS_uri_to_string (query); } tab->builder = GNUNET_GTK_get_new_builder ("gnunet_fs_gtk_search_tab.glade", tab); tab->ts = GTK_TREE_STORE (gtk_builder_get_object (tab->builder, "GNUNET_GTK_file_sharing_result_tree_store")); /* load frame */ sf = GTK_WINDOW (gtk_builder_get_object (tab->builder, "_search_result_frame_window")); tab->frame = gtk_bin_get_child (GTK_BIN (sf)); g_object_ref (tab->frame); gtk_container_remove (GTK_CONTAINER (sf), tab->frame); gtk_widget_destroy (GTK_WIDGET (sf)); /* load tab_label */ sf = GTK_WINDOW (gtk_builder_get_object (tab->builder, "_search_result_label_window")); tab->tab_label = gtk_bin_get_child (GTK_BIN (sf)); g_object_ref (tab->tab_label); gtk_container_remove (GTK_CONTAINER (sf), tab->tab_label); gtk_widget_destroy (GTK_WIDGET (sf)); /* get refs to widgets */ tab->label = GTK_LABEL (gtk_builder_get_object (tab->builder, "_search_result_label_window_label")); /* FIXME: connect these signals using glade!!! */ tab->close_button = GTK_WIDGET (gtk_builder_get_object (tab->builder, "_search_result_label_close_button")); g_signal_connect (G_OBJECT (tab->close_button), "clicked", G_CALLBACK (stop_search), tab); tab->clear_button = GTK_WIDGET (gtk_builder_get_object (tab->builder, "_search_result_label_clear_button")); g_signal_connect (G_OBJECT (tab->clear_button), "clicked", G_CALLBACK (clear_downloads), tab); tab->play_button = GTK_WIDGET (gtk_builder_get_object (tab->builder, "_search_result_label_play_button")); g_signal_connect (G_OBJECT (tab->play_button), "clicked", G_CALLBACK (continue_search), tab); tab->pause_button = GTK_WIDGET (gtk_builder_get_object (tab->builder, "_search_result_label_pause_button")); g_signal_connect (G_OBJECT (tab->pause_button), "clicked", G_CALLBACK (pause_search), tab); /* patch text */ update_search_label (tab); /* add signal handlers */ tv = GTK_TREE_VIEW (gtk_builder_get_object (tab->builder, "_search_result_frame")); sdc = GNUNET_malloc (sizeof (struct StartDownloadContext)); sdc->tab = tab; sdc->recursive = FALSE; g_signal_connect_data (G_OBJECT (tv), "row-activated", G_CALLBACK (start_download), sdc, &closure_notify_free, 0); g_signal_connect (G_OBJECT (tv), "cursor-changed", G_CALLBACK (update_meta_data_views), tab); g_signal_connect (G_OBJECT (tv), "button_press_event", G_CALLBACK (search_list_on_menu), tab); g_signal_connect (G_OBJECT (tv), "popup-menu", G_CALLBACK (search_list_on_popup), tab); /* make visible */ notebook = GTK_NOTEBOOK (GNUNET_FS_GTK_get_main_window_object ("GNUNET_GTK_main_window_notebook")); pages = gtk_notebook_get_n_pages (notebook); gtk_notebook_insert_page (notebook, tab->frame, tab->tab_label, pages - 1); gtk_notebook_set_current_page (notebook, pages - 1); gtk_widget_show (GTK_WIDGET (notebook)); return tab; } /** * Setup an inner search. * * @param sc context with FS for the search * @param parent parent search tab * @param anonymity anonymity level */ static struct SearchTab * setup_inner_search (struct GNUNET_FS_SearchContext *sc, struct SearchResult *parent) { struct SearchTab *ret; ret = GNUNET_malloc (sizeof (struct SearchTab)); ret->parent = parent; ret->sc = sc; ret->query_txt = parent->tab->query_txt; ret->builder = parent->tab->builder; ret->frame = parent->tab->frame; ret->tab_label = parent->tab->tab_label; ret->close_button = parent->tab->close_button; ret->clear_button = parent->tab->clear_button; ret->play_button = parent->tab->play_button; ret->label = parent->tab->label; return ret; } /** * Add a search result to the given search tab. * * @param tab search tab to extend * @param iter set to position where search result is added * @param parent_rr reference to parent entry in search tab * @param uri uri to add * @param meta metadata of the entry * @param result associated FS search result (can be NULL) * @param applicability_rank how relevant is the result * @return entry for the search result */ struct SearchResult * GNUNET_GTK_add_search_result (struct SearchTab *tab, GtkTreeIter * iter, GtkTreeRowReference * parent_rr, const struct GNUNET_FS_Uri *uri, const struct GNUNET_CONTAINER_MetaData *meta, struct GNUNET_FS_SearchResult *result, uint32_t applicability_rank) { struct SearchResult *sr; GtkTreePath *tp; const char *status_colour; char *desc; char *mime; char *uris; GdkPixbuf *pixbuf; GtkTreeIter *pitr; GtkTreeIter pmem; GtkTreePath *path; GtkTreeModel *tm; GtkTreeStore *ts; uint64_t fsize; if ((uri != NULL) && (!GNUNET_FS_uri_test_loc (uri)) && (!GNUNET_FS_uri_test_chk (uri))) { fsize = 0; mime = GNUNET_strdup ("GNUnet namespace"); status_colour = "lightgreen"; } else if (uri != NULL) { fsize = GNUNET_FS_uri_chk_get_file_size (uri); mime = GNUNET_CONTAINER_meta_data_get_first_by_types (meta, EXTRACTOR_METATYPE_MIMETYPE, EXTRACTOR_METATYPE_FORMAT, -1); status_colour = "white"; } else { fsize = 0; status_colour = "gray"; mime = NULL; } desc = GNUNET_CONTAINER_meta_data_get_first_by_types (meta, EXTRACTOR_METATYPE_GNUNET_ORIGINAL_FILENAME, EXTRACTOR_METATYPE_PACKAGE_NAME, EXTRACTOR_METATYPE_TITLE, EXTRACTOR_METATYPE_BOOK_TITLE, EXTRACTOR_METATYPE_FILENAME, EXTRACTOR_METATYPE_DESCRIPTION, EXTRACTOR_METATYPE_SUMMARY, EXTRACTOR_METATYPE_ALBUM, EXTRACTOR_METATYPE_COMMENT, EXTRACTOR_METATYPE_SUBJECT, EXTRACTOR_METATYPE_KEYWORDS, -1); if (desc == NULL) desc = GNUNET_strdup (_("no description supplied")); else { char *utf8_desc = NULL; utf8_desc = GNUNET_FS_GTK_dubious_meta_to_utf8 (EXTRACTOR_METAFORMAT_UTF8, desc, strlen (desc) + 1); GNUNET_free (desc); if (utf8_desc != NULL) desc = utf8_desc; else desc = NULL; } if (uri == NULL) uris = GNUNET_strdup (_("no URI")); else uris = GNUNET_FS_uri_to_string (uri); pixbuf = GNUNET_FS_GTK_get_thumbnail_from_meta_data (meta); sr = GNUNET_malloc (sizeof (struct SearchResult)); sr->result = result; sr->tab = tab; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Allocated a search result SR=%p\n", sr); if (parent_rr != NULL) { /* get piter from parent */ path = gtk_tree_row_reference_get_path (parent_rr); tm = gtk_tree_row_reference_get_model (parent_rr); if (TRUE != gtk_tree_model_get_iter (GTK_TREE_MODEL (tm), &pmem, path)) { GNUNET_break (0); gtk_tree_path_free (path); /* desperate measure: make top-level entry */ pitr = NULL; } else { pitr = &pmem; } ts = GTK_TREE_STORE (tm); } else { /* top-level result */ pitr = NULL; ts = tab->ts; } gtk_tree_store_insert_with_values (ts, iter, pitr, G_MAXINT, 0, GNUNET_CONTAINER_meta_data_duplicate (meta), 1, (uri == NULL) ? NULL : GNUNET_FS_uri_dup (uri), 2, (uri == NULL) ? 0 : fsize, 3, pixbuf /* preview */ , 4, 0 /* percent progress */ , 5, 0 /* percent availability */ , 6, desc /* filename/description */ , 7, uris, 8, status_colour, 9, sr, 10, mime, 11, applicability_rank, 12, 0 /* avail-cert */ , 13, 0, /* avail-rank */ 14, (guint64) 0, /* completed */ 15, NULL, /* downloaded_filename */ 16, -1, /* downloaded_anonymity */ -1); if (tab != NULL) { while (tab->parent != NULL) tab = tab->parent->tab; tab->num_results++; } if (pixbuf != NULL) g_object_unref (pixbuf); GNUNET_free (uris); GNUNET_free_non_null (desc); GNUNET_free_non_null (mime); tp = gtk_tree_model_get_path (GTK_TREE_MODEL (ts), iter); sr->rr = gtk_tree_row_reference_new (GTK_TREE_MODEL (ts), tp); gtk_tree_path_free (tp); return sr; } static struct SearchResult * process_search_result (void *cls, struct SearchResult *parent, const struct GNUNET_FS_Uri *uri, const struct GNUNET_CONTAINER_MetaData *meta, struct GNUNET_FS_SearchResult *result, uint32_t applicability_rank) { struct SearchTab *tab = cls; struct SearchResult *sr; GtkTreeIter iter; sr = GNUNET_GTK_add_search_result (tab, &iter, (parent != NULL) ? parent->rr : NULL, uri, meta, result, applicability_rank); update_search_label (tab); return sr; } /** * Setup a new top-level entry in the URI tab. If necessary, create * the URI tab first. * * @param iter set to the new entry * @param srp set to search result * @param meta metadata for the new entry * @param uri URI for the new entry * @return NULL on error, otherwise tree store matching iter */ struct SearchTab * GNUNET_GTK_add_to_uri_tab (GtkTreeIter * iter, struct SearchResult **srp, const struct GNUNET_CONTAINER_MetaData *meta, const struct GNUNET_FS_Uri *uri) { struct SearchResult *sr; GtkNotebook *notebook; gint page; if (uri_tab == NULL) { uri_tab = setup_search (NULL, NULL); gtk_widget_set_visible (uri_tab->close_button, FALSE); gtk_widget_set_visible (uri_tab->pause_button, FALSE); } else { /* make 'utab' the current page */ notebook = GTK_NOTEBOOK (GNUNET_FS_GTK_get_main_window_object ("GNUNET_GTK_main_window_notebook")); for (page = 0; page < gtk_notebook_get_n_pages (notebook); page++) if (uri_tab->frame == gtk_notebook_get_nth_page (notebook, page)) { gtk_notebook_set_current_page (notebook, page); break; } } sr = GNUNET_GTK_add_search_result (uri_tab, iter, NULL, uri, meta, NULL, 0); if (NULL != srp) *srp = sr; return uri_tab; } static struct SearchTab * handle_search_error (struct SearchTab *sr, const char *emsg) { /* FIXME: implement error handler */ GNUNET_break (0); return sr; } static struct SearchResult * update_search_result (struct SearchResult *sr, const struct GNUNET_CONTAINER_MetaData *meta, int32_t availability_rank, uint32_t availability_certainty, uint32_t applicability_rank) { GtkTreeIter iter; struct GNUNET_CONTAINER_MetaData *ometa; GtkTreeView *tv; GtkTreePath *tp; GtkTreeStore *ts; GtkTreeModel *tm; char *desc; char *mime; GdkPixbuf *pixbuf; guint percent_avail; GtkNotebook *notebook; gint page; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Updating search result SR=%p with %d, %u, %u\n", sr, availability_rank, availability_certainty, applicability_rank); if (sr == NULL) return NULL; desc = GNUNET_CONTAINER_meta_data_get_first_by_types (meta, EXTRACTOR_METATYPE_PACKAGE_NAME, EXTRACTOR_METATYPE_TITLE, EXTRACTOR_METATYPE_BOOK_TITLE, EXTRACTOR_METATYPE_FILENAME, EXTRACTOR_METATYPE_DESCRIPTION, EXTRACTOR_METATYPE_SUMMARY, EXTRACTOR_METATYPE_ALBUM, EXTRACTOR_METATYPE_COMMENT, EXTRACTOR_METATYPE_SUBJECT, EXTRACTOR_METATYPE_KEYWORDS, -1); if (desc == NULL) desc = GNUNET_strdup (_("no description supplied")); else { char *utf8_desc = NULL; utf8_desc = GNUNET_FS_GTK_dubious_meta_to_utf8 (EXTRACTOR_METAFORMAT_UTF8, desc, strlen (desc) + 1); GNUNET_free (desc); if (utf8_desc != NULL) desc = utf8_desc; else desc = NULL; } mime = GNUNET_CONTAINER_meta_data_get_first_by_types (meta, EXTRACTOR_METATYPE_MIMETYPE, EXTRACTOR_METATYPE_FORMAT, -1); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "mime=`%s', desc=`%s'\n", mime, desc); pixbuf = GNUNET_FS_GTK_get_thumbnail_from_meta_data (meta); tp = gtk_tree_row_reference_get_path (sr->rr); tm = gtk_tree_row_reference_get_model (sr->rr); ts = GTK_TREE_STORE (tm); gtk_tree_model_get_iter (tm, &iter, tp); gtk_tree_path_free (tp); gtk_tree_model_get (tm, &iter, 0, &ometa, -1); if (meta != NULL) GNUNET_CONTAINER_meta_data_destroy (ometa); if (availability_certainty > 0) percent_avail = (availability_certainty + availability_rank) * 50 / availability_certainty; else percent_avail = 0; gtk_tree_store_set (ts, &iter, 0, GNUNET_CONTAINER_meta_data_duplicate (meta), 3, pixbuf /* preview */ , 5, (guint) percent_avail /* percent availability */ , 6, desc /* filename/description */ , 10, mime, 11, (guint) applicability_rank, 12, (guint) availability_certainty, 13, (gint) availability_rank, -1); if (pixbuf != NULL) g_object_unref (pixbuf); GNUNET_free_non_null (desc); GNUNET_free_non_null (mime); notebook = GTK_NOTEBOOK (GNUNET_FS_GTK_get_main_window_object ("GNUNET_GTK_main_window_notebook")); page = gtk_notebook_get_current_page (notebook); if (gtk_notebook_get_nth_page (notebook, page) == sr->tab->frame) { tv = GTK_TREE_VIEW (gtk_builder_get_object (sr->tab->builder, "_search_result_frame")); update_meta_data_views (tv, sr->tab); } return sr; } static void free_search_result (struct SearchResult *sr) { GtkTreePath *tp; GtkTreeModel *tm; GtkTreeIter iter; struct GNUNET_FS_Uri *uri; struct GNUNET_CONTAINER_MetaData *meta; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Freeing a search result SR=%p\n", sr); if (sr == NULL) { GNUNET_break (0); return; } GNUNET_assert (sr->rr != NULL); tp = gtk_tree_row_reference_get_path (sr->rr); GNUNET_assert (tp != NULL); tm = gtk_tree_row_reference_get_model (sr->rr); GNUNET_assert (tm != NULL); if (TRUE != gtk_tree_model_get_iter (tm, &iter, tp)) { GNUNET_break (0); gtk_tree_path_free (tp); return; } gtk_tree_path_free (tp); gtk_tree_model_get (tm, &iter, 0, &meta, 1, &uri, -1); if (uri != NULL) GNUNET_FS_uri_destroy (uri); if (meta != NULL) GNUNET_CONTAINER_meta_data_destroy (meta); gtk_tree_row_reference_free (sr->rr); (void) gtk_tree_store_remove (GTK_TREE_STORE (tm), &iter); GNUNET_free (sr); } /** * Tell FS to stop all active publish operations. Then close the tab. */ static void stop_publishing (GtkButton * button, gpointer user_data) { struct PublishTab *tab = user_data; struct GNUNET_FS_PublishContext *pc; struct PublishEntry *ent; GtkTreeIter iter; GtkTreeModel *tm; GtkNotebook *notebook; int index; int i; GNUNET_assert (tab == publish_tab); tm = GTK_TREE_MODEL (publish_tab->ts); if (TRUE == gtk_tree_model_iter_children (tm, &iter, NULL)) { do { gtk_tree_model_get (tm, &iter, 4, &ent, -1); if (NULL != (pc = ent->pc)) { ent->pc = NULL; GNUNET_FS_publish_stop (pc); } } while (TRUE == gtk_tree_model_iter_next (tm, &iter)); } notebook = GTK_NOTEBOOK (GNUNET_FS_GTK_get_main_window_object ("GNUNET_GTK_main_window_notebook")); index = -1; for (i = gtk_notebook_get_n_pages (notebook) - 1; i >= 0; i--) if (publish_tab->frame == gtk_notebook_get_nth_page (notebook, i)) index = i; gtk_notebook_remove_page (notebook, index); g_object_unref (publish_tab->builder); GNUNET_free (publish_tab); publish_tab = NULL; } static struct PublishEntry * setup_publish (struct GNUNET_FS_PublishContext *pc, const char *fn, uint64_t fsize, struct PublishEntry *parent) { struct PublishEntry *ent; GtkTreeIter *pitrptr; GtkTreeIter iter; GtkTreeIter piter; GtkTreePath *path; GtkWindow *df; GtkWidget *tab_label; GtkWidget *close_button; GtkNotebook *notebook; char *size_fancy; if (NULL == parent) { if (NULL == publish_tab) { /* create new tab */ publish_tab = GNUNET_malloc (sizeof (struct PublishTab)); publish_tab->builder = GNUNET_GTK_get_new_builder ("gnunet_fs_gtk_publish_tab.glade", publish_tab); df = GTK_WINDOW (gtk_builder_get_object (publish_tab->builder, "_publish_frame_window")); publish_tab->frame = gtk_bin_get_child (GTK_BIN (df)); g_object_ref (publish_tab->frame); gtk_container_remove (GTK_CONTAINER (df), publish_tab->frame); gtk_widget_destroy (GTK_WIDGET (df)); /* load tab_label */ df = GTK_WINDOW (gtk_builder_get_object (publish_tab->builder, "_publish_label_window")); tab_label = gtk_bin_get_child (GTK_BIN (df)); g_object_ref (tab_label); gtk_container_remove (GTK_CONTAINER (df), tab_label); gtk_widget_destroy (GTK_WIDGET (df)); /* FIXME: connect these signals using GLADE!!! */ /* get refs to widgets */ close_button = GTK_WIDGET (gtk_builder_get_object (publish_tab->builder, "_publish_label_close_button")); g_signal_connect (G_OBJECT (close_button), "clicked", G_CALLBACK (stop_publishing), publish_tab); /* FIXME: we don't actually need the closure anymore, * so we could have glade connect the above signal... */ /* make visible */ notebook = GTK_NOTEBOOK (GNUNET_FS_GTK_get_main_window_object ("GNUNET_GTK_main_window_notebook")); gtk_notebook_insert_page (notebook, publish_tab->frame, tab_label, 0); gtk_widget_show (GTK_WIDGET (notebook)); gtk_notebook_set_current_page (notebook, 0); publish_tab->ts = GTK_TREE_STORE (gtk_builder_get_object (publish_tab->builder, "_publish_frame_tree_store")); pitrptr = NULL; } else { /* use existing TAB, but create fresh entry */ pitrptr = NULL; } } else { /* create new iter from parent */ path = gtk_tree_row_reference_get_path (parent->rr); if (TRUE != gtk_tree_model_get_iter (GTK_TREE_MODEL (publish_tab->ts), &piter, path)) { GNUNET_break (0); return NULL; } pitrptr = &piter; } size_fancy = GNUNET_STRINGS_byte_size_fancy (fsize); ent = GNUNET_malloc (sizeof (struct PublishEntry)); ent->is_top = (parent == NULL) ? GNUNET_YES : GNUNET_NO; ent->tab = publish_tab; gtk_tree_store_insert_with_values (publish_tab->ts, &iter, pitrptr, G_MAXINT, 0, fn, 1, size_fancy, 2, "white", 3, (guint) 0 /* progress */ , 4, ent, -1); path = gtk_tree_model_get_path (GTK_TREE_MODEL (publish_tab->ts), &iter); GNUNET_assert (NULL != path); ent->rr = gtk_tree_row_reference_new (GTK_TREE_MODEL (publish_tab->ts), path); gtk_tree_path_free (path); ent->pc = pc; GNUNET_free (size_fancy); return ent; } /** * Notification of FS to a client about the progress of an * operation. Callbacks of this type will be used for uploads, * downloads and searches. Some of the arguments depend a bit * in their meaning on the context in which the callback is used. * * @param cls closure * @param info details about the event, specifying the event type * and various bits about the event * @return client-context (for the next progress call * for this operation; should be set to NULL for * SUSPEND and STOPPED events). The value returned * will be passed to future callbacks in the respective * field in the GNUNET_FS_ProgressInfo struct. */ void * GNUNET_GTK_fs_event_handler (void *cls, const struct GNUNET_FS_ProgressInfo *info) { void *ret; switch (info->status) { case GNUNET_FS_STATUS_PUBLISH_START: return setup_publish (info->value.publish.pc, info->value.publish.filename, info->value.publish.size, info->value.publish.pctx); case GNUNET_FS_STATUS_PUBLISH_RESUME: ret = setup_publish (info->value.publish.pc, info->value.publish.filename, info->value.publish.size, info->value.publish.pctx); if (ret == NULL) return ret; if (info->value.publish.specifics.resume.message != NULL) { ret = handle_publish_error (ret, info->value.publish.specifics.resume.message); } else if (info->value.publish.specifics.resume.chk_uri != NULL) { ret = handle_publish_completed (ret, info->value.publish.specifics.resume. chk_uri); } return ret; case GNUNET_FS_STATUS_PUBLISH_SUSPEND: handle_publish_stop (info->value.publish.cctx); return NULL; case GNUNET_FS_STATUS_PUBLISH_PROGRESS: return mark_publish_progress (info->value.publish.cctx, info->value.publish.size, info->value.publish.completed); case GNUNET_FS_STATUS_PUBLISH_ERROR: return handle_publish_error (info->value.publish.cctx, info->value.publish.specifics.error.message); case GNUNET_FS_STATUS_PUBLISH_COMPLETED: return handle_publish_completed (info->value.publish.cctx, info->value.publish.specifics.completed. chk_uri); case GNUNET_FS_STATUS_PUBLISH_STOPPED: handle_publish_stop (info->value.publish.cctx); return NULL; case GNUNET_FS_STATUS_DOWNLOAD_START: return setup_download (info->value.download.cctx, info->value.download.pctx, info->value.download.sctx, info->value.download.dc, info->value.download.uri, info->value.download.filename, info->value.download.specifics.start.meta, info->value.download.size, info->value.download.completed); case GNUNET_FS_STATUS_DOWNLOAD_RESUME: ret = setup_download (info->value.download.cctx, info->value.download.pctx, info->value.download.sctx, info->value.download.dc, info->value.download.uri, info->value.download.filename, info->value.download.specifics.resume.meta, info->value.download.size, info->value.download.completed); if (info->value.download.specifics.resume.message != NULL) { ret = mark_download_error (ret, info->value.download.specifics.resume.message); } return ret; case GNUNET_FS_STATUS_DOWNLOAD_SUSPEND: stop_download (info->value.download.cctx, GNUNET_YES); return NULL; case GNUNET_FS_STATUS_DOWNLOAD_PROGRESS: return mark_download_progress (info->value.download.cctx, info->value.download.size, info->value.download.completed, info->value.download.specifics.progress.data, info->value.download.specifics.progress. offset, info->value.download.specifics.progress. data_len, info->value.download.specifics.progress. depth); case GNUNET_FS_STATUS_DOWNLOAD_ERROR: return mark_download_error (info->value.download.cctx, info->value.download.specifics.error.message); case GNUNET_FS_STATUS_DOWNLOAD_COMPLETED: return mark_download_completed (info->value.download.cctx, info->value.download.size, info->value.download.filename); case GNUNET_FS_STATUS_DOWNLOAD_STOPPED: stop_download (info->value.download.cctx, GNUNET_NO); return NULL; case GNUNET_FS_STATUS_DOWNLOAD_ACTIVE: return change_download_colour (info->value.download.cctx, "yellow"); case GNUNET_FS_STATUS_DOWNLOAD_INACTIVE: return change_download_colour (info->value.download.cctx, "blue"); case GNUNET_FS_STATUS_DOWNLOAD_LOST_PARENT: return download_lost_parent (info->value.download.cctx, info->value.download.size, info->value.download.completed, info->value.download.is_active); case GNUNET_FS_STATUS_SEARCH_START: if (info->value.search.pctx != NULL) return setup_inner_search (info->value.search.sc, info->value.search.pctx); return setup_search (info->value.search.sc, info->value.search.query); case GNUNET_FS_STATUS_SEARCH_RESUME: ret = setup_search (info->value.search.sc, info->value.search.query); if (info->value.search.specifics.resume.message) ret = handle_search_error (ret, info->value.search.specifics.resume.message); return ret; case GNUNET_FS_STATUS_SEARCH_RESUME_RESULT: ret = process_search_result (info->value.search.cctx, info->value.search.pctx, info->value.search.specifics.resume_result.uri, info->value.search.specifics.resume_result.meta, info->value.search.specifics.resume_result. result, info->value.search.specifics.resume_result. applicability_rank); return update_search_result (ret, info->value.search.specifics.resume_result. meta, info->value.search.specifics.resume_result. applicability_rank, info->value.search.specifics.resume_result. availability_certainty, info->value.search.specifics.resume_result. availability_rank); case GNUNET_FS_STATUS_SEARCH_SUSPEND: close_search_tab (info->value.search.cctx); return NULL; case GNUNET_FS_STATUS_SEARCH_RESULT: return process_search_result (info->value.search.cctx, info->value.search.pctx, info->value.search.specifics.result.uri, info->value.search.specifics.result.meta, info->value.search.specifics.result.result, info->value.search.specifics.result. applicability_rank); case GNUNET_FS_STATUS_SEARCH_RESULT_NAMESPACE: GNUNET_break (0); break; case GNUNET_FS_STATUS_SEARCH_UPDATE: return update_search_result (info->value.search.specifics.update.cctx, info->value.search.specifics.update.meta, info->value.search.specifics.update. applicability_rank, info->value.search.specifics.update. availability_certainty, info->value.search.specifics.update. availability_rank); case GNUNET_FS_STATUS_SEARCH_ERROR: return handle_search_error (info->value.search.cctx, info->value.search.specifics.error.message); case GNUNET_FS_STATUS_SEARCH_PAUSED: return info->value.search.cctx; case GNUNET_FS_STATUS_SEARCH_CONTINUED: return info->value.search.cctx; case GNUNET_FS_STATUS_SEARCH_RESULT_STOPPED: free_search_result (info->value.search.specifics.result_stopped.cctx); return NULL; case GNUNET_FS_STATUS_SEARCH_RESULT_SUSPEND: free_search_result (info->value.search.specifics.result_suspend.cctx); return NULL; case GNUNET_FS_STATUS_SEARCH_STOPPED: close_search_tab (info->value.search.cctx); return NULL; case GNUNET_FS_STATUS_UNINDEX_START: GNUNET_break (0); break; case GNUNET_FS_STATUS_UNINDEX_RESUME: GNUNET_break (0); break; case GNUNET_FS_STATUS_UNINDEX_SUSPEND: GNUNET_break (0); break; case GNUNET_FS_STATUS_UNINDEX_PROGRESS: GNUNET_break (0); break; case GNUNET_FS_STATUS_UNINDEX_ERROR: GNUNET_break (0); break; case GNUNET_FS_STATUS_UNINDEX_COMPLETED: GNUNET_break (0); break; case GNUNET_FS_STATUS_UNINDEX_STOPPED: GNUNET_break (0); break; default: GNUNET_break (0); break; } return NULL; } /** * Page switched in main notebook, update thumbnail and * metadata views. */ void GNUNET_GTK_main_window_notebook_switch_page_cb (GtkWidget * dummy, gpointer data) { GtkNotebook *notebook; gint page; GtkWidget *w; struct SearchTab *tab; GtkImage *image; GtkListStore *ms; GtkTreeView *tv; notebook = GTK_NOTEBOOK (GNUNET_FS_GTK_get_main_window_object ("GNUNET_GTK_main_window_notebook")); page = gtk_notebook_get_current_page (notebook); w = gtk_notebook_get_nth_page (notebook, page); tab = search_tab_head; while (tab != NULL) { if (tab->frame == w) { tv = GTK_TREE_VIEW (gtk_builder_get_object (tab->builder, "_search_result_frame")); update_meta_data_views (tv, tab); return; } tab = tab->next; } image = GTK_IMAGE (GNUNET_FS_GTK_get_main_window_object ("GNUNET_GTK_main_window_preview_image")); gtk_image_clear (image); ms = GTK_LIST_STORE (GNUNET_FS_GTK_get_main_window_object ("GNUNET_GTK_meta_data_list_store")); gtk_list_store_clear (ms); } static void copy_metadata_to_clipboard (GtkTreeModel * model, GtkTreePath * path, GtkTreeIter * iter, gpointer user_data) { gchar *type, *value; GList **l = (GList **) user_data; gtk_tree_model_get (model, iter, 2, &type, 3, &value, -1); *l = g_list_prepend (*l, type); *l = g_list_prepend (*l, value); } void metadata_copy_selection_activated (GtkMenuItem * menuitem, gpointer user_data) { GtkBuilder *builder; GtkTreeView *tree; GtkClipboard *cb; GList *pairs = NULL, *l, *next, *value, *type; guint total_len; gchar *s, *p; builder = GTK_BUILDER (user_data); tree = GTK_TREE_VIEW (gtk_builder_get_object (builder, "GNUNET_GTK_main_window_metadata_treeview")); gtk_tree_selection_selected_foreach (gtk_tree_view_get_selection (tree), copy_metadata_to_clipboard, &pairs); total_len = 0; pairs = g_list_reverse (pairs); for (l = pairs; l; l = next) { type = l; value = l->next; if (!value) break; next = value->next; total_len += strlen ((gchar *) type->data) + strlen ((gchar *) value->data) + 2 /* ": " */ + (next ? 1 : 0) /* "\n" */ ; } if (total_len > 0) { total_len += 1; /* "\0" */ s = g_new0 (gchar, total_len); p = s; for (l = pairs; l; l = next) { type = l; value = l->next; if (value) { next = value->next; p = g_stpcpy (p, (gchar *) type->data); p = g_stpcpy (p, ": "); p = g_stpcpy (p, (gchar *) value->data); if (next) p = g_stpcpy (p, "\n"); } else next = NULL; } } g_list_foreach (pairs, (GFunc) g_free, NULL); g_list_free (pairs); pairs = NULL; if (total_len > 0) { cb = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); gtk_clipboard_set_text (cb, s, -1); gtk_clipboard_store (cb); g_free (s); } } void metadata_menu_popup_position (GtkMenu * menu, gint * x, gint * y, gboolean * push_in, gpointer user_data) { GtkBuilder *builder; GtkTreeView *tree; GtkTreeSelection *sel; GList *rows; GtkTreePath *p; GtkAllocation tree_allocation; GdkWindow *main_window_gdk; gint mwg_x, mwg_y, t_x, t_y, popup_x, popup_y; builder = GTK_BUILDER (user_data); tree = GTK_TREE_VIEW (gtk_builder_get_object (builder, "GNUNET_GTK_main_window_metadata_treeview")); gtk_widget_get_allocation (GTK_WIDGET (tree), &tree_allocation); main_window_gdk = gtk_widget_get_window (GTK_WIDGET (tree)); gdk_window_get_origin (main_window_gdk, &mwg_x, &mwg_y); t_x = mwg_x + tree_allocation.x; t_y = mwg_y + tree_allocation.y; popup_x = t_x; popup_y = t_y; sel = gtk_tree_view_get_selection (tree); rows = gtk_tree_selection_get_selected_rows (sel, NULL); if (rows->data) { GdkRectangle r; p = (GtkTreePath *) rows->data; gtk_tree_view_get_cell_area (tree, p, NULL, &r); popup_x += r.x; popup_y += r.y; } g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL); g_list_free (rows); *x = popup_x; *y = popup_y; *push_in = FALSE; } static void do_metadata_popup_menu (GtkWidget * widget, GdkEventButton * event, gpointer user_data) { GtkMenu *menu; GtkBuilder *builder; int button, event_time; GtkMenuPositionFunc mpf = NULL; builder = GTK_BUILDER (user_data); menu = GTK_MENU (gtk_builder_get_object (builder, "metadata_popup_menu")); if (event) { button = event->button; event_time = event->time; } else { button = 0; event_time = gtk_get_current_event_time (); } gtk_menu_popup (menu, NULL, NULL, mpf, user_data, button, event_time); } gboolean GNUNET_GTK_main_window_metadata_treeview_button_press_event_cb (GtkWidget * widget, GdkEventButton * event, gpointer user_data) { /* Ignore double-clicks and triple-clicks */ if (event->button == 3 && event->type == GDK_BUTTON_PRESS) { do_metadata_popup_menu (widget, event, user_data); return TRUE; } return FALSE; } gboolean GNUNET_GTK_main_window_metadata_treeview_popup_menu_cb (GtkWidget * widget, gpointer user_data) { do_metadata_popup_menu (widget, NULL, user_data); return TRUE; } /* end of gnunet-fs-gtk-event_handler.c */