/* This file is part of GNUnet Copyright (C) 2011, 2012, 2013 GNUnet e.V. 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 3, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * @file src/fs/gnunet-fs-gtk_main-window-search.c * @author Christian Grothoff * @brief event handlers for the search function in the main window */ #include "gnunet-fs-gtk_common.h" #include "gnunet-fs-gtk.h" #include "gnunet-fs-gtk_anonymity-widgets.h" #include /** * How long until we decide a SKS namespace GNS lookup has failed? */ #define LOOKUP_TIMEOUT \ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 2) /** * Emits a beep using the display of the screen of the main file sharing window. */ static void beep () { gdk_display_beep (gdk_screen_get_display ( gdk_window_get_screen (gtk_widget_get_parent_window (GTK_WIDGET ( GNUNET_FS_GTK_get_main_window_object ("GNUNET_GTK_main_window")))))); } /** * Start a search. * * @param uri uri to search for, freed in this function * @param anonymity_level degree of anonymity to apply for the search */ static void search_for_uri (struct GNUNET_FS_Uri *uri, guint anonymity_level) { GNUNET_FS_search_start (GNUNET_FS_GTK_get_fs_handle (), uri, anonymity_level, GNUNET_FS_SEARCH_OPTION_NONE, NULL); GNUNET_FS_uri_destroy (uri); } /** * Abort the given search lookup. * * @param sl lookup to abort. */ void abort_search_lookup (struct SearchLookup *sl) { struct GNUNET_GTK_MainWindowContext *main_ctx = GNUNET_FS_GTK_get_main_context (); GNUNET_CONTAINER_DLL_remove (main_ctx->sl_head, main_ctx->sl_tail, sl); if (NULL != sl->timeout_task) { GNUNET_SCHEDULER_cancel (sl->timeout_task); sl->timeout_task = NULL; } if (NULL != sl->gns) { GNUNET_GNS_lookup_cancel (sl->gns); sl->gns = NULL; } g_free (sl->keywords); GNUNET_free (sl); } /** * Task run when the GNS timeout during the resolution of * the GNS namespace times out. * * @param cls the 'struct SearchLookup' */ static void timeout_search_lookup (void *cls) { struct SearchLookup *sl = cls; sl->timeout_task = NULL; GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Failed to resolve namespace in time\n")); abort_search_lookup (sl); } /** * Iterator called on obtained result for a GNS lookup for * the public key of a namespace identifier. * * @param cls closure * @param rd_count number of records in @a rd * @param rd the records in reply */ static void handle_gns_result (void *cls, uint32_t rd_count, const struct GNUNET_GNSRECORD_Data *rd) { struct SearchLookup *sl = cls; unsigned int i; struct GNUNET_FS_Uri *uri; sl->gns = NULL; for (i = 0; i < rd_count; i++) { if (GNUNET_GNSRECORD_TYPE_PKEY != rd[i].record_type) continue; if (sizeof (struct GNUNET_IDENTITY_PublicKey) != rd[i].data_size) { GNUNET_break_op (0); continue; } uri = GNUNET_FS_uri_sks_create (rd[i].data, sl->keywords); search_for_uri (uri, sl->anonymity_level); abort_search_lookup (sl); return; } GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Failed to resolve namespace in time\n")); abort_search_lookup (sl); } /** * Start a search. * * @param main_ctx the main window context */ static void start_search (struct GNUNET_GTK_MainWindowContext *main_ctx) { guint anonymity_level; gchar *keywords; gchar *mime_keyword; const char *nsid; struct GNUNET_FS_Uri *uri; struct GNUNET_IDENTITY_PublicKey pub_sks_zone; struct SearchLookup *sl; char *emsg; /* get anonymity level */ if (! GNUNET_GTK_get_selected_anonymity_level ( main_ctx->builder, "main_window_search_anonymity_combobox", &anonymity_level)) { GNUNET_break (0); return; } /* get selected mime type */ { GtkComboBox *mime_combo; GtkTreeModel *mime_model; GtkTreeIter iter; mime_combo = GTK_COMBO_BOX (GNUNET_FS_GTK_get_main_window_object ( "main_window_search_mime_combobox")); mime_model = gtk_combo_box_get_model (mime_combo); mime_keyword = NULL; if ((NULL != mime_model) && gtk_combo_box_get_active_iter (mime_combo, &iter)) gtk_tree_model_get (mime_model, &iter, GNUNET_GTK_FS_MAIN_WINDOW_SEARCH_MIME_MC_MIME, &mime_keyword, -1); if ((NULL != mime_keyword) && (0 == strcmp (mime_keyword, " "))) { g_free (mime_keyword); mime_keyword = NULL; } } { GtkComboBox *namespace_box; namespace_box = GTK_COMBO_BOX (GNUNET_FS_GTK_get_main_window_object ( "main_window_search_namespace_combobox")); nsid = gtk_entry_get_text ( GTK_ENTRY (gtk_bin_get_child (GTK_BIN (namespace_box)))); if ((NULL != nsid) && ((0 == strcasecmp (nsid, "")) || (0 == strcasecmp (nsid, _ (""))))) nsid = NULL; } /* get keywords and compose keyword string */ { const char *entry_keywords; entry_keywords = gtk_entry_get_text (main_ctx->search_entry); if (NULL != mime_keyword) { keywords = g_strdup_printf ("%s +%s", entry_keywords, mime_keyword); g_free (mime_keyword); } else { keywords = g_strdup (entry_keywords); } } /* build KSK/SKS URI */ if ((NULL != nsid) && (0 < strlen (nsid))) { sl = GNUNET_new (struct SearchLookup); sl->keywords = keywords; sl->anonymity_level = anonymity_level; sl->timeout_task = GNUNET_SCHEDULER_add_delayed (LOOKUP_TIMEOUT, &timeout_search_lookup, sl); GNUNET_IDENTITY_ego_get_public_key (main_ctx->sks_zone, &pub_sks_zone); sl->gns = GNUNET_GNS_lookup (main_ctx->gns, nsid, &pub_sks_zone, GNUNET_GNSRECORD_TYPE_PKEY, GNUNET_NO, &handle_gns_result, sl); GNUNET_CONTAINER_DLL_insert (main_ctx->sl_head, main_ctx->sl_tail, sl); return; } emsg = NULL; uri = GNUNET_FS_uri_ksk_create (keywords, &emsg); if (NULL == uri) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Invalid keyword string `%s': %s"), keywords, emsg); g_free (keywords); GNUNET_free (emsg); return; } g_free (keywords); search_for_uri (uri, anonymity_level); } /** * User clicked on the 'search' button in the main window. * * @param button the search button * @param user_data the main window context builder */ void main_window_search_button_clicked_cb (GtkButton *button, gpointer user_data) { struct GNUNET_GTK_MainWindowContext *main_ctx = user_data; start_search (main_ctx); } /** * User pushed a key (possibly ENTER) in the search entry widget. * Start the search if it was ENTER. * * @param widget the entry widget * @param event the key stroke * @param user_data the main window context * @return FALSE if this was not ENTER, TRUE if it was */ gboolean main_window_search_entry_key_press_event_cb (GtkWidget *widget, GdkEventKey *event, gpointer user_data) { struct GNUNET_GTK_MainWindowContext *main_ctx = user_data; if (event->keyval == GDK_KEY_Return) { start_search (main_ctx); return TRUE; } return FALSE; } /** * Asynchronously clean up PseuLookupContext. * * @param cls the `struct PseuLookupContext` to clean up. */ static void end_pseu_lookup (void *cls) { struct PseuLookupContext *lctx = cls; if (NULL != lctx->progress_dialog_builder) { gtk_widget_destroy (lctx->progress_dialog); g_object_unref (G_OBJECT (lctx->progress_dialog_builder)); lctx->progress_dialog_builder = NULL; } if (NULL != lctx->nick_dialog_builder) { gtk_widget_destroy (lctx->nick_dialog); g_object_unref (G_OBJECT (lctx->nick_dialog_builder)); lctx->nick_dialog_builder = NULL; } if (NULL != lctx->lr) { GNUNET_GNS_lookup_cancel (lctx->lr); lctx->lr = NULL; } if (NULL != lctx->qe) { GNUNET_NAMESTORE_cancel (lctx->qe); lctx->qe = NULL; } if (NULL != lctx->namestore) { GNUNET_NAMESTORE_disconnect (lctx->namestore); lctx->namestore = NULL; } GNUNET_free (lctx->nick); GNUNET_free (lctx); } /** * Abort the given PSEU lookup. * * @param lctx lookup to abort. */ void abort_pseu_lookup (struct PseuLookupContext *lctx) { struct GNUNET_GTK_MainWindowContext *main_ctx = lctx->main_ctx; GNUNET_CONTAINER_DLL_remove (main_ctx->lctx_head, main_ctx->lctx_tail, lctx); (void) GNUNET_SCHEDULER_add_now (&end_pseu_lookup, lctx); } /** * Continuation called to notify client about result of the * operation. * * @param cls closure * @param success #GNUNET_SYSERR on failure (including timeout/queue drop/failure to validate) * #GNUNET_NO if content was already there or not found * #GNUNET_YES (or other positive value) on success * @param emsg NULL on success, otherwise an error message */ static void store_continuation (void *cls, int32_t success, const char *emsg) { struct PseuLookupContext *lctx = cls; lctx->qe = NULL; if (NULL != emsg) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Failed to save record: %s\n"), emsg); beep (); } abort_pseu_lookup (lctx); } /** * Save the namespace under the given nickname. * * @param lctx namespace request we are processing * @param nick nickname to store the namespace under */ static void save_pseudonym_with_nick (struct PseuLookupContext *lctx, const char *nick) { struct GNUNET_GNSRECORD_Data rd; struct GNUNET_GTK_MainWindowContext *main_ctx; GNUNET_break (NULL == lctx->nick); lctx->nick = GNUNET_strdup (nick); /* again, show progress indicator, this should be fast though... */ lctx->progress_dialog_builder = GNUNET_GTK_get_new_builder ("gnunet_fs_gtk_pseu_progress_dialog.glade", lctx); lctx->progress_dialog = GTK_WIDGET (gtk_builder_get_object (lctx->progress_dialog_builder, "GNUNET_FS_GTK_pseu_progress_dialog")); /* show the window */ gtk_window_present (GTK_WINDOW (lctx->progress_dialog)); memset (&rd, 0, sizeof (rd)); rd.data_size = sizeof (struct GNUNET_IDENTITY_PublicKey); rd.data = &lctx->pkey; rd.flags = GNUNET_GNSRECORD_RF_PRIVATE; rd.expiration_time = GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us; rd.record_type = GNUNET_GNSRECORD_TYPE_PKEY; main_ctx = GNUNET_FS_GTK_get_main_context (); lctx->namestore = GNUNET_NAMESTORE_connect (main_ctx->cfg); GNUNET_assert (NULL != lctx->namestore); lctx->qe = GNUNET_NAMESTORE_records_store (lctx->namestore, main_ctx->sks_zone, nick, 1, &rd, &store_continuation, lctx); } /** * User clicked on the 'execute' button in nickname dialog. * Store the selected namespace in the "sks-fs" zone under * the given nickname. * * @param button the "execute" button * @param user_data the `struct PseuLookupContext` */ void GNUNET_GTK_enter_nick_window_execute_button_clicked_cb (GtkButton *button, gpointer user_data) { struct PseuLookupContext *lctx = user_data; GtkEntry *entry; const char *nick; entry = GTK_ENTRY ( gtk_builder_get_object (lctx->nick_dialog_builder, "GNUNET_GTK_enter_nick_window_nick_entry")); nick = gtk_entry_get_text (entry); if ((NULL == nick) || (0 == strlen (nick))) { GNUNET_break (0); abort_pseu_lookup (lctx); return; } save_pseudonym_with_nick (lctx, nick); gtk_widget_destroy (lctx->nick_dialog); g_object_unref (G_OBJECT (lctx->nick_dialog_builder)); lctx->nick_dialog_builder = NULL; lctx->nick_dialog = NULL; } /** * User edited the nickname. Update sensitivity of the execute button. * * @param widget the entry that was changed * @param user_data the `struct PseuLookupContext` */ void GNUNET_GTK_enter_nick_window_nick_entry_changed_cb (GtkWidget *widget, gpointer user_data) { struct PseuLookupContext *lctx = user_data; const gchar *new_text; GtkButton *button; new_text = gtk_entry_get_text (GTK_ENTRY (widget)); button = GTK_BUTTON ( gtk_builder_get_object (lctx->nick_dialog_builder, "GNUNET_GTK_enter_nick_window_execute_button")); gtk_widget_set_sensitive (GTK_WIDGET (button), (GNUNET_OK == GNUNET_DNSPARSER_check_label (new_text)) ? TRUE : FALSE); } /** * User clicked on the 'cancel' button in nickname dialog. * Abort the operation. * * @param button the "cancel" button * @param user_data the `struct PseuLookupContext` */ void GNUNET_GTK_enter_nick_window_cancel_button_clicked_cb (GtkButton *button, gpointer user_data) { struct PseuLookupContext *lctx = user_data; abort_pseu_lookup (lctx); } /** * Run the dialog asking the user to specify a nickname for * the namespace. * * @param lctx namespace request we are processing */ static void ask_for_nickname (struct PseuLookupContext *lctx) { /* setup the dialog and get the widgets we need most */ lctx->nick_dialog_builder = GNUNET_GTK_get_new_builder ("gnunet_fs_gtk_enter_nick_dialog.glade", lctx); lctx->nick_dialog = GTK_WIDGET (gtk_builder_get_object (lctx->nick_dialog_builder, "GNUNET_GTK_enter_nick_window")); /* show the window */ gtk_window_present (GTK_WINDOW (lctx->nick_dialog)); } /** * Iterator called on obtained result for a GNS lookup for * the PSEU lookup when "saving" a zone. The actual saving * should already have happened via the shortening of GNS, * so we only need to clean up. * * @param cls closure with the `struct PseuLookupContext` * @param rd_count number of records in @a rd * @param rd the records in reply */ static void lookup_finished (void *cls, uint32_t rd_count, const struct GNUNET_GNSRECORD_Data *rd) { struct PseuLookupContext *lctx = cls; unsigned int i; const char *nick; lctx->lr = NULL; if (NULL != lctx->progress_dialog_builder) { gtk_widget_destroy (lctx->progress_dialog); g_object_unref (G_OBJECT (lctx->progress_dialog_builder)); lctx->progress_dialog_builder = NULL; lctx->progress_dialog = NULL; } for (i = 0; i < rd_count; i++) { if (GNUNET_GNSRECORD_TYPE_NICK == rd[i].record_type) { nick = rd[i].data; if ('\0' != nick[rd[i].data_size - 1]) { GNUNET_break (0); continue; } save_pseudonym_with_nick (lctx, nick); return; } } /* no valid PSEU record found */ ask_for_nickname (lctx); } /** * User clicked on the 'cancel' button of the progress dialog. * Cancel the operation. * * @param button the cancel button * @param user_data the `struct PseuLookupContext` of our window */ void GNUNET_FS_GTK_pseu_progress_dialog_cancel_button_clicked_cb (GtkButton *button, gpointer user_data) { struct PseuLookupContext *lctx = user_data; if (NULL != lctx->progress_dialog_builder) { gtk_widget_destroy (lctx->progress_dialog); g_object_unref (G_OBJECT (lctx->progress_dialog_builder)); lctx->progress_dialog_builder = NULL; lctx->progress_dialog = NULL; } if (NULL != lctx->nick) abort_pseu_lookup (lctx); else ask_for_nickname (lctx); } /** * User attempted to close the nick dialog. Refuse. * * @param widget the widget emitting the event * @param event the event * @param cls progress dialog context of our window * @return TRUE to refuse to close */ gboolean GNUNET_GTK_enter_nick_window_delete_event_cb (GtkWidget *widget, GdkEvent *event, void *cls) { /* Don't allow GTK to kill the window, user must click execute or cancel */ beep (); return TRUE; } /** * User attempted to close the progress dialog. Refuse. * * @param widget the widget emitting the event * @param event the event * @param cls progress dialog context of our window * @return TRUE to refuse to close */ gboolean GNUNET_FS_GTK_pseu_progress_dialog_delete_event_cb (GtkWidget *widget, GdkEvent *event, void *cls) { /* Don't allow GTK to kill the window, until the search is finished */ beep (); return TRUE; } /** * User clicked on the 'save' button in the search line of the main window. * Store the selected namespace in the "sks-fs" zone. * * @param button the "save" button * @param user_data the main window context builder */ void GNUNET_FS_GTK_save_button_clicked_cb (GtkButton *button, gpointer user_data) { struct GNUNET_GTK_MainWindowContext *main_ctx = user_data; GtkComboBox *widget; const gchar *text; struct GNUNET_IDENTITY_PublicKey pkey; int ret; struct PseuLookupContext *lctx; guint anonymity_level; if (NULL == main_ctx->gns) { GNUNET_break (0); return; } /* get anonymity level */ if (! GNUNET_GTK_get_selected_anonymity_level ( main_ctx->builder, "main_window_search_anonymity_combobox", &anonymity_level)) { GNUNET_break (0); return; } widget = GTK_COMBO_BOX (GNUNET_FS_GTK_get_main_window_object ( "main_window_search_namespace_combobox")); text = gtk_entry_get_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (widget)))); ret = GNUNET_GNSRECORD_zkey_to_pkey (text, &pkey); if (GNUNET_OK != ret) { GNUNET_break (0); return; } lctx = GNUNET_new (struct PseuLookupContext); lctx->pkey = pkey; lctx->main_ctx = main_ctx; GNUNET_CONTAINER_DLL_insert (main_ctx->lctx_head, main_ctx->lctx_tail, lctx); if (0 == anonymity_level) { /* setup the dialog and get the widgets we need most */ lctx->progress_dialog_builder = GNUNET_GTK_get_new_builder ("gnunet_fs_gtk_pseu_progress_dialog.glade", lctx); lctx->progress_dialog = GTK_WIDGET ( gtk_builder_get_object (lctx->progress_dialog_builder, "GNUNET_FS_GTK_pseu_progress_dialog")); /* show the window */ gtk_window_present (GTK_WINDOW (lctx->progress_dialog)); lctx->lr = GNUNET_GNS_lookup (main_ctx->gns, GNUNET_GNS_EMPTY_LABEL_AT, &pkey, GNUNET_GNSRECORD_TYPE_NICK, GNUNET_NO, &lookup_finished, lctx); } else { /* anonymous operation; cannot use GNS/DHT, so user must make a suggestion himself */ ask_for_nickname (lctx); } /* do not allow save again just yet */ gtk_widget_set_sensitive (GTK_WIDGET (button), FALSE); } /** * The user has somehow changed the selectionin the namespace combo box entry. * Update the visibility of the "save" button. * * @param widget the combo box * @param user_data the main window context builder (unused) */ void main_window_search_namespace_combobox_changed_cb (GtkComboBox *widget, gpointer user_data) { GtkButton *button; const gchar *text; struct GNUNET_IDENTITY_PublicKey pkey; int ret; text = gtk_entry_get_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (widget)))); ret = GNUNET_GNSRECORD_zkey_to_pkey (text, &pkey); button = GTK_BUTTON ( GNUNET_FS_GTK_get_main_window_object ("GNUNET_FS_GTK_save_button")); gtk_widget_set_visible (GTK_WIDGET (button), (GNUNET_OK == ret) ? TRUE : FALSE); gtk_widget_set_sensitive (GTK_WIDGET (button), (GNUNET_OK == ret) ? TRUE : FALSE); } /* end of gnunet-fs-gtk_main-window-search.c */