/* This file is part of GNUnet. (C) 2010 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_event_handler.c * @brief Main event handler for file-sharing * @author Christian Grothoff */ #include "common.h" #include "download.h" #include "fs_event_handler.h" #include static struct SearchTab *search_tab_head; static struct SearchTab *search_tab_tail; struct PublishTab { /** * This is a doubly-linked list. */ struct PublishTab *next; /** * This is a doubly-linked list. */ struct PublishTab *prev; GtkWidget *frame; GtkBuilder *builder; /** * Associated (top-level) FS publish operation. */ struct GNUNET_FS_PublishContext *pc; GtkTreeStore *ts; }; struct PublishEntry { /** * Associated FS publish operation. */ struct GNUNET_FS_PublishContext *pc; /** * Tab storing this entry. */ struct PublishTab *tab; /** * Where in the tab is this entry? */ GtkTreeRowReference *rr; int is_top; }; struct SearchResult { /** * Where in the tab is this result? */ GtkTreeRowReference *rr; /** * Tab storing this result. */ struct SearchTab *tab; /** * Search result for top-level results and * namespace-update results. */ struct GNUNET_FS_SearchResult *result; /** * Associated download, or NULL for none. */ struct DownloadEntry *download; }; static struct PublishTab *publish_tab_head; static struct PublishTab *publish_tab_tail; static struct DownloadEntry * change_download_colour (struct DownloadEntry *de, const char *colour) { GtkTreeIter iter; GtkTreePath *path; path = gtk_tree_row_reference_get_path (de->rr); if (TRUE != gtk_tree_model_get_iter (GTK_TREE_MODEL (de->ts), &iter, path)) { GNUNET_break (0); gtk_tree_path_free (path); return de; } gtk_tree_path_free (path); gtk_tree_store_set (de->ts, &iter, 8, colour, -1); return de; } static struct PublishEntry * change_publish_colour (struct PublishEntry *pe, const char *colour) { GtkTreeIter iter; GtkTreePath *path; if (pe == NULL) { GNUNET_break (0); return NULL; } path = gtk_tree_row_reference_get_path (pe->rr); if (TRUE != gtk_tree_model_get_iter (GTK_TREE_MODEL (pe->tab->ts), &iter, path)) { GNUNET_break (0); gtk_tree_path_free (path); return pe; } gtk_tree_path_free (path); gtk_tree_store_set (pe->tab->ts, &iter, 2, colour, -1); return pe; } static void stop_download (struct DownloadEntry *de, int is_suspend) { change_download_colour (de, "white"); gtk_tree_row_reference_free (de->rr); if (is_suspend == GNUNET_NO) GNUNET_FS_download_stop (de->dc, GNUNET_YES); GNUNET_FS_uri_destroy (de->uri); GNUNET_CONTAINER_meta_data_destroy (de->meta); GNUNET_free (de); } struct AddDirectoryEntryContext { struct DownloadEntry *de; /** * Row reference of parent (the directory). */ GtkTreeRowReference *prr; int check_duplicates; }; /** * Function used to process entries in a directory. * * @param cls closure, our 'struct AddDirectoryEntryContext*' * @param filename name of the file in the directory * @param uri URI of the file * @param metadata metadata for the file; metadata for * the directory if everything else is NULL/zero * @param length length of the available data for the file * (of type size_t since data must certainly fit * into memory; if files are larger than size_t * permits, then they will certainly not be * embedded with the directory itself). * @param data data available for the file (length bytes) */ static void add_directory_entry (void *cls, const char *filename, const struct GNUNET_FS_Uri *uri, const struct GNUNET_CONTAINER_MetaData *meta, size_t length, const void *data) { struct AddDirectoryEntryContext *ade = cls; GtkTreeIter iter; GtkTreeIter piter; GtkTreePath *path; GtkTreeModel *tm; struct GNUNET_FS_Uri *xuri; if (uri == NULL) { /* directory meta data itself */ /* FIXME: consider merging it in... */ return; } if (ade->check_duplicates == GNUNET_YES) { path = gtk_tree_row_reference_get_path (ade->prr); tm = gtk_tree_row_reference_get_model (ade->prr); if (TRUE != gtk_tree_model_get_iter (tm, &piter, path)) { GNUNET_break (0); gtk_tree_path_free (path); return; } gtk_tree_path_free (path); if (TRUE == gtk_tree_model_iter_children (tm, &iter, &piter)) { do { gtk_tree_model_get (tm, &iter, 1, &xuri, -1); if (GNUNET_YES == GNUNET_FS_uri_test_equal (xuri, uri)) return; /* already present */ } while (TRUE == gtk_tree_model_iter_next (tm, &iter)); } } GNUNET_GTK_add_search_result (ade->de->tab, &iter, ade->prr, uri, meta, NULL, 0); } static struct DownloadEntry * mark_download_progress (struct DownloadEntry *de, uint64_t size, uint64_t completed, const void *block_data, uint64_t offset, uint64_t block_size, unsigned int depth) { struct AddDirectoryEntryContext ade; GtkTreeIter iter; GtkTreePath *path; path = gtk_tree_row_reference_get_path (de->rr); if (TRUE != gtk_tree_model_get_iter (GTK_TREE_MODEL (de->ts), &iter, path)) { GNUNET_break (0); gtk_tree_path_free (path); return de; } gtk_tree_path_free (path); gtk_tree_store_set (de->ts, &iter, 4, (guint) ((size > 0) ? (100 * completed / size) : 100) /* progress */, -1); if ( (depth == 0) && (block_size > 0) && (GNUNET_YES == GNUNET_FS_meta_data_test_for_directory (de->meta)) ) { ade.de = de; ade.prr = de->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)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("Metadata wrongly claims that this is a GNUnet directory!\n")); } } return de; } static struct DownloadEntry * mark_download_completed (struct DownloadEntry *de, uint64_t size, const char *filename) { struct AddDirectoryEntryContext ade; (void) mark_download_progress (de, size, size, NULL, 0, 0, 0); if ( (GNUNET_YES == GNUNET_FS_meta_data_test_for_directory (de->meta)) && (filename != NULL) ) { ade.de = de; ade.prr = de->rr; ade.check_duplicates = GNUNET_NO; GNUNET_GTK_mmap_and_scan (filename, &add_directory_entry, &ade); } (void) change_download_colour (de, "green"); return de; } static struct PublishEntry * mark_publish_progress (struct PublishEntry *pe, uint64_t size, uint64_t completed) { GtkTreeIter iter; GtkTreePath *path; path = gtk_tree_row_reference_get_path (pe->rr); if (TRUE != gtk_tree_model_get_iter (GTK_TREE_MODEL (pe->tab->ts), &iter, path)) { GNUNET_break (0); gtk_tree_path_free (path); return pe; } gtk_tree_path_free (path); gtk_tree_store_set (pe->tab->ts, &iter, 3, (guint) ((size > 0) ? (100 * completed / size) : 100) /* progress */, -1); return pe; } /** * Handle the case where an active download lost its * search parent by moving it to the URI tab. */ static struct DownloadEntry * download_lost_parent (struct DownloadEntry *de, uint64_t size, uint64_t completed, int is_active) { GtkTreeIter iter; GtkTreePath *path; struct SearchTab *tab; gtk_tree_row_reference_free (de->rr); de->sr = NULL; tab = GNUNET_GTK_add_to_uri_tab (&iter, NULL, de->meta, de->uri); de->ts = tab->ts; path = gtk_tree_model_get_path (GTK_TREE_MODEL (de->ts), &iter); de->rr = gtk_tree_row_reference_new (GTK_TREE_MODEL (de->ts), path); gtk_tree_path_free (path); mark_download_progress (de, size, completed, NULL, 0, 0, 0); /* FIXME: need to also move sub-downloads --- this could be a recursive download! */ if (size > completed) { if (is_active) change_download_colour (de, "yellow"); else change_download_colour (de, "blue"); } else { change_download_colour (de, "green"); } return de; } /** * Setup a new download entry. * * @param de existing download entry for the download, or NULL * @param pde parent download entry, or NULL * @param sr search result, or NULL * @param dc download context (for stopping) * @param uri the URI * @param meta metadata * @param size total size * @param completed current progress */ static struct DownloadEntry * setup_download (struct DownloadEntry *de, struct DownloadEntry *pde, struct SearchResult *sr, struct GNUNET_FS_DownloadContext *dc, const struct GNUNET_FS_Uri *uri, const struct GNUNET_CONTAINER_MetaData *meta, uint64_t size, uint64_t completed) { GtkTreeIter iter; GtkTreePath *path; if (de == NULL) { de = GNUNET_malloc (sizeof (struct DownloadEntry)); GNUNET_assert (sr->download == NULL); sr->download = de; de->sr = sr; de->dc = dc; de->uri = GNUNET_FS_uri_dup (uri); } de->pde = pde; if ( (meta != NULL) && (de->meta == NULL) ) de->meta = GNUNET_CONTAINER_meta_data_duplicate (meta); if (sr != NULL) { de->rr = gtk_tree_row_reference_copy (sr->rr); de->ts = sr->tab->ts; de->tab = sr->tab; } else if (de->rr == NULL) { de->tab = GNUNET_GTK_add_to_uri_tab (&iter, NULL, meta, uri); de->ts = de->tab->ts; path = gtk_tree_model_get_path (GTK_TREE_MODEL (de->ts), &iter); de->rr = gtk_tree_row_reference_new (GTK_TREE_MODEL (de->ts), path); gtk_tree_path_free (path); } path = gtk_tree_row_reference_get_path (de->rr); if (TRUE != gtk_tree_model_get_iter (GTK_TREE_MODEL (de->ts), &iter, path)) { GNUNET_break (0); gtk_tree_path_free (path); return de; } gtk_tree_path_free (path); gtk_tree_store_set (de->ts, &iter, 4, (guint) ((size > 0) ? (100 * completed / size) : 100) /* progress */, 8, "blue" /* status colour: pending */, -1); return de; } /** * Tell FS to start a download. Begins by opening the * "save as" window. */ static void start_download (GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data) { struct SearchTab *tab = user_data; GtkTreeModel *tm; GtkTreeIter iter; struct GNUNET_FS_Uri *uri; struct GNUNET_CONTAINER_MetaData *meta; struct SearchResult *sr; char *mime; struct DownloadContext *dlc; GNUNET_assert (tab != NULL); tm = gtk_tree_view_get_model (tree_view); if (TRUE != gtk_tree_model_get_iter (tm, &iter, path)) { GNUNET_break (0); return; } gtk_tree_model_get (tm, &iter, 0, &meta, 1, &uri, 9, &sr, 10, &mime, -1); dlc = GNUNET_malloc (sizeof (struct DownloadContext)); dlc->uri = GNUNET_FS_uri_dup (uri); dlc->mime = mime; dlc->filename = GNUNET_FS_meta_data_suggest_filename (meta); dlc->meta = GNUNET_CONTAINER_meta_data_duplicate (meta); dlc->rr = gtk_tree_row_reference_new (tm, path); dlc->sr = sr->result; dlc->anonymity = -1; GNUNET_GTK_open_download_as_dialog (dlc); } /** * Row reference for the current search context menu. */ static GtkTreeRowReference *current_context_row_reference; /** * Search tab used for the current search context menu. */ static struct SearchTab *current_context_search_tab; /** * Download was selected in the current search context menu. */ static void start_download_ctx_menu (gpointer user_data, guint unused, GtkWidget *widget) { GtkTreePath *path; GtkTreeView *tv; if (current_context_row_reference == NULL) { GNUNET_break (0); return; } path = gtk_tree_row_reference_get_path (current_context_row_reference); gtk_tree_row_reference_free (current_context_row_reference); current_context_row_reference = NULL; tv = GTK_TREE_VIEW (gtk_builder_get_object (current_context_search_tab->builder, "_search_result_frame")); start_download (tv, path, NULL, current_context_search_tab); gtk_tree_path_free (path); current_context_search_tab = NULL; } /** * Download was selected in the current search context menu. */ static void abort_download_ctx_menu (gpointer user_data, guint unused, GtkWidget *widget) { struct DownloadEntry *de = user_data; GNUNET_assert (de->dc != NULL); GNUNET_FS_download_stop (de->dc, GNUNET_YES); current_context_search_tab = NULL; } /** * Copy current URI to clipboard. */ static void copy_uri_to_clipboard_ctx_menu (gpointer user_data, guint unused, GtkWidget *widget) { GtkTreePath *path; GtkTreeView *tv; GtkTreeModel *tm; GtkTreeIter iter; struct GNUNET_FS_Uri *uri; char *uris; GtkClipboard *cb; if (current_context_row_reference == NULL) { GNUNET_break (0); return; } path = gtk_tree_row_reference_get_path (current_context_row_reference); gtk_tree_row_reference_free (current_context_row_reference); current_context_row_reference = NULL; tv = GTK_TREE_VIEW (gtk_builder_get_object (current_context_search_tab->builder, "_search_result_frame")); tm = gtk_tree_view_get_model (tv); if (TRUE != gtk_tree_model_get_iter (tm, &iter, path)) { GNUNET_break (0); gtk_tree_path_free (path); return; } gtk_tree_model_get (tm, &iter, 1, &uri, -1); gtk_tree_path_free (path); current_context_search_tab = NULL; if (uri == NULL) { GNUNET_break (0); return; } uris = GNUNET_FS_uri_to_string (uri); cb = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); gtk_clipboard_set_text (cb, uris, -1); gtk_clipboard_store (cb); GNUNET_free (uris); } /** * We got a right-click on the search result list. Display the context * menu. */ static int search_list_on_menu(GtkWidget *widget, GdkEvent *event, gpointer user_data) { GdkEventButton *event_button; struct SearchTab *tab = user_data; GtkTreeView *tv; GtkMenu *menu; GtkWidget *child; GtkTreePath *path; GtkTreeModel *tm; GtkTreeIter iter; struct SearchResult *sr; tv = GTK_TREE_VIEW (widget); if (event->type == GDK_BUTTON_PRESS) { event_button = (GdkEventButton *) event; if (event_button->button == 3) { current_context_search_tab = tab; if (current_context_row_reference != NULL) { gtk_tree_row_reference_free (current_context_row_reference); current_context_row_reference = NULL; } path = NULL; if (FALSE == gtk_tree_view_get_path_at_pos (tv, event_button->x, event_button->y, &path, NULL, NULL, NULL)) { /* nothing selected */ current_context_search_tab = NULL; return FALSE; } tm = gtk_tree_view_get_model (tv); gtk_tree_model_get_iter (tm, &iter, path); gtk_tree_model_get (tm, &iter, 9, &sr, -1); current_context_row_reference = gtk_tree_row_reference_new (tm, path); gtk_tree_path_free (path); /* FIXME: have additional options, depending on status: - view full meta data (in new window) - copy URI to clipboard - start recursive download - abort active download (!) => need to know download status before creating menu! */ menu = GTK_MENU (gtk_menu_new ()); if (sr->download == NULL) { child = gtk_menu_item_new_with_label (_("_Download")); g_signal_connect (child, "activate", G_CALLBACK (start_download_ctx_menu), NULL); gtk_label_set_use_underline (GTK_LABEL (gtk_bin_get_child (GTK_BIN (child))), TRUE); gtk_widget_show (child); } else { child = gtk_menu_item_new_with_label (_("_Abort download")); g_signal_connect (child, "activate", G_CALLBACK (abort_download_ctx_menu), sr->download); gtk_label_set_use_underline (GTK_LABEL (gtk_bin_get_child (GTK_BIN (child))), TRUE); gtk_widget_show (child); } child = gtk_menu_item_new_with_label (_("_Copy URI to Clipboard")); g_signal_connect (child, "activate", G_CALLBACK (copy_uri_to_clipboard_ctx_menu), NULL); gtk_label_set_use_underline (GTK_LABEL (gtk_bin_get_child (GTK_BIN (child))), TRUE); gtk_widget_show (child); gtk_menu_shell_append (GTK_MENU_SHELL (menu), child); gtk_menu_popup (menu, NULL, NULL, NULL, NULL, event_button->button, event_button->time); } } return FALSE; } /** * Selected row has changed, update preview and metadata * areas. */ static void update_meta_data_views (GtkTreeView *tv, gpointer user_data) { struct SearchTab *tab = user_data; GtkImage *image; GtkListStore *ms; GtkTreeSelection *sel; GtkTreeModel *model; GtkTreeIter iter; struct GNUNET_CONTAINER_MetaData *meta; GdkPixbuf *pixbuf; GNUNET_assert (tab->query_txt != NULL); image = GTK_IMAGE (GNUNET_GTK_get_main_window_object ("GNUNET_GTK_main_window_preview_image")); ms = GTK_LIST_STORE (GNUNET_GTK_get_main_window_object ("GNUNET_GTK_meta_data_list_store")); sel = gtk_tree_view_get_selection (tv); gtk_list_store_clear (ms); if (TRUE != gtk_tree_selection_get_selected (sel, &model, &iter)) { gtk_image_clear (image); return; } meta = NULL; pixbuf = NULL; gtk_tree_model_get (model, &iter, 0, &meta, 3, &pixbuf, -1); if (pixbuf != NULL) { gtk_image_set_from_pixbuf (image, pixbuf); /* FIXME: unref pixbuf? */ } if (meta != NULL) { GNUNET_CONTAINER_meta_data_iterate (meta, &GNUNET_GTK_add_meta_data_to_list_store, ms); } } /** * Update the label for a search */ static void update_search_label (struct SearchTab *tab) { char *name; if (tab->num_results > 0) GNUNET_asprintf (&name, "%.*s%s (%u)", 20, tab->query_txt, strlen (tab->query_txt) > 20 ? "..." : "", tab->num_results); else GNUNET_asprintf (&name, "%.*s%s", 20, tab->query_txt, strlen (tab->query_txt) > 20 ? "..." : ""); gtk_label_set_text (tab->label, name); GNUNET_free (name); } /** * Close a search tab and free associated state. */ static void close_search_tab (struct SearchTab *tab) { GtkNotebook *notebook; int index; int i; notebook = GTK_NOTEBOOK (GNUNET_GTK_get_main_window_object ("GNUNET_GTK_main_window_notebook")); index = -1; for (i = gtk_notebook_get_n_pages (notebook) - 1; i >= 0; i--) if (tab->frame == gtk_notebook_get_nth_page (notebook, i)) index = i; gtk_notebook_remove_page (notebook, index); g_object_unref (tab->builder); GNUNET_free (tab->query_txt); GNUNET_CONTAINER_DLL_remove (search_tab_head, search_tab_tail, tab); GNUNET_free (tab); } /** * Close a publish tab and free associated state. */ static void close_publish_tab (struct PublishEntry *ent) { struct PublishTab *tab; GtkNotebook *notebook; int index; int i; if (ent == NULL) { GNUNET_break (0); return; } gtk_tree_row_reference_free (ent->rr); if (GNUNET_YES != ent->is_top) { GNUNET_free (ent); return; } tab = ent->tab; GNUNET_free (ent); notebook = GTK_NOTEBOOK (GNUNET_GTK_get_main_window_object ("GNUNET_GTK_main_window_notebook")); index = -1; for (i = gtk_notebook_get_n_pages (notebook) - 1; i >= 0; i--) if (tab->frame == gtk_notebook_get_nth_page (notebook, i)) index = i; gtk_notebook_remove_page (notebook, index); g_object_unref (tab->builder); GNUNET_CONTAINER_DLL_remove (publish_tab_head, publish_tab_tail, tab); GNUNET_free (tab); } /** * Tell FS to stop a search. */ static void stop_search (GtkButton *button, gpointer user_data) { struct SearchTab *tab = user_data; if (tab->sc != NULL) { GNUNET_FS_search_stop (tab->sc); tab->sc = NULL; } } /** * Stop completed downloads (or those that failed). Should * iterate over the underlying tree store and stop all * completed entries. Furthermore, if the resulting tree * store is empty and has no search associated with it, * the tab should be closed. */ static void clear_downloads (GtkButton *button, gpointer user_data) { struct SearchTab *tab = user_data; GNUNET_assert (tab != NULL); GNUNET_break (0); /* not implemented */ } /** * Tell FS to pause a search. */ static void pause_search (GtkButton *button, gpointer user_data) { struct SearchTab *tab = user_data; if (tab->sc != NULL) { GNUNET_FS_search_pause (tab->sc); gtk_widget_show (tab->play_button); gtk_widget_hide (tab->pause_button); } } /** * Tell FS to resume a search. */ static void continue_search (GtkButton *button, gpointer user_data) { struct SearchTab *tab = user_data; if (tab->sc != NULL) { GNUNET_FS_search_continue (tab->sc); gtk_widget_show (tab->pause_button); gtk_widget_hide (tab->play_button); } } /** * Setup a new search tab. * * @param sc context with FS for the search * @param query the query * @param anonymity anonymity level */ static struct SearchTab * setup_search (struct GNUNET_FS_SearchContext *sc, const struct GNUNET_FS_Uri *query) { struct SearchTab *tab; GtkTreeView *tv; GtkNotebook *notebook; GtkWindow *sf; gint pages; tab = GNUNET_malloc (sizeof (struct SearchTab)); GNUNET_CONTAINER_DLL_insert (search_tab_head, search_tab_tail, tab); tab->sc = sc; if (query == NULL) { tab->query_txt = GNUNET_strdup ("*"); } else { 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 ("search_tab.glade"); tab->ts = GTK_TREE_STORE (gtk_builder_get_object (tab->builder, "GNUNET_GTK_file_sharing_result_tree_store")); /* load frame */ sf = GTK_WINDOW (gtk_builder_get_object (tab->builder, "_search_result_frame_window")); tab->frame = gtk_bin_get_child (GTK_BIN (sf)); gtk_widget_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)); gtk_widget_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")); g_signal_connect(G_OBJECT(tab->close_button), "clicked", G_CALLBACK(stop_search), tab); tab->clear_button = GTK_WIDGET (gtk_builder_get_object (tab->builder, "_search_result_label_clear_button")); g_signal_connect(G_OBJECT(tab->clear_button), "clicked", G_CALLBACK(clear_downloads), tab); /* FIXME: clear not implemented, hence not visible... */ gtk_widget_set_visible (tab->clear_button, FALSE); tab->play_button = GTK_WIDGET (gtk_builder_get_object (tab->builder, "_search_result_label_play_button")); g_signal_connect(G_OBJECT(tab->play_button), "clicked", G_CALLBACK(continue_search), tab); tab->pause_button = GTK_WIDGET (gtk_builder_get_object (tab->builder, "_search_result_label_pause_button")); g_signal_connect(G_OBJECT(tab->pause_button), "clicked", G_CALLBACK(pause_search), tab); /* patch text */ update_search_label (tab); /* add signal handlers */ tv = GTK_TREE_VIEW (gtk_builder_get_object (tab->builder, "_search_result_frame")); g_signal_connect(G_OBJECT(tv), "row-activated", G_CALLBACK(start_download), tab); g_signal_connect(G_OBJECT(tv), "cursor-changed", G_CALLBACK(update_meta_data_views), tab); g_signal_connect (G_OBJECT(tv), "button_press_event", G_CALLBACK(search_list_on_menu), tab); /* make visible */ notebook = GTK_NOTEBOOK (GNUNET_GTK_get_main_window_object ("GNUNET_GTK_main_window_notebook")); pages = gtk_notebook_get_n_pages (notebook); gtk_notebook_insert_page (notebook, tab->frame, tab->tab_label, pages - 1); gtk_notebook_set_current_page (notebook, pages - 1); gtk_widget_show (GTK_WIDGET (notebook)); return tab; } /** * Add a search result to the given search tab. * * @param tab search tab to extend * @param iter set to position where search result is added * @param parent_rr reference to parent entry in search tab * @param uri uri to add * @param meta metadata of the entry * @param result associated FS search result (can be NULL) * @param applicability_rank how relevant is the result * @return entry for the search result */ struct SearchResult * GNUNET_GTK_add_search_result (struct SearchTab *tab, GtkTreeIter *iter, GtkTreeRowReference *parent_rr, const struct GNUNET_FS_Uri *uri, const struct GNUNET_CONTAINER_MetaData *meta, struct GNUNET_FS_SearchResult *result, uint32_t applicability_rank) { struct SearchResult *sr; GtkTreePath *tp; char *desc; char *mime; char *uris; GdkPixbuf *pixbuf; GtkTreeIter *pitr; GtkTreeIter pmem; GtkTreePath *path; GtkTreeModel *tm; GtkTreeStore *ts; if ( (uri != NULL) && (!GNUNET_FS_uri_test_loc (uri)) && (!GNUNET_FS_uri_test_chk (uri)) ) { /* SKS support not implemented yet */ GNUNET_break (0); return NULL ; } desc = GNUNET_CONTAINER_meta_data_get_first_by_types (meta, EXTRACTOR_METATYPE_PACKAGE_NAME, EXTRACTOR_METATYPE_TITLE, EXTRACTOR_METATYPE_BOOK_TITLE, EXTRACTOR_METATYPE_FILENAME, EXTRACTOR_METATYPE_DESCRIPTION, EXTRACTOR_METATYPE_SUMMARY, EXTRACTOR_METATYPE_ALBUM, EXTRACTOR_METATYPE_COMMENT, EXTRACTOR_METATYPE_SUBJECT, EXTRACTOR_METATYPE_KEYWORDS -1); if (desc == NULL) desc = GNUNET_strdup (_("no description supplied")); if (uri == NULL) uris = GNUNET_strdup (_("no URI")); else uris = GNUNET_FS_uri_to_string (uri); mime = GNUNET_CONTAINER_meta_data_get_first_by_types (meta, EXTRACTOR_METATYPE_MIMETYPE, EXTRACTOR_METATYPE_FORMAT, -1); pixbuf = GNUNET_GTK_get_thumbnail_from_meta_data (meta); sr = GNUNET_malloc (sizeof (struct SearchResult)); sr->result = result; sr->tab = tab; if (parent_rr != NULL) { /* get piter from parent */ path = gtk_tree_row_reference_get_path (parent_rr); tm = gtk_tree_row_reference_get_model (parent_rr); if (TRUE != gtk_tree_model_get_iter (GTK_TREE_MODEL (tm), &pmem, path)) { GNUNET_break (0); gtk_tree_path_free (path); /* desperate measure: make top-level entry */ pitr = NULL; } else { pitr = &pmem; } ts = GTK_TREE_STORE (tm); } else { /* top-level result */ pitr = NULL; ts = tab->ts; } gtk_tree_store_insert_with_values (ts, iter, pitr, G_MAXINT, 0, GNUNET_CONTAINER_meta_data_duplicate (meta), 1, (uri == NULL) ? NULL : GNUNET_FS_uri_dup (uri), 2, (uri == NULL) ? 0 : GNUNET_FS_uri_chk_get_file_size (uri), 3, pixbuf /* preview */, 4, 0 /* percent progress */, 5, 0 /* percent availability */, 6, desc /* filename/description */, 7, uris, 8, "white" /* status colour */, 9, sr, 10, mime, 11, applicability_rank, 12, 0 /* avail-cert */, 13, 0 /* avail-rank */, -1); if (tab != NULL) tab->num_results++; if (pixbuf != NULL) g_object_unref (pixbuf); GNUNET_free (uris); GNUNET_free (desc); GNUNET_free_non_null (mime); tp = gtk_tree_model_get_path (GTK_TREE_MODEL (ts), iter); sr->rr = gtk_tree_row_reference_new (GTK_TREE_MODEL (ts), tp); gtk_tree_path_free (tp); return sr; } static struct SearchResult * process_search_result (void *cls, struct SearchResult *parent, const struct GNUNET_FS_Uri *uri, const struct GNUNET_CONTAINER_MetaData *meta, struct GNUNET_FS_SearchResult *result, uint32_t applicability_rank) { struct SearchTab *tab = cls; struct SearchResult *sr; GtkTreeIter iter; sr = GNUNET_GTK_add_search_result (tab, &iter, (parent != NULL) ? parent->rr : NULL, uri, meta, result, applicability_rank); update_search_label (tab); return sr; } /** * Setup a new top-level entry in the URI tab. If necessary, create * the URI tab first. * * @param iter set to the new entry * @param srp set to search result * @param meta metadata for the new entry * @param uri URI for the new entry * @return NULL on error, otherwise tree store matching iter */ struct SearchTab * GNUNET_GTK_add_to_uri_tab (GtkTreeIter *iter, struct SearchResult **srp, const struct GNUNET_CONTAINER_MetaData *meta, const struct GNUNET_FS_Uri *uri) { struct SearchTab *utab; struct SearchResult *sr; utab = search_tab_head; while (utab != NULL) { if (utab->sc == NULL) break; utab = utab->next; } if (utab == NULL) { utab = setup_search (NULL, NULL); gtk_widget_set_visible (utab->close_button, FALSE); gtk_widget_set_visible (utab->pause_button, FALSE); } else { /* FIXME: make 'utab' the current page */ GNUNET_break (0); } sr = GNUNET_GTK_add_search_result (utab, iter, NULL, uri, meta, NULL, 0); if (NULL != srp) *srp = sr; return utab; } static struct SearchResult * update_search_result (struct SearchResult *sr, const struct GNUNET_CONTAINER_MetaData *meta, int32_t availability_rank, uint32_t availability_certainty, uint32_t applicability_rank) { GtkTreeIter iter; struct GNUNET_CONTAINER_MetaData *ometa; GtkTreeView *tv; GtkTreePath *tp; GtkTreeStore *ts; GtkTreeModel *tm; char *desc; char *mime; GdkPixbuf *pixbuf; guint percent_avail; GtkNotebook *notebook; gint page; if (sr == NULL) return NULL; desc = GNUNET_CONTAINER_meta_data_get_first_by_types (meta, EXTRACTOR_METATYPE_PACKAGE_NAME, EXTRACTOR_METATYPE_TITLE, EXTRACTOR_METATYPE_BOOK_TITLE, EXTRACTOR_METATYPE_FILENAME, EXTRACTOR_METATYPE_DESCRIPTION, EXTRACTOR_METATYPE_SUMMARY, EXTRACTOR_METATYPE_ALBUM, EXTRACTOR_METATYPE_COMMENT, EXTRACTOR_METATYPE_SUBJECT, EXTRACTOR_METATYPE_KEYWORDS -1); if (desc == NULL) desc = GNUNET_strdup (_("no description supplied")); mime = GNUNET_CONTAINER_meta_data_get_first_by_types (meta, EXTRACTOR_METATYPE_MIMETYPE, EXTRACTOR_METATYPE_FORMAT, -1); pixbuf = GNUNET_GTK_get_thumbnail_from_meta_data (meta); tp = gtk_tree_row_reference_get_path (sr->rr); tm = gtk_tree_row_reference_get_model (sr->rr); ts = GTK_TREE_STORE (tm); gtk_tree_model_get_iter (tm, &iter, tp); gtk_tree_path_free (tp); gtk_tree_model_get (tm, &iter, 0, &ometa, -1); if (meta != NULL) GNUNET_CONTAINER_meta_data_destroy (ometa); if (availability_certainty > 0) percent_avail = (availability_certainty + availability_rank) * 50 / availability_certainty; else percent_avail = 0; gtk_tree_store_set (ts, &iter, 0, GNUNET_CONTAINER_meta_data_duplicate (meta), 3, pixbuf /* preview */, 5, (guint) percent_avail /* percent availability */, 6, desc /* filename/description */, 10, mime, 11, (guint) applicability_rank, 12, (guint) availability_certainty, 13, (gint) availability_rank, -1); if (pixbuf != NULL) g_object_unref (pixbuf); GNUNET_free (desc); GNUNET_free_non_null (mime); notebook = GTK_NOTEBOOK (GNUNET_GTK_get_main_window_object ("GNUNET_GTK_main_window_notebook")); page = gtk_notebook_get_current_page (notebook); if (gtk_notebook_get_nth_page (notebook, page) == sr->tab->frame) { tv = GTK_TREE_VIEW (gtk_builder_get_object (sr->tab->builder, "_search_result_frame")); update_meta_data_views (tv, sr->tab); } return sr; } static void free_search_result (struct SearchResult *sr) { GtkTreePath *tp; GtkTreeModel *tm; GtkTreeIter iter; struct GNUNET_FS_Uri *uri; struct GNUNET_CONTAINER_MetaData *meta; tp = gtk_tree_row_reference_get_path (sr->rr); tm = gtk_tree_row_reference_get_model (sr->rr); gtk_tree_model_get_iter (tm, &iter, tp); gtk_tree_path_free (tp); gtk_tree_model_get (tm, &iter, 0, &meta, 1, &uri, -1); if (uri != NULL) GNUNET_FS_uri_destroy (uri); if (meta != NULL) GNUNET_CONTAINER_meta_data_destroy (meta); gtk_tree_row_reference_free (sr->rr); gtk_tree_store_remove (GTK_TREE_STORE (tm), &iter); GNUNET_free (sr); } /** * Tell FS to stop publishing. */ static void stop_publishing (GtkButton *button, gpointer user_data) { struct PublishTab *tab = user_data; if (tab->pc != NULL) { GNUNET_FS_publish_stop (tab->pc); tab->pc = NULL; } } static struct PublishEntry * setup_publish (struct GNUNET_FS_PublishContext *pc, const char *fn, uint64_t fsize, struct PublishEntry *parent) { struct PublishTab *tab; struct PublishEntry *ent; GtkTreeIter *pitrptr; GtkTreeIter iter; GtkTreeIter piter; GtkTreePath *path; GtkWindow *df; GtkWidget *tab_label; GtkLabel *fn_label; GtkWidget *close_button; GtkNotebook *notebook; gint pages; char *size_fancy; if (NULL == parent) { /* create new tab */ tab = GNUNET_malloc (sizeof (struct PublishTab)); tab->pc = pc; GNUNET_CONTAINER_DLL_insert (publish_tab_head, publish_tab_tail, tab); tab->builder = GNUNET_GTK_get_new_builder ("publish_tab.glade"); df = GTK_WINDOW (gtk_builder_get_object (tab->builder, "_publish_frame_window")); tab->frame = gtk_bin_get_child (GTK_BIN (df)); gtk_widget_ref (tab->frame); gtk_container_remove (GTK_CONTAINER (df), tab->frame); gtk_widget_destroy (GTK_WIDGET (df)); /* load tab_label */ df = GTK_WINDOW (gtk_builder_get_object (tab->builder, "_publish_label_window")); tab_label = gtk_bin_get_child (GTK_BIN (df)); gtk_widget_ref (tab_label); gtk_container_remove (GTK_CONTAINER (df), tab_label); gtk_widget_destroy (GTK_WIDGET (df)); /* get refs to widgets */ fn_label = GTK_LABEL (gtk_builder_get_object (tab->builder, "_publish_label_window_label")); gtk_label_set_text (fn_label, fn); close_button = GTK_WIDGET (gtk_builder_get_object (tab->builder, "_publish_label_close_button")); g_signal_connect(G_OBJECT(close_button), "clicked", G_CALLBACK(stop_publishing), tab); /* make visible */ notebook = GTK_NOTEBOOK (GNUNET_GTK_get_main_window_object ("GNUNET_GTK_main_window_notebook")); pages = gtk_notebook_get_n_pages (notebook); gtk_notebook_insert_page (notebook, tab->frame, tab_label, pages - 1); gtk_widget_show (GTK_WIDGET (notebook)); tab->ts = GTK_TREE_STORE (gtk_builder_get_object (tab->builder, "_publish_frame_tree_store")); pitrptr = NULL; } else { /* create new iter from parent */ tab = parent->tab; path = gtk_tree_row_reference_get_path (parent->rr); if (TRUE != gtk_tree_model_get_iter (GTK_TREE_MODEL (tab->ts), &piter, path)) { GNUNET_break (0); return NULL; } pitrptr = &piter; } size_fancy = GNUNET_STRINGS_byte_size_fancy (fsize); gtk_tree_store_insert_with_values (tab->ts, &iter, pitrptr, G_MAXINT, 0, fn, 1, size_fancy, 2, "white", 3, (guint) 0 /* progress */, -1); GNUNET_free (size_fancy); ent = GNUNET_malloc (sizeof (struct PublishEntry)); ent->is_top = (parent == NULL) ? GNUNET_YES : GNUNET_NO; ent->tab = tab; path = gtk_tree_model_get_path (GTK_TREE_MODEL (tab->ts), &iter); ent->rr = gtk_tree_row_reference_new (GTK_TREE_MODEL (tab->ts), path); gtk_tree_path_free (path); ent->pc = pc; return ent; } /** * Notification of FS to a client about the progress of an * operation. Callbacks of this type will be used for uploads, * downloads and searches. Some of the arguments depend a bit * in their meaning on the context in which the callback is used. * * @param cls closure * @param info details about the event, specifying the event type * and various bits about the event * @return client-context (for the next progress call * for this operation; should be set to NULL for * SUSPEND and STOPPED events). The value returned * will be passed to future callbacks in the respective * field in the GNUNET_FS_ProgressInfo struct. */ void* GNUNET_GTK_fs_event_handler (void *cls, const struct GNUNET_FS_ProgressInfo *info) { 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: GNUNET_break (0); break; case GNUNET_FS_STATUS_PUBLISH_SUSPEND: close_publish_tab (info->value.publish.cctx); return NULL; case GNUNET_FS_STATUS_PUBLISH_PROGRESS: return mark_publish_progress (info->value.publish.cctx, info->value.publish.size, info->value.publish.completed); case GNUNET_FS_STATUS_PUBLISH_ERROR: GNUNET_break (0); break; case GNUNET_FS_STATUS_PUBLISH_COMPLETED: return change_publish_colour (info->value.publish.cctx, "green"); case GNUNET_FS_STATUS_PUBLISH_STOPPED: close_publish_tab (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.specifics.start.meta, info->value.download.size, info->value.download.completed); case GNUNET_FS_STATUS_DOWNLOAD_RESUME: GNUNET_break (0); break; case GNUNET_FS_STATUS_DOWNLOAD_SUSPEND: stop_download (info->value.download.cctx, GNUNET_YES); return NULL; case GNUNET_FS_STATUS_DOWNLOAD_PROGRESS: return mark_download_progress (info->value.download.cctx, info->value.download.size, info->value.download.completed, info->value.download.specifics.progress.data, info->value.download.specifics.progress.offset, info->value.download.specifics.progress.data_len, info->value.download.specifics.progress.depth); case GNUNET_FS_STATUS_DOWNLOAD_ERROR: return change_download_colour (info->value.download.cctx, "red"); case GNUNET_FS_STATUS_DOWNLOAD_COMPLETED: return mark_download_completed (info->value.download.cctx, info->value.download.size, info->value.download.filename); case GNUNET_FS_STATUS_DOWNLOAD_STOPPED: stop_download (info->value.download.cctx, GNUNET_NO); return NULL; case GNUNET_FS_STATUS_DOWNLOAD_ACTIVE: return change_download_colour (info->value.download.cctx, "yellow"); case GNUNET_FS_STATUS_DOWNLOAD_INACTIVE: return change_download_colour (info->value.download.cctx, "blue"); case GNUNET_FS_STATUS_DOWNLOAD_LOST_PARENT: return download_lost_parent (info->value.download.cctx, info->value.download.size, info->value.download.completed, info->value.download.is_active); case GNUNET_FS_STATUS_SEARCH_START: if (info->value.search.pctx != NULL) { GNUNET_break (0); break; } return setup_search (info->value.search.sc, info->value.search.query); case GNUNET_FS_STATUS_SEARCH_RESUME: GNUNET_break (0); break; case GNUNET_FS_STATUS_SEARCH_RESUME_RESULT: GNUNET_break (0); break; case GNUNET_FS_STATUS_SEARCH_SUSPEND: close_search_tab (info->value.search.cctx); return NULL; case GNUNET_FS_STATUS_SEARCH_RESULT: return process_search_result (info->value.search.cctx, info->value.search.pctx, info->value.search.specifics.result.uri, info->value.search.specifics.result.meta, info->value.search.specifics.result.result, info->value.search.specifics.result.applicability_rank); case GNUNET_FS_STATUS_SEARCH_RESULT_NAMESPACE: GNUNET_break (0); break; case GNUNET_FS_STATUS_SEARCH_UPDATE: return update_search_result (info->value.search.specifics.update.cctx, info->value.search.specifics.update.meta, info->value.search.specifics.update.applicability_rank, info->value.search.specifics.update.availability_certainty, info->value.search.specifics.update.availability_rank); case GNUNET_FS_STATUS_SEARCH_ERROR: GNUNET_break (0); break; 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_suspend.cctx); return NULL; case GNUNET_FS_STATUS_SEARCH_RESULT_SUSPEND: free_search_result (info->value.search.specifics.result_suspend.cctx); return NULL; case GNUNET_FS_STATUS_SEARCH_STOPPED: close_search_tab (info->value.search.cctx); return NULL; case GNUNET_FS_STATUS_UNINDEX_START: GNUNET_break (0); break; case GNUNET_FS_STATUS_UNINDEX_RESUME: GNUNET_break (0); break; case GNUNET_FS_STATUS_UNINDEX_SUSPEND: GNUNET_break (0); break; case GNUNET_FS_STATUS_UNINDEX_PROGRESS: GNUNET_break (0); break; case GNUNET_FS_STATUS_UNINDEX_ERROR: GNUNET_break (0); break; case GNUNET_FS_STATUS_UNINDEX_COMPLETED: GNUNET_break (0); break; case GNUNET_FS_STATUS_UNINDEX_STOPPED: GNUNET_break (0); break; default: GNUNET_break (0); break; } return NULL; } /** * Page switched in main notebook, update thumbnail and * metadata views. */ void GNUNET_GTK_main_window_notebook_switch_page_cb (GtkWidget * dummy, gpointer data) { GtkNotebook *notebook; gint page; GtkWidget *w; struct SearchTab *tab; GtkImage *image; GtkListStore *ms; GtkTreeView *tv; notebook = GTK_NOTEBOOK (GNUNET_GTK_get_main_window_object ("GNUNET_GTK_main_window_notebook")); page = gtk_notebook_get_current_page (notebook); w = gtk_notebook_get_nth_page (notebook, page); tab = search_tab_head; while (tab != NULL) { if (tab->frame == w) { tv = GTK_TREE_VIEW (gtk_builder_get_object (tab->builder, "_search_result_frame")); update_meta_data_views (tv, tab); return; } tab = tab->next; } image = GTK_IMAGE (GNUNET_GTK_get_main_window_object ("GNUNET_GTK_main_window_preview_image")); gtk_image_clear (image); ms = GTK_LIST_STORE (GNUNET_GTK_get_main_window_object ("GNUNET_GTK_meta_data_list_store")); gtk_list_store_clear (ms); } /* end of fs_event_handler.c */