/* 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" /** * Columns in the file sharing result model. */ enum SEARCH_TAB_ModelColumns { /** * A gpointer. */ SEARCH_TAB_MC_METADATA = 0, /** * A gpointer. */ SEARCH_TAB_MC_URI = 1, /** * A guint64. */ SEARCH_TAB_MC_FILESIZE = 2, /** * A GdkPixbuf. */ SEARCH_TAB_MC_PREVIEW = 3, /** * A guint. */ SEARCH_TAB_MC_PERCENT_PROGRESS = 4, /** * A guint. */ SEARCH_TAB_MC_PERCENT_AVAILABILITY = 5, /** * A gchararray. */ SEARCH_TAB_MC_FILENAME = 6, /** * A gchararray. */ SEARCH_TAB_MC_URI_AS_STRING = 7, /** * A gchararray. */ SEARCH_TAB_MC_STATUS_COLOUR = 8, /** * A gpointer. */ SEARCH_TAB_MC_SEARCH_RESULT = 9, /** * A gchararray. */ SEARCH_TAB_MC_MIMETYPE = 10, /** * A guint. */ SEARCH_TAB_MC_APPLICABILITY_RANK = 11, /** * A guint. */ SEARCH_TAB_MC_AVAILABILITY_CERTAINTY = 12, /** * A gint. */ SEARCH_TAB_MC_AVAILABILITY_RANK = 13, /** * A guint64. */ SEARCH_TAB_MC_COMPLETED = 14, /** * A gchararray. */ SEARCH_TAB_MC_DOWNLOADED_FILENAME = 15, /** * A gint. */ SEARCH_TAB_MC_DOWNLOADED_ANONYMITY = 16, /** * A GdkPixbuf. */ SEARCH_TAB_MC_STATUS_ICON = 17, /** * A guint. */ SEARCH_TAB_MC_UNKNOWN_AVAILABILITY = 18 }; /** * Columns in the publish frame model. */ enum PUBLISH_TAB_ModelColumns { /** * A gchararray. */ PUBLISH_TAB_MC_FILENAME = 0, /** * A gchararray. */ PUBLISH_TAB_MC_FILESIZE = 1, /** * A gchararray. */ PUBLISH_TAB_MC_BGCOLOUR = 2, /** * A guint. */ PUBLISH_TAB_MC_PROGRESS = 3, /** * A gpointer. */ PUBLISH_TAB_MC_ENT = 4, /** * A gchararray. */ PUBLISH_TAB_MC_RESULT_STRING = 5, /** * A GdkPixbuf. */ PUBLISH_TAB_MC_STATUS_ICON = 6 }; /** * 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; /** * Animation handle associated with the tree store. */ struct GNUNET_FS_AnimationTreeViewHandle *atv; }; /** * 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; /** * Currently displayed search tab */ static struct SearchTab *current_search_tab = NULL; /** * Currently selected row in a search tab. */ static GtkTreePath *current_selected_search_result = NULL; /** * Animation to display while publishing. */ static struct GNUNET_FS_AnimationContext *animation_publishing; /** * Animation to display after publishing is complete. */ static struct GNUNET_FS_AnimationContext *animation_published; /** * Animation to display while downloading. */ static struct GNUNET_FS_AnimationContext *animation_downloading; /** * Animation to display after downloading is complete. */ static struct GNUNET_FS_AnimationContext *animation_downloaded; /** * Animation to display if a download has stalled. */ static struct GNUNET_FS_AnimationContext *animation_download_stalled; /** * Animation to display while searching for sources to download from. */ static struct GNUNET_FS_AnimationContext *animation_searching_sources; /** * Animation to display if we found sources to download from. */ static struct GNUNET_FS_AnimationContext *animation_found_sources; /** * Animation to display if we encountered a hard error. */ static struct GNUNET_FS_AnimationContext *animation_error; struct SearchTab * GNUNET_FS_GTK_get_current_search_tab () { return current_search_tab; } /* ***************** Search event handling ****************** */ static struct GNUNET_FS_AnimationContext * load_animation (const char *basename) { struct GNUNET_FS_AnimationContext *ac; const char *dd; char *fn; dd = GNUNET_GTK_get_data_dir (); GNUNET_asprintf (&fn, "%s%s.gif", dd, basename); ac = GNUNET_GTK_animation_context_create (fn); GNUNET_free (fn); return ac; } /** * Clear the metadata list and the preview widget. */ static void clear_metadata_display () { struct GNUNET_GTK_MainWindowContext *mctx = GNUNET_FS_GTK_get_main_context (); gtk_image_clear (mctx->preview_image); gtk_list_store_clear (mctx->md_liststore); } /** * 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-existing directory). * TODO: 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. * Buffer will be NUL-terminated, if not NULL. */ static char * get_default_download_directory (char *buffer, size_t size) { const struct GNUNET_CONFIGURATION_Handle *cfg; char *dirname; size_t dirname_len; size_t copy_bytes; cfg = GNUNET_FS_GTK_get_configuration (); if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (cfg, "gnunet-fs-gtk", "DEFAULT_DOWNLOAD_DIRECTORY", &dirname)) return NULL; if (GNUNET_YES != GNUNET_DISK_directory_test ((const char *) dirname, GNUNET_YES)) { GNUNET_free (dirname); return NULL; } dirname_len = strlen (dirname); if (dirname_len >= size) copy_bytes = size - 1; else copy_bytes = dirname_len; memcpy (buffer, dirname, copy_bytes); buffer[copy_bytes] = '\0'; GNUNET_free (dirname); return buffer; } /** * finished_chain - non-NULL for top-level call (for the item we're about to download), NULL otherwise * function sets it to GNUNET_YES if the item we're about to download was, in fact, already downloaded once, and thus we provide a name for it, * returning a finished relative filename that might only need .gnd appended to it, nothing else. * root_directory - top-level download directory to use. Set to the directory into which root of the tree (grand-*-parent item) was downloaded. * If there's no already-downloaded grand-*-parents, set to default download directory (thus it will anways be filled on return). * relative_directory - name of the directory in which we're about to download a file, relative to the root_directory. Whether it includes name of the file itself, depends on finished_chain. * anonymity - anonymity level of one of the *parents. Initialize to -1. If none were downloaded, remains -1. * Returned strings should be freed with GNUNET_free() if not NULL. */ static void build_relative_name (GtkTreeModel *tm, GtkTreeIter *iter, int *finished_chain, gchar **root_directory, gchar **relative_directory, int *anonymity) { char *filename; int downloaded_anonymity; GtkTreeIter parent; gtk_tree_model_get (tm, iter, SEARCH_TAB_MC_DOWNLOADED_FILENAME, &filename, SEARCH_TAB_MC_DOWNLOADED_ANONYMITY, &downloaded_anonymity, -1); if (! gtk_tree_model_iter_parent (tm, &parent, iter)) { /* Ok, we're at the top item. * bottom == GNUNET_YES is a corner case when we only have one * item (top and bottom at the same time), and it was already * downloaded once (has SEARCH_TAB_MC_DOWNLOADED_FILENAME set) and * cleaned up afterwards (downloadable again). */ if (filename == NULL) { /* Top-level item is not downloaded yet * (i.e. we only have one item, it's toplevel, and we're * about to start downloading it). * Use default download directory. */ char buf[FILENAME_MAX]; char *tmp; tmp = get_default_download_directory (buf, sizeof (buf)); /* If no download directory is known, try working directory */ if (NULL == tmp) tmp = g_strdup (getcwd (buf, sizeof (buf))); *root_directory = g_strdup (tmp); } else { char *dot_gnd; /* Toplevel item is a .gnd file, get its directory, * and use it as download root (for now). */ *root_directory = g_path_get_dirname (filename); *relative_directory = g_path_get_basename (filename); dot_gnd = strrchr (*relative_directory, '.'); if (NULL != dot_gnd && strcmp (dot_gnd, ".gnd") == 0) *dot_gnd = '\0'; if (finished_chain) *finished_chain = GNUNET_YES; } } else { build_relative_name (tm, &parent, NULL, root_directory, relative_directory, anonymity); /* We now know the root directory parent stems from, * and parent's name (without .gnd) relative to the root directory. * Now we need to check that root + reldir = directory * where current item resides (that is, it was not saved in a different directory). */ if (NULL != filename) { gchar *our_dirname = g_path_get_dirname (filename); gchar *our_expected_dirname = g_build_filename (*root_directory, *relative_directory, NULL); gchar *bname = g_path_get_basename (filename); int chain_ok; char *dot_gnd; /* FIXME: Use better checking (don't compare paths as strings, * only verify directory inode is the same, or something). */ #if WINDOWS /* Kind of stricmp() for utf-8 */ gchar *tmp = g_utf8_casefold (our_dirname, -1); gchar *tmpd = g_utf8_casefold (our_expected_dirname, -1); chain_ok = 0 == g_utf8_collate (tmp, tmpd); g_free (tmp); g_free (tmpd); #else chain_ok = 0 == g_utf8_collate (our_dirname, our_expected_dirname); #endif dot_gnd = strrchr (bname, '.'); if (NULL != dot_gnd && strcmp (dot_gnd, ".gnd") == 0) *dot_gnd = '\0'; if (!chain_ok) { /* User decided to download one of the directories into * a different place - respect that decision, pick that * place as the new root directory, and re-start relative name from here. */ g_free (*root_directory); *root_directory = g_strdup (our_dirname); g_free (*relative_directory); *relative_directory = g_strdup (bname); } else { /* Continue the chain from the same root directory */ gchar *new_relative_directory = g_build_filename (*relative_directory, bname, NULL); g_free (*relative_directory); *relative_directory = new_relative_directory; } if (finished_chain) *finished_chain = GNUNET_YES; g_free (our_dirname); g_free (our_expected_dirname); g_free (bname); } } if ((downloaded_anonymity != -1) && (*anonymity == -1)) *anonymity = downloaded_anonymity; g_free (filename); } /** * Builds 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 download_directory will receive a pointer to download directory. * free it with GNUNET_free() when done. * Will never be NULL on return (CWD will be used as a fallback). * @param anonymity will receive suggested anonymity (or -1 if anonymity can't be suggested) * @return suggested filename relative to download directory (free with GNUNET_free()), or NULL */ static char * get_suggested_filename_anonymity2 (GtkTreeModel *tm, GtkTreeIter *iter, char **download_directory, int *anonymity) { char *result; char *downloaddir; char *relname; char *filename; char *tmp; int downloaded_anonymity = -1; struct GNUNET_CONTAINER_MetaData *meta; size_t tmplen; int finished_chain; downloaddir = NULL; relname = NULL; finished_chain = GNUNET_NO; build_relative_name (tm, iter, &finished_chain, &downloaddir, &relname, &downloaded_anonymity); gtk_tree_model_get (tm, iter, SEARCH_TAB_MC_METADATA, &meta, -1); filename = GNUNET_FS_meta_data_suggest_filename (meta); /* Don't trust metadata */ tmp = (char *) GNUNET_STRINGS_get_short_name (filename); /* Really don't trust metadata */ if (NULL != tmp) { tmplen = strlen (tmp); if ((1 == tmplen && '.' == tmp[0]) || (2 <= tmplen && '.' == tmp[0] && '.' == tmp[1])) tmp = NULL; else if ((1 == tmplen && ('/' == tmp[0] || '\\' == tmp[0])) || 0 == tmplen) tmp = NULL; } if (NULL != 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'; if (relname) { if (finished_chain) GNUNET_asprintf (&result, "%s%s", relname, GNUNET_FS_DIRECTORY_EXT); else GNUNET_asprintf (&result, "%s%s%s%s", relname, DIR_SEPARATOR_STR, tmp, GNUNET_FS_DIRECTORY_EXT); } else GNUNET_asprintf (&result, "%s%s", tmp, GNUNET_FS_DIRECTORY_EXT); } else { if (relname) { if (finished_chain) result = GNUNET_strdup (relname); else GNUNET_asprintf (&result, "%s%s%s", relname, DIR_SEPARATOR_STR, tmp); } else result = GNUNET_strdup (tmp); } } else result = NULL; *download_directory = GNUNET_strdup (downloaddir); *anonymity = downloaded_anonymity; GNUNET_free (filename); g_free (downloaddir); g_free (relname); return result; } /** * 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); } /** * 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); /** * save_as - GNUNET_YES to open SaveAs dialog, GNUNET_NO to start downloading. * download_directly - GNUNET_YES to make SaveAs dialog initiate the download, * GNUNET_NO to only change names on the download panel. * Ingored if save_as is GNUNET_NO. */ static void start_download2 (int save_as, int download_directly) { struct GNUNET_GTK_MainWindowContext *mctx = GNUNET_FS_GTK_get_main_context (); struct SearchTab *st = GNUNET_FS_GTK_get_current_search_tab (); struct GNUNET_CONTAINER_MetaData *meta; GtkTreeView *tv; GtkTreeSelection *sel; GtkTreeModel *model; GtkTreeIter iter; GtkTreeIter parent_iter; struct SearchResult *sr; struct GNUNET_FS_Uri *uri; GtkTreePath *path; const gchar *filename; gchar *downloaddir; struct DownloadEntry *de; guint anonymity; gboolean recursive; GtkTreeIter next_item; tv = GTK_TREE_VIEW (gtk_builder_get_object (st->builder, "_search_result_frame")); sel = gtk_tree_view_get_selection (tv); if (!gtk_tree_selection_get_selected (sel, &model, &iter)) return; meta = NULL; gtk_tree_model_get (model, &iter, SEARCH_TAB_MC_METADATA, &meta, SEARCH_TAB_MC_URI, &uri, SEARCH_TAB_MC_SEARCH_RESULT, &sr, -1); if (uri == NULL) return; if (GNUNET_FS_uri_test_ksk (uri) || GNUNET_FS_uri_test_sks (uri)) { GNUNET_FS_GTK_handle_uri (uri, 1); return; } if (!((NULL == sr->download) && (NULL != uri) && ((GNUNET_FS_uri_test_chk (uri) || GNUNET_FS_uri_test_loc (uri))))) return; path = gtk_tree_model_get_path (model, &iter); recursive = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (mctx->download_recursive_checkbutton)); filename = gtk_entry_get_text (mctx->download_name_entry); downloaddir = gtk_file_chooser_get_filename (mctx->download_location_chooser); de = GNUNET_malloc (sizeof (struct DownloadEntry)); if (gtk_tree_model_iter_parent (model, &parent_iter, &iter)) { struct SearchResult *psr = NULL; gtk_tree_model_get (model, &parent_iter, SEARCH_TAB_MC_SEARCH_RESULT, &psr, -1); if (psr) de->pde = psr->download; } /* else pde remains zero */ de->uri = GNUNET_FS_uri_dup (uri); GNUNET_asprintf (&de->filename, "%s%s%s", downloaddir, DIR_SEPARATOR_STR, filename); de->sr = sr; sr->download = de; if (GNUNET_GTK_get_selected_anonymity_combo_level (mctx->download_anonymity_combo, &anonymity)) de->anonymity = anonymity; else de->anonymity = 1; de->is_recursive = recursive; de->is_directory = GNUNET_FS_meta_data_test_for_directory (meta); if (save_as == GNUNET_NO) GNUNET_FS_GTK_download_context_start_download (de); else if (download_directly == GNUNET_YES) GNUNET_FS_GTK_open_download_as_dialog (de); else GNUNET_FS_GTK_open_change_download_name_dialog (de); gtk_tree_path_free (path); g_free (downloaddir); if (!save_as) { if (GNUNET_GTK_tree_model_get_next_flat_iter (model, &iter, !recursive, &next_item)) gtk_tree_selection_select_iter (sel, &next_item); GNUNET_FS_GTK_search_treeview_cursor_changed (tv, st); } } /** * "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) { start_download2 (save_as, GNUNET_YES); } /** * 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) { start_download2 (GNUNET_NO, GNUNET_NO); } /** * User clicked on "Download!" button at the download options panel. * * @param button the "Download!" button * @param user_data the main window context */ void GNUNET_GTK_search_frame_download_download_button_clicked_cb ( GtkButton *button, gpointer user_data) { start_download2 (GNUNET_NO, GNUNET_NO); } /** * User clicked on "..." button at the download options panel, next * to the Download As entry. * * @param button the "..." button * @param user_data the main window context */ void GNUNET_GTK_search_frame_download_filename_change_button_clicked_cb ( GtkButton *button, gpointer user_data) { start_download2 (GNUNET_YES, GNUNET_NO); } /** * "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 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; GtkTreeView *tv; GNUNET_assert (de->dc != NULL); GNUNET_FS_download_stop (de->dc, GNUNET_YES); tv = GTK_TREE_VIEW (gtk_builder_get_object (spc->tab->builder, "_search_result_frame")); GNUNET_FS_GTK_search_treeview_cursor_changed (tv, spc->tab); } /** * 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, SEARCH_TAB_MC_URI, &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; struct GNUNET_CONTAINER_MetaData *meta; gboolean is_directory = FALSE; 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, SEARCH_TAB_MC_URI, &uri, SEARCH_TAB_MC_METADATA, &meta, SEARCH_TAB_MC_SEARCH_RESULT, &sr, -1); if (meta != NULL) is_directory = GNUNET_FS_meta_data_test_for_directory (meta); 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); if (is_directory) { 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 (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 to propagate the event further, * TRUE to stop the propagation */ 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); search_list_popup (tm, tab, event_button->button, event_button->time, &iter); return FALSE; } /** * 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) { struct GNUNET_GTK_MainWindowContext *mctx = GNUNET_FS_GTK_get_main_context (); 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 (); index = -1; for (i = gtk_notebook_get_n_pages (mctx->notebook) - 1; i >= 0; i--) if (tab->frame == gtk_notebook_get_nth_page (mctx->notebook, i)) index = i; gtk_notebook_remove_page (mctx->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; if (NULL != tab->atv) GNUNET_GTK_animation_tree_view_unregister (tab->atv); GNUNET_free (tab); if ( (NULL == search_tab_head) && (NULL == uri_tab) ) { GNUNET_GTK_animation_context_destroy (animation_downloading); animation_downloading = NULL; GNUNET_GTK_animation_context_destroy (animation_downloaded); animation_downloaded = NULL; GNUNET_GTK_animation_context_destroy (animation_download_stalled); animation_download_stalled = NULL; GNUNET_GTK_animation_context_destroy (animation_searching_sources); animation_searching_sources = NULL; GNUNET_GTK_animation_context_destroy (animation_found_sources); animation_found_sources = NULL; } } /** * Close the 'uri_tab'. */ void GNUNET_FS_GTK_close_uri_tab_ () { if (NULL != uri_tab) close_search_tab (uri_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; gint unknown_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, SEARCH_TAB_MC_METADATA, &meta, SEARCH_TAB_MC_URI, &uri, SEARCH_TAB_MC_FILESIZE, &filesize, SEARCH_TAB_MC_PREVIEW, &preview, SEARCH_TAB_MC_PERCENT_PROGRESS, &percent_progress, SEARCH_TAB_MC_PERCENT_AVAILABILITY, &percent_availability, SEARCH_TAB_MC_UNKNOWN_AVAILABILITY, &unknown_availability, SEARCH_TAB_MC_FILENAME, &filename, SEARCH_TAB_MC_URI_AS_STRING, &uri_as_string, SEARCH_TAB_MC_STATUS_COLOUR, &status_colour, SEARCH_TAB_MC_SEARCH_RESULT, &search_result_old, SEARCH_TAB_MC_MIMETYPE, &mimetype, SEARCH_TAB_MC_APPLICABILITY_RANK, &applicability_rank, SEARCH_TAB_MC_AVAILABILITY_CERTAINTY, &availability_certainty, SEARCH_TAB_MC_AVAILABILITY_RANK, &availability_rank, SEARCH_TAB_MC_COMPLETED, &completed, SEARCH_TAB_MC_DOWNLOADED_FILENAME, &downloaded_filename, SEARCH_TAB_MC_DOWNLOADED_ANONYMITY, &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, SEARCH_TAB_MC_METADATA, GNUNET_CONTAINER_meta_data_duplicate (meta), SEARCH_TAB_MC_URI, GNUNET_FS_uri_dup (uri), SEARCH_TAB_MC_SEARCH_RESULT, filesize, SEARCH_TAB_MC_PREVIEW, preview, SEARCH_TAB_MC_PERCENT_PROGRESS, percent_progress, SEARCH_TAB_MC_PERCENT_AVAILABILITY, percent_availability, SEARCH_TAB_MC_UNKNOWN_AVAILABILITY, unknown_availability, SEARCH_TAB_MC_FILENAME, filename, SEARCH_TAB_MC_URI_AS_STRING, uri_as_string, SEARCH_TAB_MC_STATUS_COLOUR, status_colour, SEARCH_TAB_MC_SEARCH_RESULT, search_result_new, SEARCH_TAB_MC_MIMETYPE, mimetype, SEARCH_TAB_MC_APPLICABILITY_RANK, applicability_rank, SEARCH_TAB_MC_AVAILABILITY_CERTAINTY, availability_certainty, SEARCH_TAB_MC_AVAILABILITY_RANK, availability_rank, SEARCH_TAB_MC_COMPLETED, completed, SEARCH_TAB_MC_DOWNLOADED_FILENAME, downloaded_filename, SEARCH_TAB_MC_DOWNLOADED_ANONYMITY, 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; gint unknown_availability; gchar *filename; gchar *status_colour; guint applicability_rank; guint availability_certainty; gint availability_rank; gchar *downloaded_filename; gint downloaded_anonymity; GdkPixbuf *status_animation; /* 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, SEARCH_TAB_MC_METADATA, &meta, SEARCH_TAB_MC_URI, &uri, SEARCH_TAB_MC_PERCENT_PROGRESS, &percent_progress, SEARCH_TAB_MC_PERCENT_AVAILABILITY, &percent_availability, SEARCH_TAB_MC_UNKNOWN_AVAILABILITY, &unknown_availability, SEARCH_TAB_MC_FILENAME, &filename, SEARCH_TAB_MC_STATUS_COLOUR, &status_colour, SEARCH_TAB_MC_APPLICABILITY_RANK, &applicability_rank, SEARCH_TAB_MC_AVAILABILITY_CERTAINTY, &availability_certainty, SEARCH_TAB_MC_AVAILABILITY_RANK, &availability_rank, SEARCH_TAB_MC_COMPLETED, &completed, SEARCH_TAB_MC_DOWNLOADED_FILENAME, &downloaded_filename, SEARCH_TAB_MC_DOWNLOADED_ANONYMITY, &downloaded_anonymity, SEARCH_TAB_MC_STATUS_ICON, &status_animation, -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); g_free (filename); g_free (downloaded_filename); g_free (status_colour); return; } gtk_tree_path_free (path); gtk_tree_store_set (de->sr->tab->ts, &iter, SEARCH_TAB_MC_PERCENT_PROGRESS, percent_progress, SEARCH_TAB_MC_PERCENT_AVAILABILITY, percent_availability, SEARCH_TAB_MC_UNKNOWN_AVAILABILITY, unknown_availability, SEARCH_TAB_MC_FILENAME, filename, SEARCH_TAB_MC_STATUS_COLOUR, status_colour, SEARCH_TAB_MC_APPLICABILITY_RANK, applicability_rank, SEARCH_TAB_MC_AVAILABILITY_CERTAINTY, availability_certainty, SEARCH_TAB_MC_AVAILABILITY_RANK, availability_rank, SEARCH_TAB_MC_COMPLETED, completed, SEARCH_TAB_MC_DOWNLOADED_FILENAME, downloaded_filename, SEARCH_TAB_MC_DOWNLOADED_ANONYMITY, downloaded_anonymity, SEARCH_TAB_MC_STATUS_ICON, status_animation, -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, SEARCH_TAB_MC_METADATA, &meta, SEARCH_TAB_MC_URI, &uri, SEARCH_TAB_MC_SEARCH_RESULT, &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, SEARCH_TAB_MC_METADATA, NULL, SEARCH_TAB_MC_URI, NULL, SEARCH_TAB_MC_SEARCH_RESULT, 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, SEARCH_TAB_MC_METADATA, &meta, SEARCH_TAB_MC_URI, &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; GtkTreeSelection *sel; GtkTreeModel *model; GtkTreeIter iter; struct GNUNET_CONTAINER_MetaData *meta; GdkPixbuf *pixbuf; GtkTreePath *selpath; struct SearchResult *sr; struct GNUNET_FS_Uri *uri; struct GNUNET_GTK_MainWindowContext *mctx = GNUNET_FS_GTK_get_main_context (); GNUNET_assert (tab->query_txt != NULL); gtk_list_store_clear (mctx->md_liststore); sel = gtk_tree_view_get_selection (tv); if (! gtk_tree_selection_get_selected (sel, &model, &iter)) { /* nothing selected, clear preview */ gtk_image_clear (mctx->preview_image); gtk_widget_hide (GTK_WIDGET (mctx->download_panel)); if (current_selected_search_result != NULL) gtk_tree_path_free (current_selected_search_result); current_selected_search_result = NULL; return; } meta = NULL; pixbuf = NULL; gtk_tree_model_get (model, &iter, SEARCH_TAB_MC_METADATA, &meta, SEARCH_TAB_MC_PREVIEW, &pixbuf, SEARCH_TAB_MC_URI, &uri, SEARCH_TAB_MC_SEARCH_RESULT, &sr, -1); selpath = gtk_tree_model_get_path (model, &iter); if (current_selected_search_result == NULL || gtk_tree_path_compare (selpath, current_selected_search_result) != 0) { if ((NULL == sr->download) && (NULL != uri) && ((GNUNET_FS_uri_test_chk (uri) || GNUNET_FS_uri_test_loc (uri)))) { char *download_directory; char *filename; int anonymity; int is_directory; /* Calculate suggested filename */ anonymity = -1; download_directory = NULL; filename = get_suggested_filename_anonymity2 (model, &iter, &download_directory, &anonymity); is_directory = GNUNET_FS_meta_data_test_for_directory (meta); gtk_widget_set_sensitive (GTK_WIDGET (mctx->download_recursive_checkbutton), is_directory); /* TODO: make this configurable */ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (mctx->download_recursive_checkbutton), is_directory); /* TODO: make this configurable */ GNUNET_GTK_select_anonymity_combo_level (mctx->download_anonymity_combo, anonymity >= 0 ? anonymity : 1); gtk_entry_set_text (mctx->download_name_entry, filename != NULL ? filename : NULL); gtk_file_chooser_set_current_folder (mctx->download_location_chooser, download_directory); gtk_widget_show_all (GTK_WIDGET (mctx->download_panel)); GNUNET_free_non_null (filename); GNUNET_free (download_directory); } else gtk_widget_hide (GTK_WIDGET (mctx->download_panel)); if (current_selected_search_result != NULL) gtk_tree_path_free (current_selected_search_result); current_selected_search_result = selpath; } else gtk_tree_path_free (selpath); if (NULL != pixbuf) { gtk_image_set_from_pixbuf (mctx->preview_image, pixbuf); g_object_unref (G_OBJECT (pixbuf)); } else gtk_image_clear (mctx->preview_image); if (NULL != meta) GNUNET_CONTAINER_meta_data_iterate (meta, &GNUNET_FS_GTK_add_meta_data_to_list_store, mctx->md_liststore); } /** * Page switched in main notebook, update thumbnail and * metadata views. * * @param dummy widget emitting the event, unused * @param data main window context */ void GNUNET_GTK_main_window_notebook_switch_page_cb (GtkWidget * dummy, gpointer data) { gint page; GtkWidget *w; struct SearchTab *tab; GtkTreeView *tv; struct GNUNET_GTK_MainWindowContext *mctx = GNUNET_FS_GTK_get_main_context (); page = gtk_notebook_get_current_page (mctx->notebook); w = gtk_notebook_get_nth_page (mctx->notebook, page); current_search_tab = NULL; for (tab = search_tab_head; NULL != tab; tab = tab->next) { if (tab->frame != w) continue; current_search_tab = tab; tv = GTK_TREE_VIEW (gtk_builder_get_object (tab->builder, "_search_result_frame")); GNUNET_FS_GTK_search_treeview_cursor_changed (tv, tab); return; } gtk_widget_hide (GTK_WIDGET (mctx->download_panel)); /* active tab is not a search tab (likely the 'publish' tab), clear meta data and preview widgets */ gtk_image_clear (mctx->preview_image); gtk_list_store_clear (mctx->md_liststore); if (current_selected_search_result != NULL) gtk_tree_path_free (current_selected_search_result); current_selected_search_result = NULL; } /** * 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, SEARCH_TAB_MC_SEARCH_RESULT, &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, SEARCH_TAB_MC_SEARCH_RESULT, &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 * @param probe_time how long has the search been running */ 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, struct GNUNET_TIME_Relative probe_time) { GtkTreeIter iter; struct GNUNET_CONTAINER_MetaData *ometa; GtkTreeView *tv; GtkTreePath *tp; GtkTreeStore *ts; GtkTreeModel *tm; char *desc; char *mime; GdkPixbuf *pixbuf; guint percent_avail; gint page; int desc_is_a_dup; struct GNUNET_GTK_MainWindowContext *mctx = GNUNET_FS_GTK_get_main_context (); if (sr == NULL) { GNUNET_break (0); return; } tp = gtk_tree_row_reference_get_path (sr->rr); tm = gtk_tree_row_reference_get_model (sr->rr); ts = GTK_TREE_STORE (tm); if (! gtk_tree_model_get_iter (tm, &iter, tp)) { GNUNET_break (0); return; } 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); gtk_tree_model_get (tm, &iter, SEARCH_TAB_MC_METADATA, &ometa, -1); if (NULL != ometa) GNUNET_CONTAINER_meta_data_destroy (ometa); if (availability_certainty > 0) percent_avail = 50 + (gint) (availability_rank * 50.0 / availability_certainty); else percent_avail = 50; gtk_tree_store_set (ts, &iter, SEARCH_TAB_MC_METADATA, GNUNET_CONTAINER_meta_data_duplicate (meta), SEARCH_TAB_MC_PREVIEW, pixbuf, SEARCH_TAB_MC_PERCENT_AVAILABILITY, (guint) percent_avail, SEARCH_TAB_MC_UNKNOWN_AVAILABILITY, (0 == availability_certainty) ? (gint) (probe_time.rel_value / GNUNET_FS_PROBE_UPDATE_FREQUENCY.rel_value) : -1, SEARCH_TAB_MC_FILENAME, desc, SEARCH_TAB_MC_MIMETYPE, mime, SEARCH_TAB_MC_APPLICABILITY_RANK, (guint) applicability_rank, SEARCH_TAB_MC_AVAILABILITY_CERTAINTY, (guint) availability_certainty, SEARCH_TAB_MC_AVAILABILITY_RANK, (gint) availability_rank, -1); if (pixbuf != NULL) g_object_unref (pixbuf); GNUNET_free (desc); GNUNET_free_non_null (mime); page = gtk_notebook_get_current_page (mctx->notebook); if (gtk_notebook_get_nth_page (mctx->notebook, page) == sr->tab->frame) { GtkTreeSelection *sel; GtkTreeModel *model; GtkTreeIter iter; tv = GTK_TREE_VIEW (gtk_builder_get_object (sr->tab->builder, "_search_result_frame")); sel = gtk_tree_view_get_selection (tv); if (gtk_tree_selection_get_selected (sel, &model, &iter)) { GtkTreePath *selpath = gtk_tree_model_get_path (model, &iter); if (gtk_tree_path_compare (selpath, tp) == 0) GNUNET_FS_GTK_search_treeview_cursor_changed (tv, sr->tab); gtk_tree_path_free (selpath); } } gtk_tree_path_free (tp); } /** * 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, SEARCH_TAB_MC_METADATA, GNUNET_CONTAINER_meta_data_duplicate (meta), SEARCH_TAB_MC_URI, (uri == NULL) ? NULL : GNUNET_FS_uri_dup (uri), SEARCH_TAB_MC_FILESIZE, fsize, SEARCH_TAB_MC_PREVIEW, pixbuf, SEARCH_TAB_MC_PERCENT_PROGRESS, 0, SEARCH_TAB_MC_PERCENT_AVAILABILITY, (fsize == 0) ? 100 : 50, SEARCH_TAB_MC_UNKNOWN_AVAILABILITY, (fsize == 0) ? -1 : 0, SEARCH_TAB_MC_FILENAME, desc, SEARCH_TAB_MC_URI_AS_STRING, uris, SEARCH_TAB_MC_STATUS_COLOUR, status_colour, SEARCH_TAB_MC_SEARCH_RESULT, sr, SEARCH_TAB_MC_MIMETYPE, mime, SEARCH_TAB_MC_APPLICABILITY_RANK, applicability_rank, SEARCH_TAB_MC_AVAILABILITY_CERTAINTY, 0, SEARCH_TAB_MC_AVAILABILITY_RANK, 0, SEARCH_TAB_MC_COMPLETED, (guint64) 0, SEARCH_TAB_MC_DOWNLOADED_FILENAME, NULL, SEARCH_TAB_MC_DOWNLOADED_ANONYMITY, (guint) -1, -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; } /** * Sets downloaded name on an item referenced by @rr * in a tree store @ts to @filename. * Used by SaveAs dialog to communicate back new filename * (unless SaveAs dialog initiates the download by itself). * Arguments can be taken from DownloadEntry. * * @param ts treestore * @param rr row reference * @param filename new filename */ void GNUNET_FS_GTK_set_item_downloaded_name (GtkTreeStore *ts, GtkTreeRowReference *rr, gchar *filename) { struct GNUNET_GTK_MainWindowContext *mctx = GNUNET_FS_GTK_get_main_context (); GtkTreeIter iter; GtkTreePath *path; path = gtk_tree_row_reference_get_path (rr); if (path) { if (gtk_tree_model_get_iter (GTK_TREE_MODEL (ts), &iter, path)) { /* TODO: maybe create a new store slot for user-defined filenames? * Also - maybe separate slots for downloaddir and relative filename? */ /* This code relies on download panel contents being re-populated every 0.2 seconds, * thus it updates the treestore item property, from which suggested filename * is derived. */ /* char *download_directory; char *suggested_filename; int anonymity = -1; gtk_tree_store_set (ts, &iter, SEARCH_TAB_MC_DOWNLOADED_FILENAME, filename, -1); download_directory = NULL; suggested_filename = get_suggested_filename_anonymity2 (GTK_TREE_MODEL (ts), &iter, &download_directory, &anonymity); gtk_entry_set_text (mctx->download_name_entry, suggested_filename != NULL ? suggested_filename : NULL); gtk_file_chooser_set_current_folder (mctx->download_location_chooser, download_directory); GNUNET_free_non_null (suggested_filename); GNUNET_free (download_directory); */ /* This code relies on download panel contents NOT being re-populated every 0.2 seconds, * thus it only updates download panel contents - these changes will be lost after * selecting a different item and then coming back to this one. */ gchar *current = g_strdup (filename); gchar *dirname = NULL; /* We take the filename user gave us, then check its parent directories until * we find one that actually exists (SaveAs dialog might have some options about * only picking existing names, but better be safe. * gtk_file_chooser_set_current_folder() does NOT work with non-existing dirnames! */ do { dirname = g_path_get_dirname (current); g_free (current); if (g_file_test (dirname, G_FILE_TEST_EXISTS)) { gchar *relname = &filename[strlen (dirname)]; while (relname[0] == '/' || relname[0] == '\\') relname++; gtk_entry_set_text (mctx->download_name_entry, relname); gtk_file_chooser_set_current_folder (mctx->download_location_chooser, dirname); break; } current = dirname; } while (dirname[0] != '.'); /* FIXME: Check that this condition is correct */ g_free (dirname); } gtk_tree_path_free (path); } } /** * 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; GtkWindow *sf; GtkTreeViewColumn *anim_col; GtkTreeView *tv; gint pages; struct GNUNET_GTK_MainWindowContext *mctx = GNUNET_FS_GTK_get_main_context (); if (NULL == animation_downloading) { animation_downloading = load_animation ("downloading"); animation_downloaded = load_animation ("downloaded"); animation_download_stalled = load_animation ("downloading_not_receiving"); animation_searching_sources = load_animation ("searching_sources"); animation_found_sources = load_animation ("found_source"); } 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")); tv = GTK_TREE_VIEW (gtk_builder_get_object (tab->builder, "_search_result_frame")); anim_col = GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (tab->builder, "search_result_status_pixbuf_column")); tab->atv = GNUNET_GTK_animation_tree_view_register (tv, anim_col); /* 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 */ pages = gtk_notebook_get_n_pages (mctx->notebook); gtk_notebook_insert_page (mctx->notebook, tab->frame, tab->tab_label, pages - 1); gtk_notebook_set_current_page (mctx->notebook, pages - 1); gtk_widget_show (GTK_WIDGET (mctx->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) { struct GNUNET_GTK_MainWindowContext *mctx = GNUNET_FS_GTK_get_main_context (); 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 */ for (page = 0; page < gtk_notebook_get_n_pages (mctx->notebook); page++) if (uri_tab->frame == gtk_notebook_get_nth_page (mctx->notebook, page)) { gtk_notebook_set_current_page (mctx->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, SEARCH_TAB_MC_STATUS_COLOUR, color, -1); } /** * Change the status icon for the download. * * @param de download that had an error * @param icon status icon to display */ static void change_download_status_icon (struct DownloadEntry *de, GdkPixbuf *icon) { GtkTreeIter iter; GtkTreePath *path; 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, SEARCH_TAB_MC_STATUS_ICON, icon, -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"); change_download_status_icon (de, NULL); 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, SEARCH_TAB_MC_URI, &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, SEARCH_TAB_MC_PERCENT_PROGRESS, (guint) ((size > 0) ? (100 * completed / size) : 100), SEARCH_TAB_MC_COMPLETED, 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, SEARCH_TAB_MC_MIMETYPE, "", -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); if (NULL == animation_error) animation_error = load_animation ("error"); gtk_tree_store_set (de->sr->tab->ts, &iter, SEARCH_TAB_MC_PERCENT_PROGRESS, (guint) 0, SEARCH_TAB_MC_URI_AS_STRING, emsg, SEARCH_TAB_MC_STATUS_ICON, GNUNET_GTK_animation_context_get_pixbuf (animation_error), -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, SEARCH_TAB_MC_PERCENT_PROGRESS, (guint) 100, SEARCH_TAB_MC_PERCENT_AVAILABILITY, (guint) 100, SEARCH_TAB_MC_UNKNOWN_AVAILABILITY, -1, SEARCH_TAB_MC_STATUS_ICON, GNUNET_GTK_animation_context_get_pixbuf (animation_downloaded), -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, SEARCH_TAB_MC_URI, &uri, SEARCH_TAB_MC_SEARCH_RESULT, &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)); if (NULL == de->sr) { /* child not found, what's going on!? */ GNUNET_break (0); } else { 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, SEARCH_TAB_MC_METADATA, &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, SEARCH_TAB_MC_PERCENT_PROGRESS, (guint) ((size > 0) ? (100 * completed / size) : 100), SEARCH_TAB_MC_FILENAME, filename, SEARCH_TAB_MC_STATUS_COLOUR, "blue", SEARCH_TAB_MC_SEARCH_RESULT, de->sr, SEARCH_TAB_MC_COMPLETED, (guint64) completed, SEARCH_TAB_MC_DOWNLOADED_FILENAME, de->filename, SEARCH_TAB_MC_DOWNLOADED_ANONYMITY, de->anonymity, SEARCH_TAB_MC_STATUS_ICON, GNUNET_GTK_animation_context_get_pixbuf (animation_download_stalled), -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, PUBLISH_TAB_MC_BGCOLOUR, 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, PUBLISH_TAB_MC_PROGRESS, (guint) ((size > 0) ? (100 * completed / size) : 100), -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, PUBLISH_TAB_MC_RESULT_STRING, uris, PUBLISH_TAB_MC_PROGRESS, 100, PUBLISH_TAB_MC_STATUS_ICON, GNUNET_GTK_animation_context_get_pixbuf (animation_published), -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); if (NULL == animation_error) animation_error = load_animation ("error"); gtk_tree_store_set (pe->tab->ts, &iter, PUBLISH_TAB_MC_RESULT_STRING, emsg, PUBLISH_TAB_MC_PROGRESS, 100, PUBLISH_TAB_MC_STATUS_ICON, SEARCH_TAB_MC_STATUS_ICON, GNUNET_GTK_animation_context_get_pixbuf (animation_error), -1); change_publish_color (pe, "red"); } /** * Remove publish tab from notebook */ static void delete_publish_tab () { struct PublishTab *pt; int index; int i; struct GNUNET_GTK_MainWindowContext *mctx = GNUNET_FS_GTK_get_main_context (); if (NULL == publish_tab) return; pt = publish_tab; publish_tab = NULL; index = -1; for (i = gtk_notebook_get_n_pages (mctx->notebook) - 1; i >= 0; i--) if (pt->frame == gtk_notebook_get_nth_page (mctx->notebook, i)) index = i; gtk_notebook_remove_page (mctx->notebook, index); /* fully destroy tab */ g_object_unref (pt->builder); if (NULL != pt->atv) GNUNET_GTK_animation_tree_view_unregister (pt->atv); GNUNET_free (pt); publish_tab = NULL; GNUNET_GTK_animation_context_destroy (animation_publishing); animation_publishing = NULL; GNUNET_GTK_animation_context_destroy (animation_published); animation_published = NULL; } /** * 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; } if (! gtk_tree_model_iter_children (GTK_TREE_MODEL (pe->tab->ts), &iter, NULL)) delete_publish_tab (); 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; 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, PUBLISH_TAB_MC_ENT, &pe, -1); GNUNET_FS_publish_stop (pe->pc); } clear_metadata_display (); delete_publish_tab (); } /** * 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; char *size_fancy; GtkTreeView *tv; GtkTreeViewColumn *anim_col; struct GNUNET_GTK_MainWindowContext *mctx = GNUNET_FS_GTK_get_main_context (); 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)); tv = GTK_TREE_VIEW (gtk_builder_get_object (publish_tab->builder, "_publish_tree_view")); anim_col = GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (publish_tab->builder, "_publish_animated_icon")); if ( (NULL != tv) && (NULL != anim_col) ) publish_tab->atv = GNUNET_GTK_animation_tree_view_register (tv, anim_col); /* make visible */ gtk_notebook_insert_page (mctx->notebook, publish_tab->frame, tab_label, 0); gtk_widget_show (GTK_WIDGET (mctx->notebook)); gtk_notebook_set_current_page (mctx->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; } if (NULL == animation_publishing) { animation_publishing = load_animation ("publishing"); animation_published = load_animation ("published"); } /* 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, PUBLISH_TAB_MC_FILENAME, fn, PUBLISH_TAB_MC_FILESIZE, size_fancy, PUBLISH_TAB_MC_BGCOLOUR, "white", PUBLISH_TAB_MC_PROGRESS, (guint) 0, PUBLISH_TAB_MC_ENT, ent, PUBLISH_TAB_MC_STATUS_ICON, GNUNET_GTK_animation_context_get_pixbuf (animation_publishing), -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, PUBLISH_TAB_MC_ENT, &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 button press 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 to propagate the event further, * TRUE to stop the event propagation. */ 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 ( (GDK_BUTTON_PRESS != event->type) || (3 != event_button->button) ) 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); publish_list_popup (tm, tab, event_button->button, event_button->time, &iter); return FALSE; /* propagate further, to focus on the item (for example) */ } /* ***************** 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 (NULL == ret) return ret; if (NULL != info->value.publish.specifics.resume.message) { handle_publish_error (ret, info->value.publish.specifics.resume.message); } else if (NULL != info->value.publish.specifics.resume.chk_uri) { 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 (NULL != info->value.download.specifics.resume.message) 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"); change_download_status_icon (info->value.download.cctx, GNUNET_GTK_animation_context_get_pixbuf (animation_downloading)); return info->value.download.cctx; case GNUNET_FS_STATUS_DOWNLOAD_INACTIVE: change_download_color (info->value.download.cctx, "blue"); change_download_status_icon (info->value.download.cctx, GNUNET_GTK_animation_context_get_pixbuf (animation_download_stalled)); 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 (NULL != info->value.search.pctx) 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_rank, info->value.search.specifics.resume_result. availability_certainty, GNUNET_TIME_UNIT_ZERO); 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_rank, info->value.search.specifics.update. availability_certainty, info->value.search.specifics.update. current_probe_time); 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 */