/* 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_common.h" #include "gnunet-fs-gtk_download-save-as.h" #include "gnunet-fs-gtk_event-handler.h" #include "gnunet-fs-gtk_unindex.h" /** * We have a single tab where we display publishing operations. * So there is only one instance of this struct. */ struct PublishTab { /** * Frame for the tab. */ GtkWidget *frame; /** * Associated builder. */ GtkBuilder *builder; /** * Associated tree store. */ GtkTreeStore *ts; }; /** * Information we keep for each file or directory being published. * Used to quickly identify the tab and row of the operation; stored * in the user-context of the FS library for the publish operation. */ 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; /** * Is this the top-level entry for the publish operation * or sub-operation? */ int is_top; }; /** * Head of linked list of tabs for searches. */ static struct SearchTab *search_tab_head; /** * Tail of linked list of tabs for searches. */ static struct SearchTab *search_tab_tail; /** * Special tab we use to for downloads-by-URIs and downloads * where the search tab has been closed ("parent lost"). */ static struct SearchTab *uri_tab; /** * Special tab we use to store publishing operations. */ static struct PublishTab *publish_tab; /* ***************** Search event handling ****************** */ /** * Clear the metadata list and the preview widget. */ static void clear_metadata_display () { GtkImage *image; GtkListStore *ms; 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); } /** * 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) { /* FIXME-FEATURE: implement... */ return NULL; } /** * Called recursively to build a suggested filename by prepending * suggested names for its parent directories (if any). * * @param tm tree model this function gets the data from * @param iter current position in the tree, for which we want a suggested filename * @param top GNUNET_YES for the original call to this function, * GNUNET_NO for recursive calls; used to decide if the * current call is eligible to set 'anonymity' and 'local_parents' * @param local_parents set to GNUNET_YES if all parents are directories, and are downloaded. * @param anonymity set to the anonymity level of the closest ancestor download (if any); * set to "-1" for uninitialized initially (will be left at -1 if * no suitable parent download was found) * @param filename_is_absolute set to GNUNET_YES if the suggestion is an absolute filename, * GNUNET_NO for relative filenames (gotten from meta data) * @return suggested filename, possibly NULL */ 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; char *filename; int downloaded_anonymity; int have_a_parent; struct GNUNET_CONTAINER_MetaData *meta; GtkTreeIter parent; const char *basename; char *dot; /* FIXME-BUG-MAYBE: this function is likely responsible for not always suggesting the best filename... To be investigated some more... */ gtk_tree_model_get (tm, iter, 0, &meta, 15, &local_filename, 16, &downloaded_anonymity, -1); if (GNUNET_NO == top) { if (NULL != local_filename) *local_parents = GNUNET_YES; else *local_parents = GNUNET_NO; if ( (downloaded_anonymity != -1) && (*anonymity == -1) ) *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 (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. */ if ( (NULL == dirname) && (GNUNET_NO == have_a_parent)) { /* This is the root. Use absolute path. */ basename = (const char *) local_filename; *filename_is_absolute = GNUNET_YES; } else { basename = GNUNET_STRINGS_get_short_name (local_filename); } if ( (NULL != basename) && (strlen (basename) > 0) ) { filename = GNUNET_strdup (basename); dot = strrchr (filename, '.'); if (dot) *dot = '\0'; } else { filename = GNUNET_FS_meta_data_suggest_filename (meta); } } if ( (NULL != dirname) && (NULL != filename) ) { GNUNET_asprintf (&result, "%s%s%s", dirname, DIR_SEPARATOR_STR, filename); GNUNET_free (filename); GNUNET_free (dirname); return result; } if (NULL != filename) return filename; return NULL; } /** * This function is called when the user double-clicks on a search * result. Begins the download, if necessary by opening the "save as" * window. * * @param tree_view tree view with the details * @param path path selecting which entry we want to download * @param tab the search tab where the user triggered the download request * @param is_recursive was the request for a recursive download? * @param save_as force opening the 'save as' dialog? */ static void start_download (GtkTreeView *tree_view, GtkTreePath *path, struct SearchTab *tab, int is_recursive, int save_as) { GtkTreeModel *tm; GtkTreeIter iter; struct GNUNET_FS_Uri *uri; struct GNUNET_CONTAINER_MetaData *meta; struct SearchResult *sr; struct DownloadEntry *de; 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; tm = gtk_tree_view_get_model (tree_view); if (TRUE != gtk_tree_model_get_iter (tm, &iter, path)) { GNUNET_break (0); return; } gtk_tree_model_get (tm, &iter, 0, &meta, 1, &uri, 9, &sr, -1); if (NULL == uri) { /* user clicked on directory that was opened (not downloaded!), so we have no URI and downloading makes no sense. Ignore! */ return; } if (NULL != sr->download) { /* download already active! */ return; } if (!(GNUNET_FS_uri_test_chk (uri) || GNUNET_FS_uri_test_loc (uri))) { /* can only download chk/loc URIs, ignore */ /* break because in this case, we should not have even gotten here */ GNUNET_break (0); 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_NO; 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 */ de = GNUNET_malloc (sizeof (struct DownloadEntry)); de->uri = GNUNET_FS_uri_dup (uri); de->filename = buf; de->sr = sr; sr->download = de; de->anonymity = anonymity; de->is_recursive = is_recursive; de->is_directory = GNUNET_FS_meta_data_test_for_directory (meta); if (save_as) have_a_suggestion = GNUNET_NO; if ( (GNUNET_YES == local_parents) && (GNUNET_YES == have_a_suggestion) ) /* Skip the dialog, call directly */ GNUNET_FS_GTK_download_context_start_download (de); else GNUNET_FS_GTK_open_download_as_dialog (de); } /** * Context for the search list popup menu. */ struct SearchListPopupContext { /** * Tab where the search list popup was created. */ struct SearchTab *tab; /** * Row where the search list popup was created. */ GtkTreeRowReference *rr; /** * Search result at the respective row. */ struct SearchResult *sr; }; /** * An item was selected from the context menu; destroy the menu shell. * * @param menushell menu to destroy * @param user_data the 'struct SearchListPopupContext' of the menu */ static void search_list_popup_selection_done (GtkMenuShell *menushell, gpointer user_data) { struct SearchListPopupContext *spc = user_data; gtk_widget_destroy (GTK_WIDGET (menushell)); gtk_tree_row_reference_free (spc->rr); GNUNET_free (spc); } /** * This function is called when the user double-clicks on a search * result. Begins the download, if necessary by opening the "save as" * window. * * @param tree_view tree view with the details * @param path path selecting which entry we want to download * @param column unused entry specifying which column the mouse was in * @param user_data the 'struct SearchTab' that was activated */ void GNUNET_FS_GTK_search_treeview_row_activated (GtkTreeView * tree_view, GtkTreePath * path, GtkTreeViewColumn * column, gpointer user_data) { struct SearchTab *tab = user_data; struct GNUNET_FS_Uri *uri; GtkTreeModel *tm; GtkTreeIter iter; tm = gtk_tree_view_get_model (tree_view); if (TRUE != gtk_tree_model_get_iter (tm, &iter, path)) { GNUNET_break (0); return; } gtk_tree_model_get (tm, &iter, 1, &uri, -1); if (NULL == uri) { /* user clicked on directory that was opened (not downloaded!), so we have no URI and downloading makes no sense. Ignore! */ return; } if (GNUNET_FS_uri_test_ksk (uri) || GNUNET_FS_uri_test_sks (uri)) { GNUNET_FS_GTK_handle_uri (uri); return; } /* must be chk/loc URI, start download */ start_download (tree_view, path, tab, GNUNET_NO, GNUNET_NO); } /** * "Download" was selected in the current search context menu. * * @param spc the 'struct SearchListPopupContext' of the menu * @param is_recursive was this the 'recursive' option? * @parma save_as was this the 'save as' option? */ static void start_download_ctx_menu_helper (struct SearchListPopupContext *spc, int is_recursive, int save_as) { GtkTreePath *path; GtkTreeView *tv; path = gtk_tree_row_reference_get_path (spc->rr); tv = GTK_TREE_VIEW (gtk_builder_get_object (spc->tab->builder, "_search_result_frame")); start_download (tv, path, spc->tab, is_recursive, save_as); gtk_tree_path_free (path); } /** * "Download" was selected in the current search context menu. * * @param item the 'download' menu item * @param user_data the 'struct SearchListPopupContext' of the menu */ static void start_download_ctx_menu (GtkMenuItem *item, gpointer user_data) { struct SearchListPopupContext *spc = user_data; start_download_ctx_menu_helper (spc, GNUNET_NO, GNUNET_NO); } /** * "Download recursively" was selected in the current search context menu. * * @param item the 'download recursively' menu item * @param user_data the 'struct SearchListPopupContext' of the menu */ static void start_download_recursively_ctx_menu (GtkMenuItem *item, gpointer user_data) { struct SearchListPopupContext *spc = user_data; start_download_ctx_menu_helper (spc, GNUNET_YES, GNUNET_NO); } /** * "Download as..." was selected in the current search context menu. * * @param item the 'download as...' menu item * @param user_data the 'struct SearchListPopupContext' of the menu */ static void start_download_as_ctx_menu (GtkMenuItem *item, gpointer user_data) { struct SearchListPopupContext *spc = user_data; start_download_ctx_menu_helper (spc, GNUNET_NO, GNUNET_YES); } /** * Download "abort" was selected in the current search context menu. * * @param item the 'abort' menu item * @parma user_data the 'struct SearchListPopupContext' with the download to abort. */ static void abort_download_ctx_menu (GtkMenuItem *item, gpointer user_data) { struct SearchListPopupContext *spc = user_data; struct DownloadEntry *de = spc->sr->download; GNUNET_assert (de->dc != NULL); GNUNET_FS_download_stop (de->dc, GNUNET_YES); } /** * Copy current URI to clipboard was selected in the current context menu. * * @param item the 'copy-to-clipboard' menu item * @parma user_data the 'struct SearchListPopupContext' of the menu */ static void copy_search_uri_to_clipboard_ctx_menu (GtkMenuItem *item, gpointer user_data) { struct SearchListPopupContext *spc = user_data; GtkTreePath *path; GtkTreeView *tv; GtkTreeModel *tm; GtkTreeIter iter; struct GNUNET_FS_Uri *uri; char *uris; GtkClipboard *cb; path = gtk_tree_row_reference_get_path (spc->rr); tv = GTK_TREE_VIEW (gtk_builder_get_object (spc->tab->builder, "_search_result_frame")); tm = gtk_tree_view_get_model (tv); if (! 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); 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); } /** * Context menu was requested for a search result list. * Compute which menu items are applicable and display * an appropriate menu. * * @param tm tree model underlying the tree view where the event happened * @param tab tab where the event happened * @param event_button the event * @return FALSE if no menu could be popped up, * TRUE if there is now a pop-up menu */ static gboolean search_list_popup (GtkTreeModel *tm, struct SearchTab *tab, gint init_button, guint32 event_time, GtkTreeIter *iter) { GtkMenu *menu; GtkWidget *child; GtkTreePath *path; struct SearchResult *sr; struct GNUNET_FS_Uri *uri; struct SearchListPopupContext *spc; spc = GNUNET_malloc (sizeof (struct SearchListPopupContext)); spc->tab = tab; path = gtk_tree_model_get_path (tm, iter); spc->rr = gtk_tree_row_reference_new (tm, path); gtk_tree_path_free (path); gtk_tree_model_get (tm, iter, 1, &uri, 9, &sr, -1); spc->sr = sr; menu = GTK_MENU (gtk_menu_new ()); if ( (NULL == sr->download) && (NULL != uri) && ( (GNUNET_FS_uri_test_chk (uri) || GNUNET_FS_uri_test_loc (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), spc); 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), spc); 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 _as...")); g_signal_connect (child, "activate", G_CALLBACK (start_download_as_ctx_menu), spc); 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); } if ( (NULL != sr->download) && (GNUNET_YES != sr->download->is_done) ) { child = gtk_menu_item_new_with_label (_("_Abort download")); g_signal_connect (child, "activate", G_CALLBACK (abort_download_ctx_menu), spc); 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); } if (NULL != uri) { child = gtk_menu_item_new_with_label (_("_Copy URI to Clipboard")); g_signal_connect (child, "activate", G_CALLBACK (copy_search_uri_to_clipboard_ctx_menu), spc); 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); } g_signal_connect (menu, "selection-done", G_CALLBACK (search_list_popup_selection_done), spc); gtk_menu_popup (menu, NULL, NULL, NULL, NULL, init_button, event_time); return TRUE; } /** * We got a 'popup-menu' event, display the context menu. * * @param widget the tree view where the event happened * @param user_data the 'struct SearchTab' of the tree view * @return FALSE if no menu could be popped up, * TRUE if there is now a pop-up menu */ gboolean GNUNET_FS_GTK_search_treeview_popup_menu (GtkWidget *widget, gpointer user_data) { GtkTreeView *tv = GTK_TREE_VIEW (widget); struct SearchTab *tab = user_data; GtkTreeSelection *sel; GtkTreeIter iter; GtkTreeModel *tm; sel = gtk_tree_view_get_selection (tv); if (! gtk_tree_selection_get_selected (sel, &tm, &iter)) return FALSE; /* nothing selected */ return search_list_popup (tm, tab, 0, gtk_get_current_event_time (), &iter); } /** * We got a right-click on the search result list. Display the context * menu. * * @param widget the GtkTreeView with the search result list * @param event the event, we only care about button events * @param user_data the 'struct SearchTab' the widget is in * @return FALSE if no menu could be popped up, * TRUE if there is now a pop-up menu */ gboolean GNUNET_FS_GTK_search_treeview_button_press_event (GtkWidget * widget, GdkEvent * event, gpointer user_data) { GtkTreeView *tv = GTK_TREE_VIEW (widget); GdkEventButton *event_button = (GdkEventButton *) event; struct SearchTab *tab = user_data; GtkTreeModel *tm; GtkTreePath *path; GtkTreeIter iter; if ( (event->type != GDK_BUTTON_PRESS) || (event_button->button != 3) ) return FALSE; /* not a right-click */ if (! gtk_tree_view_get_path_at_pos (tv, event_button->x, event_button->y, &path, NULL, NULL, NULL)) return FALSE; /* click outside of area with values, ignore */ tm = gtk_tree_view_get_model (tv); if (! gtk_tree_model_get_iter (tm, &iter, path)) return FALSE; /* not sure how we got a path but no iter... */ gtk_tree_path_free (path); return search_list_popup (tm, tab, event_button->button, event_button->time, &iter); } /** * Recalculate and update the label for a search, as we have * received additional search results. * * @param tab search tab for which we should update the label */ static void update_search_label (struct SearchTab *tab) { char *label_text; while (tab->parent != NULL) tab = tab->parent->tab; if (tab->num_results > 0) GNUNET_asprintf (&label_text, "%.*s%s (%u)", 20, tab->query_txt, strlen (tab->query_txt) > 20 ? "..." : "", tab->num_results); else GNUNET_asprintf (&label_text, "%.*s%s", 20, tab->query_txt, strlen (tab->query_txt) > 20 ? "..." : ""); gtk_label_set_text (tab->label, label_text); gtk_widget_set_tooltip_text (GTK_WIDGET (tab->label), tab->query_txt); GNUNET_free (label_text); } /** * Close a search tab and free associated state. Assumes that the * respective tree model has already been cleaned up (this just * updates the notebook and frees the 'tab' itself). * * @param tab search tab to close */ static void close_search_tab (struct SearchTab *tab) { GtkNotebook *notebook; int index; int i; if (tab->parent != NULL) { /* not a top-level search (namespace update search), do not close tab here! */ GNUNET_free (tab); return; } clear_metadata_display (); 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); } /** * 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. * * @param src_model source model * @param src_iter parent of the nodes to move * @param dst_tab destination tab * @param dst_iter new parent of the entries we are moving */ static void copy_children (GtkTreeModel * src_model, GtkTreeIter * src_iter, struct SearchTab *dst_tab, 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_old; struct SearchResult *search_result_new; gchar *mimetype; guint applicability_rank; guint availability_certainty; gint availability_rank; gchar *downloaded_filename; gint downloaded_anonymity; if (! gtk_tree_model_iter_children (src_model, &src_child, src_iter)) return; 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_old, 10, &mimetype, 11, &applicability_rank, 12, &availability_certainty, 13, &availability_rank, 14, &completed, 15, &downloaded_filename, 16, &downloaded_anonymity, -1); search_result_new = GNUNET_malloc (sizeof (struct SearchResult)); search_result_new->tab = dst_tab; search_result_new->download = search_result_old->download; if (NULL != search_result_old->download) { search_result_old->download = NULL; search_result_new->download->sr = search_result_new; } gtk_tree_store_insert_with_values (dst_tab->ts, &dst_child, dst_iter, G_MAXINT, 0, GNUNET_CONTAINER_meta_data_duplicate (meta), 1, GNUNET_FS_uri_dup (uri), 2, filesize, 3, preview, 4, percent_progress, 5, percent_availability, 6, filename, 7, uri_as_string, 8, status_colour, 9, search_result_new, 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); path = gtk_tree_model_get_path (GTK_TREE_MODEL (dst_tab->ts), &dst_child); search_result_new->rr = gtk_tree_row_reference_new (GTK_TREE_MODEL (dst_tab->ts), path); gtk_tree_path_free (path); copy_children (src_model, &src_child, dst_tab, &dst_child); } while (gtk_tree_model_iter_next (src_model, &src_child)); } /** * Handle the case where an active download lost its * search parent by moving it to the URI tab. * * @param de download where the parent (i.e. search) was lost */ static void download_lost_parent (struct DownloadEntry *de) { GtkTreeIter iter; GtkTreePath *path; GtkTreeModel *tm_old; GtkTreeIter iter_old; GtkTreeModel *model; struct GNUNET_CONTAINER_MetaData *meta; struct GNUNET_FS_Uri *uri; guint64 completed; guint percent_progress; guint percent_availability; gchar *filename; gchar *status_colour; guint applicability_rank; guint availability_certainty; gint availability_rank; gchar *downloaded_filename; gint downloaded_anonymity; /* find the 'old' root */ tm_old = GTK_TREE_MODEL (de->sr->tab->ts); path = gtk_tree_row_reference_get_path (de->sr->rr); if (! gtk_tree_model_get_iter (tm_old, &iter_old, path)) { GNUNET_break (0); gtk_tree_path_free (path); return; } gtk_tree_path_free (path); gtk_tree_model_get (tm_old, &iter_old, 0, &meta, 1, &uri, 4, &percent_progress, 5, &percent_availability, 6, &filename, 8, &status_colour, 11, &applicability_rank, 12, &availability_certainty, 13, &availability_rank, 14, &completed, 15, &downloaded_filename, 16, &downloaded_anonymity, -1); GNUNET_assert (GNUNET_YES == GNUNET_FS_uri_test_equal (uri, de->uri)); GNUNET_assert (de->sr->download == de); de->sr->download = NULL; /* create the target root */ de->sr = GNUNET_GTK_add_to_uri_tab (meta, uri); de->sr->download = de; /* get positions of the 'new' root */ model = GTK_TREE_MODEL (de->sr->tab->ts); path = gtk_tree_row_reference_get_path (de->sr->rr); if (! gtk_tree_model_get_iter (model, &iter, path)) { GNUNET_break (0); gtk_tree_path_free (path); return; } gtk_tree_path_free (path); gtk_tree_store_set (de->sr->tab->ts, &iter, 4, percent_progress, 5, percent_availability, 6, filename, 8, status_colour, 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 (status_colour); /* finally, move all children over as well */ copy_children (tm_old, &iter_old, de->sr->tab, &iter); } /** * Moves all of the downloads in the given subtree to the URI tab * and cleans up the state of the other entries from the view. * The subtree itself will be removed from the tree view later. * * @param tm tree model * @param iter parent of the subtree to check */ static void move_downloads_in_subtree (GtkTreeModel *tm, GtkTreeIter *iter) { GtkTreeIter child; struct SearchResult *sr; struct GNUNET_CONTAINER_MetaData *meta; struct GNUNET_FS_Uri *uri; if (gtk_tree_model_iter_children (tm, &child, iter)) { do { gtk_tree_model_get (tm, &child, 0, &meta, 1, &uri, 9, &sr, -1); if (NULL != sr->download) { if (sr->download->is_done == GNUNET_YES) { /* got a finished download, stop it */ GNUNET_FS_download_stop (sr->download->dc, GNUNET_YES); } else { /* got an active download, move to URI tab! */ download_lost_parent (sr->download); } } GNUNET_assert (NULL == sr->download); move_downloads_in_subtree (tm, &child); GNUNET_FS_uri_destroy (uri); if (NULL != meta) GNUNET_CONTAINER_meta_data_destroy (meta); gtk_tree_row_reference_free (sr->rr); GNUNET_free (sr); /* get ready for removal of the tree */ gtk_tree_store_set (GTK_TREE_STORE (tm), &child, 0, NULL, 1, NULL, 9, NULL, -1); } while (TRUE == gtk_tree_model_iter_next (tm, &child)); } } /** * Free a particular search result and remove the respective * entries from the respective tree store. This function * is called when a search is stopped to clean up the state * of the tab. * * @param sr the search result to clean up */ 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 ( (NULL == sr) || (NULL == sr->rr) || (NULL == (tm = gtk_tree_row_reference_get_model (sr->rr))) || (NULL == (tp = gtk_tree_row_reference_get_path (sr->rr))) ) { GNUNET_break (0); return; } if (! 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); GNUNET_free (sr); move_downloads_in_subtree (tm, &iter); GNUNET_FS_GTK_remove_treestore_subtree (GTK_TREE_STORE (tm), &iter); } /** * Selected row has changed in search result tree view, update preview * and metadata areas. * * @param tv the tree view in a search tab where the selection changed * @param user_data the 'struct SearchTab' that contains the tree view */ void GNUNET_FS_GTK_search_treeview_cursor_changed (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 (! gtk_tree_selection_get_selected (sel, &model, &iter)) { /* nothing selected, clear preview */ gtk_image_clear (image); return; } meta = NULL; pixbuf = NULL; gtk_tree_model_get (model, &iter, 0, &meta, 3, &pixbuf, -1); if (NULL != pixbuf) { gtk_image_set_from_pixbuf (image, pixbuf); g_object_unref (G_OBJECT (pixbuf)); } if (NULL != meta) GNUNET_CONTAINER_meta_data_iterate (meta, &GNUNET_FS_GTK_add_meta_data_to_list_store, ms); } /** * Page switched in main notebook, update thumbnail and * metadata views. * * @param dummy widget emitting the event, unused * @param data master Gtk builder, unused */ 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); for (tab = search_tab_head; NULL != tab; tab = tab->next) { if (tab->frame != w) continue; tv = GTK_TREE_VIEW (gtk_builder_get_object (tab->builder, "_search_result_frame")); GNUNET_FS_GTK_search_treeview_cursor_changed (tv, tab); return; } /* active tab is not a search tab (likely the 'publish' tab), clear meta data and preview widgets */ 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); } /** * User clicked on the 'close' button for a search tab. Tell FS to stop the search. * * @param button the 'close' button * @param user_data the 'struct SearchTab' of the tab to close */ void GNUNET_FS_GTK_search_result_close_button_clicked (GtkButton *button, gpointer user_data) { struct SearchTab *tab = user_data; struct GNUNET_FS_SearchContext *sc; sc = tab->sc; if (NULL == sc) { GNUNET_break (0); return; } tab->sc = NULL; GNUNET_FS_search_stop (sc); } /** * The user clicked on the 'pause' button for a search tab. Tell FS to pause the search. * * @param button the 'pause' button * @param user_data the 'struct SearchTab' of the tab to pause */ void GNUNET_FS_GTK_search_result_pause_button_clicked (GtkButton *button, gpointer user_data) { struct SearchTab *tab = user_data; if (NULL == tab->sc) { GNUNET_break (0); return; } GNUNET_FS_search_pause (tab->sc); gtk_widget_show (tab->play_button); gtk_widget_hide (tab->pause_button); } /** * The user clicked on the 'resume' button for a search tab. Tell FS to resume the search. * * @param button the 'resume' button * @param user_data the 'struct SearchTab' of the tab to resume */ void GNUNET_FS_GTK_search_result_play_button_clicked (GtkButton * button, gpointer user_data) { struct SearchTab *tab = user_data; if (NULL == tab->sc) { GNUNET_break (0); return; } GNUNET_FS_search_continue (tab->sc); gtk_widget_show (tab->pause_button); gtk_widget_hide (tab->play_button); } /** * Stops all of the downloads in the given subtree. * * @param tm tree model * @param iter parent of the subtree to check * @return GNUNET_YES if there are no active downloads left in the subtree */ static int stop_downloads_in_subtree (GtkTreeModel *tm, GtkTreeIter *iter) { GtkTreeIter child; struct SearchResult *sr; int ret; ret = GNUNET_YES; if (gtk_tree_model_iter_children (tm, &child, iter)) { do { gtk_tree_model_get (tm, &child, 9, &sr, -1); if ( (NULL != sr->download) && (sr->download->is_done == GNUNET_YES) ) { /* got a finished download, stop it */ GNUNET_FS_download_stop (sr->download->dc, GNUNET_YES); } if ( (NULL != sr->download) || (NULL != sr->result) ) ret = GNUNET_NO; if (GNUNET_YES != stop_downloads_in_subtree (tm, &child)) ret = GNUNET_NO; } while (TRUE == gtk_tree_model_iter_next (tm, &child)); } return ret; } /** * User clicked on the 'clean' button of a search tab. * 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. * * @param button the button pressed by the user * @param user_data the 'struct SearchTab' of the respective tab to clean up */ void GNUNET_FS_GTK_search_result_clear_button_clicked (GtkButton * button, gpointer user_data) { struct SearchTab *tab = user_data; struct SearchResult *sr; GtkTreeModel *tm; GtkTreeIter iter; tm = GTK_TREE_MODEL (tab->ts); if (! gtk_tree_model_get_iter_first (tm, &iter)) return; do { gtk_tree_model_get (tm, &iter, 9, &sr, -1); if ( (sr->download != NULL) && (sr->download->is_done == GNUNET_YES) ) { /* got a finished download, stop it */ GNUNET_FS_download_stop (sr->download->dc, GNUNET_YES); } if ( (NULL == sr->download) && (NULL == sr->result) && (GNUNET_YES == stop_downloads_in_subtree (tm, &iter)) ) { /* no active download and no associated FS-API search result; so this must be some left-over entry from an opened directory; clean it up */ free_search_result (sr); /* the above call clobbered our 'iter', restart from the beginning... */ if (! gtk_tree_model_get_iter_first (tm, &iter)) return; } } while (gtk_tree_model_iter_next (tm, &iter)); } /** * We received a search error message from the FS library. * Present it to the user in an appropriate form. * * @param tab search tab affected by the error * @param emsg the error message */ static void handle_search_error (struct SearchTab *tab, const char *emsg) { gtk_label_set_text (tab->label, _("Error!")); gtk_widget_set_tooltip_text (GTK_WIDGET (tab->label), emsg); } /** * Obtain the mime type (or format description) will use to describe a search result from * the respective meta data. * * @param meta meta data to inspect * @return mime type to use, possibly NULL */ static char * get_mimetype_from_metadata (const struct GNUNET_CONTAINER_MetaData *meta) { return GNUNET_CONTAINER_meta_data_get_first_by_types (meta, EXTRACTOR_METATYPE_MIMETYPE, EXTRACTOR_METATYPE_FORMAT, -1); } /** * Some additional information about a search result has been * received. Update the view accordingly. * * @param sr search result that is being updated * @param meta updated meta data * @param availability_rank updated availability information * @param availability_certainty updated availability certainty * @param applicability_rank updated applicability information */ static void update_search_result (struct SearchResult *sr, const struct GNUNET_CONTAINER_MetaData *meta, uint32_t applicability_rank, int32_t availability_rank, uint32_t availability_certainty) { 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; int desc_is_a_dup; if (sr == NULL) { GNUNET_break (0); return; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Updating search result SR=%p with %d, %u, %u\n", sr, availability_rank, availability_certainty, applicability_rank); desc = GNUNET_FS_GTK_get_description_from_metadata (meta, &desc_is_a_dup); mime = get_mimetype_from_metadata (meta); 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 (NULL != ometa) GNUNET_CONTAINER_meta_data_destroy (ometa); if (availability_certainty > 0) percent_avail = 50 + (2 * availability_rank - availability_certainty) * 50 / availability_certainty; else percent_avail = 50; 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 (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")); GNUNET_FS_GTK_search_treeview_cursor_changed (tv, sr->tab); } } /** * Add a search result to the given search tab. This function is called * not only for 'normal' search results but also for directories that * are being opened and if the user manually enters a URI. * * @param tab search tab to extend, never NULL * @param parent_rr reference to parent entry in search tab, NULL for normal * search results, * @param uri uri to add, can be NULL for top-level entry of a directory opened from disk * (in this case, we don't know the URI and should probably not * bother to calculate it) * @param meta metadata of the entry * @param result associated FS search result (can be NULL if this result * was part of a directory) * @param applicability_rank how relevant is the result * @return struct representing the search result (also stored in the tree * model at 'iter') */ struct SearchResult * GNUNET_GTK_add_search_result (struct SearchTab *tab, 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 iter; GtkTreeIter *pitr; GtkTreeIter pmem; GtkTreePath *path; GtkTreeModel *tm; GtkTreeStore *ts; uint64_t fsize; int desc_is_a_dup; if (NULL == uri) { /* opened directory file */ fsize = 0; status_colour = "gray"; mime = NULL; /* FIXME-FEATURE-MAYBE: should we set mime to directory? */ uris = GNUNET_strdup (_("no URI")); } else { if ( (GNUNET_FS_uri_test_loc (uri)) || (GNUNET_FS_uri_test_chk (uri)) ) { fsize = GNUNET_FS_uri_chk_get_file_size (uri); mime = get_mimetype_from_metadata (meta); status_colour = "white"; } else { /* FIXME-FEATURE-MAYBE: create mime type for namespaces? */ /* FIXME-BUG-MAYBE: can we encounter ksk URIs here too? */ fsize = 0; mime = GNUNET_strdup ("GNUnet namespace"); status_colour = "lightgreen"; } uris = GNUNET_FS_uri_to_string (uri); } desc = GNUNET_FS_GTK_get_description_from_metadata (meta, &desc_is_a_dup); 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 (! 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, fsize, 3, pixbuf /* preview */ , 4, 0 /* percent progress */ , 5, (fsize == 0) ? 100 : 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 (pixbuf != NULL) g_object_unref (pixbuf); GNUNET_free (uris); GNUNET_free (desc); GNUNET_free_non_null (mime); /* remember in 'sr' where we added the result */ 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); /* move up to the outermost tab, in case this is an 'inner' search (namespace update case) */ while (tab->parent != NULL) tab = tab->parent->tab; tab->num_results++; return sr; } /** * We have received a search result from the FS API. Add it to the * respective search tab. The search result can be an 'inner' * search result (updated result for a namespace search) or a * top-level search result. Update the tree view and the label * of the search tab accordingly. * * @param tab the search tab where the new result should be added * @param parent parent search result (if this is a namespace update result), or NULL * @param uri URI of the search result * @param meta meta data for the result * @param result FS API handle to the result * @param applicability_rank how applicable is the result to the query * @return struct representing the search result (also stored in the tree * model at 'iter') */ static struct SearchResult * process_search_result (struct SearchTab *tab, 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 SearchResult *sr; sr = GNUNET_GTK_add_search_result (tab, (parent != NULL) ? parent->rr : NULL, uri, meta, result, applicability_rank); update_search_label (tab); return sr; } /** * Setup a new search tab. * * @param sc context with FS for the search, NULL for none (open-URI/orphan tab) * @param query the query, NULL for none (open-URI/orphan tab) * @return search tab handle */ static struct SearchTab * setup_search_tab (struct GNUNET_FS_SearchContext *sc, const struct GNUNET_FS_Uri *query) { struct SearchTab *tab; GtkNotebook *notebook; GtkWindow *sf; gint pages; tab = GNUNET_malloc (sizeof (struct SearchTab)); GNUNET_CONTAINER_DLL_insert (search_tab_head, search_tab_tail, tab); tab->sc = sc; if (query == NULL) { /* no real query, tab is for non-queries, use special label */ 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")); tab->close_button = GTK_WIDGET (gtk_builder_get_object (tab->builder, "_search_result_label_close_button")); tab->play_button = GTK_WIDGET (gtk_builder_get_object (tab->builder, "_search_result_label_play_button")); tab->pause_button = GTK_WIDGET (gtk_builder_get_object (tab->builder, "_search_result_label_pause_button")); /* patch text */ update_search_label (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, that is a subtree representing namespace * 'update' results. We use a 'struct SearchTab' to represent this * sub-search. In the GUI, the presentation is similar to search * results in a directory, except that this is for a namespace search * result that gave pointers to an alternative keyword to use and this * is the set of the results found for this alternative keyword. * * All of the 'widget' elements of the returned 'search tab' reference * the parent search. The whole construction is essentially a trick * to allow us to store the FS-API's 'SearchContext' somewhere and to * find it when we get this kind of 'inner' search results (so that we * can then place them in the tree view in the right spot). * * FIXME-BUG-MAYBE: don't we need a bit more information then? Like exactly where * this 'right spot' is? Not sure how just having 'sc' helps there, * as it is not a search result (!) to hang this up on! This might * essentially boil down to an issue with the FS API, not sure... * * @param sc context with FS for the search * @param parent parent search tab * @return struct representing the search result (also stored in the tree * model at 'iter') */ 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->play_button = parent->tab->play_button; ret->label = parent->tab->label; return ret; } /** * Setup a new top-level entry in the URI/orphan tab. If necessary, create * the URI tab first. * * @param meta metadata for the new entry * @param uri URI for the new entry * @return the search result that was set up */ struct SearchResult * GNUNET_GTK_add_to_uri_tab (const struct GNUNET_CONTAINER_MetaData *meta, const struct GNUNET_FS_Uri *uri) { GtkNotebook *notebook; gint page; if (NULL == uri_tab) { uri_tab = setup_search_tab (NULL, NULL); gtk_widget_set_visible (uri_tab->close_button, FALSE); gtk_widget_set_visible (uri_tab->pause_button, FALSE); } /* make 'uri_tab' 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; } return GNUNET_GTK_add_search_result (uri_tab, NULL, uri, meta, NULL, 0); } /* ***************** Download event handling ****************** */ /** * Change the (background) color of the given download entry. * * @param de entry to change * @param color name of the color to use */ static void change_download_color (struct DownloadEntry *de, const char *color) { GtkTreeIter iter; GtkTreePath *path; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Changing download DE=%p color to %s\n", de, color); path = gtk_tree_row_reference_get_path (de->sr->rr); if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (de->sr->tab->ts), &iter, path)) { GNUNET_break (0); gtk_tree_path_free (path); return; } gtk_tree_path_free (path); gtk_tree_store_set (de->sr->tab->ts, &iter, 8, color, -1); } /** * A download operation was stopped. Remove all state associated with * it and reset the search result's background color to 'white'. * * @param de the download that was stopped */ static void stop_download (struct DownloadEntry *de) { change_download_color (de, "white"); de->dc = NULL; GNUNET_FS_GTK_free_download_entry (de); } /** * Closure for 'add_directory_entry'. */ struct AddDirectoryEntryContext { /** * Search tab where we need to expand the result list. */ struct SearchTab *tab; /** * 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. Whenever we * download a directory, this function is called on the entries in the * directory to add them to the search tab. Note that the function * maybe called twice for the same entry, once during incremental * processing and later once more when we have the complete directory. * * For the second round, the 'check_duplicates' flag will be set in * the closure. If called on an entry that already exists, the * function should simply do nothing. * * @param cls closure, our 'struct AddDirectoryEntryContext*' * @param filename name of the file in the directory * @param uri URI of the file, NULL for the directory itself * @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 (NULL == uri) { /* directory meta data itself */ /* FIXME-FEATURE-MAYBE: consider merging it with the meta data from the original search result... */ return; } if (ade->check_duplicates == GNUNET_YES) { tm = gtk_tree_row_reference_get_model (ade->prr); path = gtk_tree_row_reference_get_path (ade->prr); if (! 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->tab, ade->prr, uri, meta, NULL, 0); } /** * We got an event that some download is progressing. Update the tree * model accordingly. If the download is a directory, try to display * the contents. * * @param de download entry that is progressing * @param filename name of the downloaded file on disk (possibly a temporary file) * @param size overall size of the download * @param completed number of bytes we have completed * @param block_data current block we've downloaded * @param offset offset of block_data in the overall file * @param block_size number of bytes in block_data * @param depth depth of the block in the ECRS tree */ static void mark_download_progress (struct DownloadEntry *de, const char *filename, 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->sr->rr); if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (de->sr->tab->ts), &iter, path)) { GNUNET_break (0); gtk_tree_path_free (path); return; } gtk_tree_path_free (path); /* FIXME-DESIGN: should we replace the 'availability' with 'progress' once the download has started and re-use the space in the display? Probably yes, at least once we have a custom CellRenderer... */ gtk_tree_store_set (de->sr->tab->ts, &iter, 4, (guint) ((size > 0) ? (100 * completed / size) : 100) /* progress */, 5, (guint) ((size > 0) ? (100 * completed / size) : 100) /* availability == progress */, 14, completed, -1); if (completed < size) { /* partial completion, consider looking at the block */ if ( (depth == 0) && (block_size > 0) && (GNUNET_YES == de->is_directory) ) { /* got a data block of a directory, list its contents */ struct AddDirectoryEntryContext ade; ade.tab = de->sr->tab; ade.prr = de->sr->rr; ade.check_duplicates = GNUNET_NO; if (GNUNET_SYSERR == GNUNET_FS_directory_list_contents ((size_t) block_size, block_data, offset, &add_directory_entry, &ade)) { /* Mime type was wrong, this is not a directory, update model! */ de->is_directory = GNUNET_SYSERR; gtk_tree_store_set (de->sr->tab->ts, &iter, 10, "" /* unknown mime type */, -1); } } } else { /* full completion, look at the entire file */ if ( (GNUNET_YES == de->is_directory) && (filename != NULL) ) { struct AddDirectoryEntryContext ade; /* download was for a directory (and we have a temp file for scanning); add contents of the directory to the view */ ade.tab = de->sr->tab; ade.prr = de->sr->rr; ade.check_duplicates = GNUNET_YES; if (GNUNET_OK != GNUNET_FS_GTK_mmap_and_scan (filename, &add_directory_entry, &ade)) de->is_directory = GNUNET_NO; } } } /** * FS-API encountered an error downloading a file. Update the * view accordingly. * * @param de download that had an error * @param emsg error message to display */ static void mark_download_error (struct DownloadEntry *de, const char *emsg) { GtkTreeIter iter; GtkTreePath *path; change_download_color (de, "red"); de->is_done = GNUNET_YES; path = gtk_tree_row_reference_get_path (de->sr->rr); if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (de->sr->tab->ts), &iter, path)) { GNUNET_break (0); gtk_tree_path_free (path); return; } gtk_tree_path_free (path); gtk_tree_store_set (de->sr->tab->ts, &iter, 4, 0, 7, emsg, -1); } /** * FS-API notified us that we're done with a download. Update the * view accordingly. * * @param de download that has finished * @param size overall size of the file */ static void mark_download_completed (struct DownloadEntry *de, uint64_t size) { GtkTreeIter iter; GtkTreePath *path; de->is_done = GNUNET_YES; change_download_color (de, "green"); path = gtk_tree_row_reference_get_path (de->sr->rr); if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (de->sr->tab->ts), &iter, path)) { GNUNET_break (0); gtk_tree_path_free (path); return; } gtk_tree_path_free (path); gtk_tree_store_set (de->sr->tab->ts, &iter, 4, (guint) 100, 5, (guint) 100, -1); } /** * Setup a new download entry. * * @param de existing download entry for the download, or NULL (in which case we create a fresh one) * @param pde parent download entry, or NULL * @param sr search result, or NULL * @param dc download context (for stopping) * @param uri the URI, must not be NULL * @param filename filename on disk * @param meta metadata * @param size total size * @param completed current progress * @return download entry struct for the download (equal to 'de' if 'de' was not NULL) */ 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; 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 (NULL == de) { /* no existing download entry to build on, create a fresh one */ de = GNUNET_malloc (sizeof (struct DownloadEntry)); de->uri = GNUNET_FS_uri_dup (uri); } else { GNUNET_assert (GNUNET_YES == GNUNET_FS_uri_test_equal (de->uri, uri)); } de->dc = dc; de->pde = pde; if (NULL != sr) { /* have a search result, establish mapping de <--> sr */ if (NULL == de->sr) { GNUNET_assert (sr->download == NULL); de->sr = sr; sr->download = de; } else { GNUNET_assert (sr == de->sr); } } if ( (NULL == de->sr) && (NULL != pde) ) { /* child download, find appropriate search result from parent! */ GtkTreePath *path; GtkTreeModel *tm; GtkTreeIter iter; GtkTreeIter child; struct GNUNET_FS_Uri *uri; tm = GTK_TREE_MODEL (pde->sr->tab->ts); path = gtk_tree_row_reference_get_path (pde->sr->rr); if ( (! gtk_tree_model_get_iter (tm, &iter, path)) || (! gtk_tree_model_iter_children (tm, &child, &iter)) ) { GNUNET_break (0); } else { do { gtk_tree_model_get (tm, &child, 1, &uri, 9, &de->sr, -1); if (GNUNET_YES == GNUNET_FS_uri_test_equal (de->uri, uri)) break; de->sr = NULL; } while (gtk_tree_model_iter_next (tm, &child)); GNUNET_break (NULL != de->sr); de->sr->download = de; } gtk_tree_path_free (path); } if (NULL == de->sr) { /* Stand-alone download with no 'row'/search result affiliated with the download so far; create a fresh entry for this download in the URI tab */ de->sr = GNUNET_GTK_add_to_uri_tab (meta, uri); de->sr->download = de; path = gtk_tree_row_reference_get_path (de->sr->rr); if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (de->sr->tab->ts), &iter, path)) { GNUNET_break (0); gtk_tree_path_free (path); return de; } } else { struct GNUNET_CONTAINER_MetaData *meta; /* get metadata from existing tab, might have a mime type */ path = gtk_tree_row_reference_get_path (de->sr->rr); if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (de->sr->tab->ts), &iter, path)) { GNUNET_break (0); gtk_tree_path_free (path); return de; } gtk_tree_model_get (GTK_TREE_MODEL (de->sr->tab->ts), &iter, 0, &meta, -1); de->is_directory = GNUNET_FS_meta_data_test_for_directory (meta); } gtk_tree_path_free (path); gtk_tree_store_set (de->sr->tab->ts, &iter, 4, (guint) ((size > 0) ? (100 * completed / size) : 100) /* progress */ , 6, filename /* filename/description */ , 8, "blue" /* status colour: pending */ , 9, de->sr, 14, completed, 15, de->filename, 16, de->anonymity, -1); return de; } /* ***************** Publish event handling ****************** */ /** * Change the (background) color of the given publish entry. * * @param pe entry to change * @param color name of the color to use */ static void change_publish_color (struct PublishEntry *pe, const char *color) { GtkTreeIter iter; GtkTreePath *path; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Changing publish PE=%p color to %s\n", pe, color); path = gtk_tree_row_reference_get_path (pe->rr); if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (pe->tab->ts), &iter, path)) { GNUNET_break (0); gtk_tree_path_free (path); return; } gtk_tree_path_free (path); gtk_tree_store_set (pe->tab->ts, &iter, 2, color, -1); } /** * We got an event that some publishing operation is progressing. * Update the tree model accordingly. * * @param pe publish entry that is progressing * @param size overall size of the file or directory * @param completed number of bytes we have completed */ static void mark_publish_progress (struct PublishEntry *pe, uint64_t size, uint64_t completed) { GtkTreeIter iter; GtkTreePath *path; path = gtk_tree_row_reference_get_path (pe->rr); if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (pe->tab->ts), &iter, path)) { GNUNET_break (0); gtk_tree_path_free (path); return; } gtk_tree_path_free (path); gtk_tree_store_set (pe->tab->ts, &iter, 3, (guint) ((size > 0) ? (100 * completed / size) : 100) /* progress */ , -1); } /** * FS-API notified us that we're done with some publish operation. * Update the view accordingly. * * @param pe publish operation that has finished * @param uri resulting URI */ static void handle_publish_completed (struct PublishEntry *pe, const struct GNUNET_FS_Uri *uri) { GtkTreeIter iter; GtkTreePath *path; char *uris; path = gtk_tree_row_reference_get_path (pe->rr); if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (pe->tab->ts), &iter, path)) { GNUNET_break (0); gtk_tree_path_free (path); return; } gtk_tree_path_free (path); pe->uri = GNUNET_FS_uri_dup (uri); uris = GNUNET_FS_uri_to_string (uri); gtk_tree_store_set (pe->tab->ts, &iter, 5, uris, -1); GNUNET_free (uris); change_publish_color (pe, "green"); } /** * We received a publish error message from the FS library. * Present it to the user in an appropriate form. * * @param pe publishing operation affected by the error * @param emsg the error message */ static void handle_publish_error (struct PublishEntry *pe, const char *emsg) { GtkTreeIter iter; GtkTreePath *path; path = gtk_tree_row_reference_get_path (pe->rr); if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (pe->tab->ts), &iter, path)) { GNUNET_break (0); gtk_tree_path_free (path); return; } gtk_tree_path_free (path); gtk_tree_store_set (pe->tab->ts, &iter, 5, emsg, -1); change_publish_color (pe, "red"); } /** * A publishing operation was stopped (in FS API). Free an entry in * the publish tab and its associated state. * * @param pe publishing operation that was stopped */ static void handle_publish_stop (struct PublishEntry *pe) { GtkTreeIter iter; GtkTreePath *path; path = gtk_tree_row_reference_get_path (pe->rr); /* This is a child of a directory, and we've had that directory free'd already */ if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (pe->tab->ts), &iter, path)) { GNUNET_break (0); return; } (void) gtk_tree_store_remove (pe->tab->ts, &iter); gtk_tree_path_free (path); gtk_tree_row_reference_free (pe->rr); if (pe->uri != NULL) { GNUNET_FS_uri_destroy (pe->uri); pe->uri = NULL; } GNUNET_free (pe); } /** * The user clicked on the "close" button of the publishing tab. * Tell FS to stop all active publish operations. Then close the tab. * * @param button the stop button * @param user_data the 'struct PublishTab' that is being closed */ void GNUNET_FS_GTK_publish_label_close_button_clicked (GtkButton * button, gpointer user_data) { struct PublishTab *tab = user_data; struct PublishEntry *pe; GtkTreeIter iter; GtkTreeModel *tm; GtkNotebook *notebook; int index; int i; GNUNET_assert (tab == publish_tab); /* stop all active operations */ tm = GTK_TREE_MODEL (publish_tab->ts); while (gtk_tree_model_iter_children (tm, &iter, NULL)) { gtk_tree_model_get (tm, &iter, 4, &pe, -1); GNUNET_FS_publish_stop (pe->pc); } clear_metadata_display (); /* remove tab from notebook */ 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); /* fully destroy tab */ g_object_unref (publish_tab->builder); GNUNET_free (publish_tab); publish_tab = NULL; } /** * The user started a publishing operation. Add it to the publishing * tab. If needed, create the publishing tab. * * @param pc the FS-API's publishing context for the operation * @param fn the name of the file (or directory) that is being published * @param fsize size of the file * @param parent parent of this publishing operation (for recursive operations), NULL for top-level operations * @return the publishing entry that will represent this operation */ 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; GtkNotebook *notebook; char *size_fancy; 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)); /* 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")); } /* decide where to insert in the tab */ if (NULL == parent) { 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; } /* create entry and perform insertion */ ent = GNUNET_malloc (sizeof (struct PublishEntry)); ent->is_top = (parent == NULL) ? GNUNET_YES : GNUNET_NO; ent->tab = publish_tab; ent->pc = pc; size_fancy = GNUNET_STRINGS_byte_size_fancy (fsize); 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); GNUNET_free (size_fancy); path = gtk_tree_model_get_path (GTK_TREE_MODEL (publish_tab->ts), &iter); ent->rr = gtk_tree_row_reference_new (GTK_TREE_MODEL (publish_tab->ts), path); gtk_tree_path_free (path); return ent; } /** * Context for the publish list popup menu. */ struct PublishListPopupContext { /** * Tab where the publish list popup was created. */ struct PublishTab *tab; /** * Row where the publish list popup was created. */ GtkTreeRowReference *rr; /** * Publishing entry at the respective row. */ struct PublishEntry *pe; }; /** * An item was selected from the context menu; destroy the menu shell. * * @param menushell menu to destroy * @param user_data the 'struct PublishListPopupContext' of the menu */ static void publish_list_popup_selection_done (GtkMenuShell *menushell, gpointer user_data) { struct PublishListPopupContext *ppc = user_data; gtk_widget_destroy (GTK_WIDGET (menushell)); gtk_tree_row_reference_free (ppc->rr); GNUNET_free (ppc); } /** * Publish "abort" was selected in the current publish context menu. * * @param item the 'abort' menu item * @parma user_data the 'struct PublishListPopupContext' with the operation to abort. */ static void abort_publish_ctx_menu (GtkMenuItem *item, gpointer user_data) { struct PublishListPopupContext *ppc = user_data; struct PublishEntry *pe = ppc->pe; if (NULL != pe->pc) GNUNET_FS_publish_stop (pe->pc); } /** * Copy current URI to clipboard was selected in the current context menu. * * @param item the 'copy-to-clipboard' menu item * @parma user_data the 'struct DownloadListPopupContext' of the menu */ static void copy_publish_uri_to_clipboard_ctx_menu (GtkMenuItem *item, gpointer user_data) { struct PublishListPopupContext *ppc = user_data; struct GNUNET_FS_Uri *uri; char *uris; GtkClipboard *cb; uri = ppc->pe->uri; 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); } /** * Context menu was requested for a publish result list. * Compute which menu items are applicable and display * an appropriate menu. * * @param tm tree model underlying the tree view where the event happened * @param tab tab where the event happened * @param event_button the event * @return FALSE if no menu could be popped up, * TRUE if there is now a pop-up menu */ static gboolean publish_list_popup (GtkTreeModel *tm, struct PublishTab *tab, gint init_button, guint32 event_time, GtkTreeIter *iter) { GtkMenu *menu; GtkWidget *child; GtkTreePath *path; struct PublishEntry *pe; struct PublishListPopupContext *ppc; gtk_tree_model_get (tm, iter, 4, &pe, -1); if ( (NULL == pe->uri) && ( (NULL == pe->pc) || (GNUNET_NO == pe->is_top) ) ) { /* no actions available, refuse to pop up */ return FALSE; } ppc = GNUNET_malloc (sizeof (struct PublishListPopupContext)); ppc->tab = tab; path = gtk_tree_model_get_path (tm, iter); ppc->rr = gtk_tree_row_reference_new (tm, path); gtk_tree_path_free (path); ppc->pe = pe; menu = GTK_MENU (gtk_menu_new ()); if (NULL != pe->uri) { child = gtk_menu_item_new_with_label (_("_Copy URI to Clipboard")); g_signal_connect (child, "activate", G_CALLBACK (copy_publish_uri_to_clipboard_ctx_menu), ppc); 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 if (NULL != pe->pc) { child = gtk_menu_item_new_with_label (_("_Abort publishing")); g_signal_connect (child, "activate", G_CALLBACK (abort_publish_ctx_menu), ppc); 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); } g_signal_connect (menu, "selection-done", G_CALLBACK (publish_list_popup_selection_done), ppc); gtk_menu_popup (menu, NULL, NULL, NULL, NULL, init_button, event_time); return TRUE; } /** * We got a 'popup-menu' event, display the context menu. * * @param widget the tree view where the event happened * @param user_data the 'struct PublishTab' of the tree view * @return FALSE if no menu could be popped up, * TRUE if there is now a pop-up menu */ gboolean GNUNET_FS_GTK_publish_treeview_popup_menu (GtkWidget *widget, gpointer user_data) { GtkTreeView *tv = GTK_TREE_VIEW (widget); struct PublishTab *tab = user_data; GtkTreeSelection *sel; GtkTreeIter iter; GtkTreeModel *tm; sel = gtk_tree_view_get_selection (tv); if (! gtk_tree_selection_get_selected (sel, &tm, &iter)) return FALSE; /* nothing selected */ return publish_list_popup (tm, tab, 0, gtk_get_current_event_time (), &iter); } /** * We got a right-click on the search result list. Display the context * menu. * * @param widget the GtkTreeView with the search result list * @param event the event, we only care about button events * @param user_data the 'struct SearchTab' the widget is in * @return FALSE if no menu could be popped up, * TRUE if there is now a pop-up menu */ gboolean GNUNET_FS_GTK_publish_treeview_button_press_event (GtkWidget * widget, GdkEvent * event, gpointer user_data) { GtkTreeView *tv = GTK_TREE_VIEW (widget); GdkEventButton *event_button = (GdkEventButton *) event; struct PublishTab *tab = user_data; GtkTreeModel *tm; GtkTreePath *path; GtkTreeIter iter; if ( (event->type != GDK_BUTTON_PRESS) || (event_button->button != 3) ) return FALSE; /* not a right-click */ if (! gtk_tree_view_get_path_at_pos (tv, event_button->x, event_button->y, &path, NULL, NULL, NULL)) return FALSE; /* click outside of area with values, ignore */ tm = gtk_tree_view_get_model (tv); if (! gtk_tree_model_get_iter (tm, &iter, path)) return FALSE; /* not sure how we got a path but no iter... */ gtk_tree_path_free (path); return publish_list_popup (tm, tab, event_button->button, event_button->time, &iter); } /* ***************** Master event handler ****************** */ /** * 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) { handle_publish_error (ret, info->value.publish.specifics.resume.message); } else if (info->value.publish.specifics.resume.chk_uri != NULL) { 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: mark_publish_progress (info->value.publish.cctx, info->value.publish.size, info->value.publish.completed); return info->value.publish.cctx; case GNUNET_FS_STATUS_PUBLISH_ERROR: handle_publish_error (info->value.publish.cctx, info->value.publish.specifics.error.message); return info->value.publish.cctx; case GNUNET_FS_STATUS_PUBLISH_COMPLETED: handle_publish_completed (info->value.publish.cctx, info->value.publish.specifics.completed.chk_uri); return info->value.publish.cctx; 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) mark_download_error (ret, info->value.download.specifics.resume.message); return ret; case GNUNET_FS_STATUS_DOWNLOAD_SUSPEND: stop_download (info->value.download.cctx); return NULL; case GNUNET_FS_STATUS_DOWNLOAD_PROGRESS: mark_download_progress (info->value.download.cctx, info->value.download.filename, 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); return info->value.download.cctx; case GNUNET_FS_STATUS_DOWNLOAD_ERROR: mark_download_error (info->value.download.cctx, info->value.download.specifics.error.message); return info->value.download.cctx; case GNUNET_FS_STATUS_DOWNLOAD_COMPLETED: mark_download_completed (info->value.download.cctx, info->value.download.size); return info->value.download.cctx; case GNUNET_FS_STATUS_DOWNLOAD_STOPPED: stop_download (info->value.download.cctx); return NULL; case GNUNET_FS_STATUS_DOWNLOAD_ACTIVE: change_download_color (info->value.download.cctx, "yellow"); return info->value.download.cctx; case GNUNET_FS_STATUS_DOWNLOAD_INACTIVE: change_download_color (info->value.download.cctx, "blue"); return info->value.download.cctx; case GNUNET_FS_STATUS_DOWNLOAD_LOST_PARENT: download_lost_parent (info->value.download.cctx); return info->value.download.cctx; 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_tab (info->value.search.sc, info->value.search.query); case GNUNET_FS_STATUS_SEARCH_RESUME: ret = setup_search_tab (info->value.search.sc, info->value.search.query); if (info->value.search.specifics.resume.message) 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); 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); return ret; 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: 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); return info->value.search.specifics.update.cctx; case GNUNET_FS_STATUS_SEARCH_ERROR: handle_search_error (info->value.search.cctx, info->value.search.specifics.error.message); return info->value.search.cctx; 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: return info->value.unindex.cctx; case GNUNET_FS_STATUS_UNINDEX_RESUME: return GNUNET_FS_GTK_unindex_handle_resume_ (info->value.unindex.uc, info->value.unindex.filename, info->value.unindex.size, info->value.unindex.completed, info->value.unindex.specifics.resume.message); case GNUNET_FS_STATUS_UNINDEX_SUSPEND: GNUNET_FS_GTK_unindex_handle_stop_ (info->value.unindex.cctx); return NULL; case GNUNET_FS_STATUS_UNINDEX_PROGRESS: GNUNET_FS_GTK_unindex_handle_progress_ (info->value.unindex.cctx, info->value.unindex.completed); return info->value.unindex.cctx; case GNUNET_FS_STATUS_UNINDEX_ERROR: GNUNET_FS_GTK_unindex_handle_error_ (info->value.unindex.cctx, info->value.unindex.specifics.error.message); return info->value.unindex.cctx; case GNUNET_FS_STATUS_UNINDEX_COMPLETED: GNUNET_FS_GTK_unindex_handle_completed_ (info->value.unindex.cctx); return info->value.unindex.cctx; case GNUNET_FS_STATUS_UNINDEX_STOPPED: GNUNET_FS_GTK_unindex_handle_stop_ (info->value.unindex.cctx); return NULL; default: GNUNET_break (0); break; } return NULL; } /* end of gnunet-fs-gtk-event_handler.c */