/* This file is part of GNUnet. (C) 2005, 2006 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/plugins/fs/search.c * @brief code for searching with gnunet-gtk * @author Christian Grothoff */ #include "platform.h" #include "gnunetgtk_common.h" #include "search.h" #include "helper.h" #include "fs.h" #include "meta.h" #include #include #include /** * The user has edited the search entry. * Update search button status. */ static void on_fssearchSelectionChanged(gpointer signal, gpointer cls) { SearchList * list = cls; GtkTreeSelection * selection; GtkWidget * downloadButton; selection = gtk_tree_view_get_selection(list->treeview); downloadButton = glade_xml_get_widget(list->searchXML, "downloadButton"); gtk_widget_set_sensitive(downloadButton, gtk_tree_selection_count_selected_rows(selection) > 0); } /* **************** FSUI event handling ****************** */ /** * Update the number of results received for the given * search in the summary and in the label of the tab. */ static void updateSearchSummary(SearchList * searchContext) { GtkTreePath * path; GtkTreeIter iter; char * new_title; GtkLabel * label; path = gtk_tree_row_reference_get_path(searchContext->summaryViewRowReference); if (TRUE != gtk_tree_model_get_iter(GTK_TREE_MODEL(search_summary), &iter, path)) { GE_BREAK(ectx, 0); return; } gtk_tree_path_free(path); gtk_list_store_set(search_summary, &iter, SEARCH_SUMMARY_RESULT_COUNT, searchContext->resultsReceived, -1); /* update tab title with the number of results */ new_title = g_strdup_printf("%s (%u)", searchContext->searchString, searchContext->resultsReceived); label = GTK_LABEL(glade_xml_get_widget(searchContext->labelXML, "searchTabLabel")); gtk_label_set(label, new_title); FREE(new_title); } /** * Add the given search result to the search * tree at the specified position. */ void addEntryToSearchTree(SearchList * searchContext, DownloadList * downloadParent, const ECRS_FileInfo * info, GtkTreeIter * iter) { char * name; char * mime; char * desc; unsigned long long size; char * size_h; GdkPixbuf * pixbuf; mime = getMimeTypeFromMetaData(info->meta); desc = getDescriptionFromMetaData(info->meta); name = getFileNameFromMetaData(info->meta); size = ECRS_isFileUri(info->uri) ? ECRS_fileSize(info->uri) : 0; pixbuf = getThumbnailFromMetaData(info->meta); size_h = string_get_fancy_byte_size(size); gtk_tree_store_set(searchContext->tree, iter, SEARCH_NAME, name, SEARCH_SIZE, size, SEARCH_HSIZE, size_h, SEARCH_MIME, mime, SEARCH_DESC, desc, SEARCH_PIXBUF, pixbuf, SEARCH_URI, ECRS_dupUri(info->uri), SEARCH_META, ECRS_dupMetaData(info->meta), SEARCH_CELL_BG_COLOR, "white", SEARCH_CELL_FG_COLOR, "black", SEARCH_INTERNAL, searchContext, SEARCH_INTERNAL_PARENT, downloadParent, -1); FREE(size_h); FREE(name); FREE(desc); FREE(mime); } /** * Add the given result to the model (search result * list). * * @param info the information to add to the model * @param uri the search URI * @param searchContext identifies the search page */ void fs_search_result_received(SearchList * searchContext, const ECRS_FileInfo * info, const struct ECRS_URI * uri) { GtkTreeStore * model; GtkTreeIter iter; model = GTK_TREE_STORE(gtk_tree_view_get_model(searchContext->treeview)); gtk_tree_store_append(model, &iter, NULL); addEntryToSearchTree(searchContext, NULL, info, &iter); searchContext->resultsReceived++; updateSearchSummary(searchContext); } /** * FSUI event: a search was started; create the * tab and add an entry to the summary. */ SearchList * fs_search_started(struct FSUI_SearchList * fsui_list, const struct ECRS_URI * uri, unsigned int anonymityLevel, unsigned int resultCount, const ECRS_FileInfo * results, FSUI_State state) { SearchList * list; gint pages; char * description; const char * dhead; GtkTreeViewColumn * column; GtkCellRenderer * renderer; GtkNotebook * notebook; GtkTreePath * path; GtkTreeIter iter; int col; int i; description = ECRS_uriToString(uri); if (description == NULL) { GE_BREAK(ectx, 0); return NULL; } GE_ASSERT(ectx, strlen(description) >= strlen(ECRS_URI_PREFIX)); dhead = &description[strlen(ECRS_URI_PREFIX)]; if (0 == strncmp(dhead, ECRS_SEARCH_INFIX, strlen(ECRS_SEARCH_INFIX))) dhead = &dhead[strlen(ECRS_SEARCH_INFIX)]; else if (0 == strncmp(dhead, ECRS_SUBSPACE_INFIX, strlen(ECRS_SUBSPACE_INFIX))) dhead = &dhead[strlen(ECRS_SUBSPACE_INFIX)]; list = MALLOC(sizeof(SearchList)); memset(list, 0, sizeof(SearchList)); list->searchString = STRDUP(dhead); list->uri = ECRS_dupUri(uri); list->fsui_list = fsui_list; list->next = search_head; search_head = list; list->searchXML = glade_xml_new(getGladeFileName(), "searchResultsFrame", PACKAGE_NAME); connectGladeWithPlugins(list->searchXML); list->searchpage = extractMainWidgetFromWindow(list->searchXML, "searchResultsFrame"); /* setup tree view and renderers */ list->treeview = GTK_TREE_VIEW(glade_xml_get_widget(list->searchXML, "searchResults")); list->tree = gtk_tree_store_new(SEARCH_NUM, G_TYPE_STRING, /* name */ G_TYPE_UINT64, /* size */ G_TYPE_STRING, /* human-readable size */ G_TYPE_STRING, /* mime-type */ G_TYPE_STRING, /* meta-data (some) */ GDK_TYPE_PIXBUF, /* preview */ G_TYPE_POINTER, /* url */ G_TYPE_POINTER, /* meta */ G_TYPE_STRING, /* bg-color */ G_TYPE_STRING, /* fg-color */ G_TYPE_POINTER, /* internal: search list */ G_TYPE_POINTER); /* internal: download parent list */ gtk_tree_view_set_model(list->treeview, GTK_TREE_MODEL(list->tree)); gtk_tree_selection_set_mode(gtk_tree_view_get_selection(list->treeview), GTK_SELECTION_MULTIPLE); g_signal_connect_data(gtk_tree_view_get_selection(list->treeview), "changed", G_CALLBACK(&on_fssearchSelectionChanged), list, NULL, 0); renderer = gtk_cell_renderer_text_new(); col = gtk_tree_view_insert_column_with_attributes(list->treeview, -1, _("Name"), renderer, "text", SEARCH_NAME, NULL); column = gtk_tree_view_get_column(list->treeview, col - 1); gtk_tree_view_column_set_resizable(column, TRUE); gtk_tree_view_column_set_clickable(column, TRUE); gtk_tree_view_column_set_reorderable(column, TRUE); gtk_tree_view_column_set_sort_column_id(column, SEARCH_NAME); renderer = gtk_cell_renderer_text_new(); g_object_set (renderer, "xalign", 1.00, NULL); col = gtk_tree_view_insert_column_with_attributes(list->treeview, -1, _("Size"), renderer, "text", SEARCH_HSIZE, "cell-background", SEARCH_CELL_BG_COLOR, "foreground", SEARCH_CELL_FG_COLOR, NULL); column = gtk_tree_view_get_column(list->treeview, col - 1); gtk_tree_view_column_set_resizable(column, TRUE); gtk_tree_view_column_set_clickable(column, TRUE); gtk_tree_view_column_set_reorderable(column, TRUE); gtk_tree_view_column_set_sort_column_id(column, SEARCH_SIZE); renderer = gtk_cell_renderer_text_new(); col = gtk_tree_view_insert_column_with_attributes(list->treeview, -1, _("Mime-type"), renderer, "text", SEARCH_MIME, NULL); column = gtk_tree_view_get_column(list->treeview, col - 1); gtk_tree_view_column_set_resizable(column, TRUE); gtk_tree_view_column_set_clickable(column, TRUE); gtk_tree_view_column_set_reorderable(column, TRUE); gtk_tree_view_column_set_sort_column_id(column, SEARCH_MIME); renderer = gtk_cell_renderer_text_new(); col = gtk_tree_view_insert_column_with_attributes(list->treeview, -1, _("Meta-data"), renderer, "text", SEARCH_DESC, NULL); column = gtk_tree_view_get_column(list->treeview, col - 1); gtk_tree_view_column_set_resizable(column, TRUE); gtk_tree_view_column_set_clickable(column, TRUE); gtk_tree_view_column_set_reorderable(column, TRUE); gtk_tree_view_column_set_sort_column_id(column, SEARCH_DESC); /*gtk_tree_view_column_set_sort_indicator(column, TRUE);*/ if (YES != GC_get_configuration_value_yesno(cfg, "GNUNET-GTK", "DISABLE-PREVIEWS", NO)) { renderer = gtk_cell_renderer_pixbuf_new(); col = gtk_tree_view_insert_column_with_attributes(list->treeview, -1, _("Preview"), renderer, "pixbuf", SEARCH_PIXBUF, NULL); column = gtk_tree_view_get_column(list->treeview, col - 1); gtk_tree_view_column_set_resizable(column, TRUE); gtk_tree_view_column_set_reorderable(column, TRUE); gtk_tree_view_column_set_resizable(column, TRUE); } /* add entry in search summary */ gtk_list_store_append(search_summary, &iter); gtk_list_store_set(search_summary, &iter, SEARCH_SUMMARY_NAME, dhead, SEARCH_SUMMARY_RESULT_COUNT, 0, SEARCH_SUMMARY_INTERNAL, list, -1); FREE(description); path = gtk_tree_model_get_path(GTK_TREE_MODEL(search_summary), &iter); list->summaryViewRowReference = gtk_tree_row_reference_new(GTK_TREE_MODEL(search_summary), path); gtk_tree_path_free(path); /* load label */ list->labelXML = glade_xml_new(getGladeFileName(), "searchTabLabelWindow", PACKAGE_NAME); connectGladeWithPlugins(list->labelXML); list->tab_label = extractMainWidgetFromWindow(list->labelXML, "searchTabLabelWindow"); /* process existing results */ for (i=0;isearchpage, list->tab_label); gtk_notebook_set_current_page(notebook, pages); gtk_widget_show(GTK_WIDGET(notebook)); /* may have been hidden! */ return list; } /** * Recursively free the (internal) model data fields * (uri and meta) from the search tree model. */ static void freeIterSubtree(GtkTreeModel * tree, GtkTreeIter * iter) { GtkTreeIter child; struct ECRS_URI * uri; struct ECRS_MetaData * meta; do { uri = NULL; meta = NULL; gtk_tree_model_get(tree, iter, SEARCH_URI, &uri, SEARCH_META, &meta, -1); if (uri != NULL) ECRS_freeUri(uri); if (meta != NULL) ECRS_freeMetaData(meta); gtk_tree_store_set(GTK_TREE_STORE(tree), iter, SEARCH_URI, NULL, SEARCH_META, NULL, -1); if (gtk_tree_model_iter_children(tree, &child, iter)) freeIterSubtree(tree, &child); } while (gtk_tree_model_iter_next(tree, iter)); } /** * FSUI event: a search was aborted. * Update views accordingly. */ void fs_search_aborted(SearchList * list) { /* FIXME: show aborted status somehow! */ } /** * FSUI event: a search was stopped. Remove the * respective tab and its entry in the summary. */ void fs_search_stopped(SearchList * list) { GtkNotebook * notebook; GtkTreeIter iter; SearchList * prev; DownloadList * downloads; GtkTreePath * path; int index; int i; /* remove from linked list */ if (search_head == list) { search_head = search_head->next; } else { prev = search_head; while (prev->next != list) prev = prev->next; prev->next = list->next; } /* remove links from download views */ downloads = download_head; while (downloads != NULL) { if (downloads->searchList == list) { gtk_tree_row_reference_free(downloads->searchViewRowReference); downloads->searchViewRowReference = NULL; downloads->searchList = NULL; } downloads = downloads->next; } /* remove page from notebook */ notebook = GTK_NOTEBOOK(glade_xml_get_widget(getMainXML(), "downloadNotebook")); index = -1; for (i=gtk_notebook_get_n_pages(notebook)-1;i>=0;i--) if (list->searchpage == gtk_notebook_get_nth_page(notebook, i)) index = i; GE_BREAK(ectx, index != -1); gtk_notebook_remove_page(notebook, index); /* recursively free search model */ if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(list->tree), &iter)) freeIterSubtree(GTK_TREE_MODEL(list->tree), &iter); /* destroy entry in summary */ path = gtk_tree_row_reference_get_path(list->summaryViewRowReference); gtk_tree_model_get_iter(GTK_TREE_MODEL(search_summary), &iter, path); gtk_tree_path_free(path); gtk_list_store_remove(search_summary, &iter); /* free list state itself */ UNREF(list->searchXML); UNREF(list->labelXML); gtk_tree_row_reference_free(list->summaryViewRowReference); FREE(list->searchString); ECRS_freeUri(list->uri); FREE(list); } /* ****************** User event handling ************* */ /** * The user has edited the search entry. * Update search button status. */ void on_fssearchKeywordComboBoxEntry_changed_fs(gpointer dummy2, GtkWidget * searchEntry) { const char * searchString; GtkWidget * searchButton; searchString = getEntryLineValue(getMainXML(), "fssearchKeywordComboBoxEntry"); searchButton = glade_xml_get_widget(getMainXML(), "fssearchbutton"); gtk_widget_set_sensitive(searchButton, strlen(searchString) > 0); } /** * The user has clicked the "SEARCH" button. * Initiate a search. */ void on_fssearchbutton_clicked_fs(gpointer dummy2, GtkWidget * searchButton) { struct ECRS_URI * uri; const char * searchString; gint pages; gint i; SearchList * list; GtkTreeIter iter; GtkComboBox * searchKeywordGtkCB; GtkWidget * searchNamespaceGtkCB; GtkNotebook * notebook; searchString = getEntryLineValue(getMainXML(), "fssearchKeywordComboBoxEntry"); if ( (searchString == NULL) || (strlen(searchString) == 0) ) { GE_LOG(ectx, GE_ERROR | GE_USER | GE_IMMEDIATE, _("Need a keyword to search!\n")); return; } /* add the keyword to the list of keywords that have been used so far */ searchKeywordGtkCB = GTK_COMBO_BOX(glade_xml_get_widget(getMainXML(), "fssearchKeywordComboBoxEntry")); i = gtk_combo_box_get_active(searchKeywordGtkCB); if (i == -1) { GtkListStore * model; model = GTK_LIST_STORE(gtk_combo_box_get_model(searchKeywordGtkCB)); gtk_list_store_prepend(model, &iter); gtk_list_store_set(model, &iter, 0, searchString, -1); } uri = NULL; /* check for namespace search */ searchNamespaceGtkCB = glade_xml_get_widget(getMainXML(), "searchNamespaceComboBoxEntry"); if (TRUE == gtk_combo_box_get_active_iter(GTK_COMBO_BOX(searchNamespaceGtkCB), &iter)) { char * descStr; char * ns; ns = NULL; descStr = NULL; gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(searchNamespaceGtkCB)), &iter, NS_SEARCH_DESCRIPTION, &descStr, NS_SEARCH_ENCNAME, &ns, -1); if ( (descStr != NULL) && (0 == strcmp(descStr, _("globally"))) ) { ns = NULL; } else { GE_ASSERT(ectx, strlen(ns) == sizeof(EncName) - 1); if ( (descStr == NULL) && (ns != NULL) ) descStr = STRDUP(ns); } if (ns != NULL) { char * ustring; ustring = MALLOC(strlen(searchString) + sizeof(EncName) + strlen(ECRS_URI_PREFIX) + strlen(ECRS_SUBSPACE_INFIX) + 10); strcpy(ustring, ECRS_URI_PREFIX); strcat(ustring, ECRS_SUBSPACE_INFIX); strcat(ustring, ns); strcat(ustring, "/"); strcat(ustring, searchString); uri = ECRS_stringToUri(ectx, ustring); if (uri == NULL) { GE_LOG(ectx, GE_ERROR | GE_BULK | GE_USER, _("Failed to create namespace URI from `%s'.\n"), ustring); } FREE(ustring); } if (descStr != NULL) free(descStr); if (ns != NULL) free(ns); } if (uri == NULL) uri = ECRS_parseCharKeywordURI(ectx, searchString); if (uri == NULL) { GE_BREAK(ectx, 0); return; } /* check if search is already running */ notebook = GTK_NOTEBOOK(glade_xml_get_widget(getMainXML(), "downloadNotebook")); pages = gtk_notebook_get_n_pages(notebook); list = search_head; while (list != NULL) { if (ECRS_equalsUri(list->uri, uri)) { for (i=0;isearchpage) { gtk_notebook_set_current_page(notebook, i); ECRS_freeUri(uri); return; } } GE_BREAK(ectx, 0); } list = list->next; } FSUI_startSearch(ctx, getSpinButtonValue(getMainXML(), "searchAnonymitySelectionSpinButton"), getSpinButtonValue(getMainXML(), "maxResultsSpinButton"), getSpinButtonValue(getMainXML(), "searchDelaySpinButton") * cronSECONDS, uri); ECRS_freeUri(uri); } struct FCBC { int (*method)(struct FSUI_Context * ctx, struct FSUI_SearchList * list); struct FSUI_SearchList * argument; }; static void * fsui_callback(void * cls) { struct FCBC * fcbc = cls; fcbc->method(ctx, fcbc->argument); return NULL; } /** * This method is called when the user clicks on either * the "CLOSE" button (at the bottom of the search page) * or on the "CANCEL (X)" button in the TAB of the * search notebook. Note that "searchPage" can thus * either refer to the main page in the tab or to the * main entry of the tab label. */ void on_closeSearchButton_clicked_fs(GtkWidget * searchPage, GtkWidget * closeButton) { SearchList * list; struct FCBC fcbc; list = search_head; while (list != NULL) { if ( (list->searchpage == searchPage) || (list->tab_label == searchPage) ) break; list = list->next; } GE_ASSERT(ectx, list != NULL); fcbc.method = &FSUI_abortSearch; fcbc.argument = list->fsui_list; run_with_save_calls(&fsui_callback, &fcbc); fcbc.method = &FSUI_stopSearch; run_with_save_calls(&fsui_callback, &fcbc); } /** * The abort button was clicked. Abort the search. */ void on_abortSearchButton_clicked_fs(GtkWidget * searchPage, GtkWidget * closeButton) { SearchList * list; list = search_head; while (list != NULL) { if (list->searchpage == searchPage) break; list = list->next; } GE_ASSERT(ectx, list != NULL); FSUI_abortSearch(ctx, list->fsui_list); } static void stopSearch(GtkTreeModel * model, GtkTreePath * path, GtkTreeIter * iter, gpointer unused) { SearchList * s; struct FCBC fcbc; s = NULL; gtk_tree_model_get(model, iter, SEARCH_SUMMARY_INTERNAL, &s, -1); if (s != NULL) { fcbc.method = &FSUI_abortSearch; fcbc.argument = s->fsui_list; run_with_save_calls(&fsui_callback, &fcbc); fcbc.method = &FSUI_stopSearch; run_with_save_calls(&fsui_callback, &fcbc); } } /** * The stop button in the search summary was clicked. */ void on_closeSearchSummaryButton_clicked_fs(GtkWidget * treeview, GtkWidget * closeButton) { GtkTreeSelection * selection; selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)); ggc_tree_selection_selected_foreach (selection, &stopSearch, NULL); } static void abortSearch(GtkTreeModel * model, GtkTreePath * path, GtkTreeIter * iter, gpointer unused) { SearchList * s; struct FCBC fcbc; s = NULL; gtk_tree_model_get(model, iter, SEARCH_SUMMARY_INTERNAL, &s, -1); if (s != NULL) { fcbc.method = &FSUI_abortSearch; fcbc.argument = s->fsui_list; run_with_save_calls(&fsui_callback, &fcbc); } } /** * The abort button in the search summary was clicked. */ void on_abortSearchSummaryButton_clicked_fs(GtkWidget * treeview, GtkWidget * closeButton) { GtkTreeSelection * selection; selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)); ggc_tree_selection_selected_foreach (selection, &abortSearch, NULL); } /* end of search.c */