/* This file is part of GNUnet. (C) 2005 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 "fs.h" #include /** * @brief linked list of pages in the search notebook */ typedef struct SL { struct SL * next; GtkWidget * treeview; GtkWidget * searchpage; GtkTreeModel * model; GtkWidget * anonymityButton; struct ECRS_URI * uri; } SearchList; static SearchList * head; static GtkListStore * summary; /** * Add an entry to the search tree. * * @param model the search model * @param pos the position to add the entry * @param uri the URI to add * @param meta metadata describing the URI */ void addEntryToSearchTree(GtkTreeStore * model, GtkTreeIter * pos, const struct ECRS_URI * uri, const struct ECRS_MetaData * meta) { char * name; char * mime; char * desc; unsigned char * thumb; size_t ts; GdkPixbuf * pixbuf; GdkPixbufLoader * loader; unsigned long long size; mime = ECRS_getFromMetaData(meta, EXTRACTOR_MIMETYPE); if (mime == NULL) mime = STRDUP(_("unknown")); desc = ECRS_getFirstFromMetaData(meta, EXTRACTOR_DESCRIPTION, EXTRACTOR_GENRE, EXTRACTOR_ALBUM, EXTRACTOR_COMMENT, EXTRACTOR_SUBJECT, EXTRACTOR_FORMAT, EXTRACTOR_SIZE, EXTRACTOR_KEYWORDS, -1); if (desc == NULL) desc = STRDUP(""); name = ECRS_getFirstFromMetaData(meta, EXTRACTOR_FILENAME, EXTRACTOR_TITLE, EXTRACTOR_ARTIST, EXTRACTOR_AUTHOR, EXTRACTOR_PUBLISHER, EXTRACTOR_CREATOR, EXTRACTOR_PRODUCER, EXTRACTOR_UNKNOWN, -1); if (name == NULL) name = STRDUP(_("no name given")); size = ECRS_fileSize(uri); thumb = NULL; ts = ECRS_getThumbnailFromMetaData(meta, &thumb); if (ts != 0) { loader = gdk_pixbuf_loader_new(); gdk_pixbuf_loader_write(loader, (const guchar*) thumb, ts, NULL); pixbuf = gdk_pixbuf_loader_get_pixbuf(loader); gdk_pixbuf_loader_close(loader, NULL); } else { pixbuf = NULL; } gtk_tree_store_set(model, pos, SEARCH_NAME, name, SEARCH_SIZE, size, SEARCH_MIME, mime, SEARCH_DESC, desc, SEARCH_PIXBUF, pixbuf, SEARCH_URI, ECRS_dupUri(uri), SEARCH_META, ECRS_dupMetaData(meta), SEARCH_INTERNAL, NULL, /* internal */ -1); FREE(mime); FREE(desc); FREE(name); FREENONNULL(thumb); } GtkWidget * getAnonymityButtonFromTM(GtkTreeModel * model) { SearchList * list; list = head; while (list != NULL) { if (list->model == model) return list->anonymityButton; list = list->next; } return NULL; } /** * 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)); } void on_searchResults_destroy(GtkWidget * dummy, GtkWidget * treeview) { GtkTreeStore * tree; GtkTreeIter iter; tree = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(treeview))); if (! gtk_tree_model_get_iter_first(GTK_TREE_MODEL(tree), &iter)) return; /* tree empty */ freeIterSubtree(GTK_TREE_MODEL(tree), &iter); } /** * Add the given result to the model (search result * list). * @param info the information to add to the model * @param treeview the page from which to obtain the model * @param path the tree path that selects where to add * the information */ static void addSearchResultToModel(const ECRS_FileInfo * info, GtkWidget * treeview, GtkTreeRowReference * row) { GtkTreeStore * model; GtkTreeIter iter; GtkTreeIter parent; GtkTreeIter * pparent; GtkTreePath * path; if (! gtk_tree_row_reference_valid(row)) path = NULL; else path = gtk_tree_row_reference_get_path(row); model = GTK_TREE_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW(treeview))); if (path != NULL) { gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &parent, path); pparent = &parent; } else pparent = NULL; gtk_tree_store_insert(model, &iter, pparent, 0); addEntryToSearchTree(model, &iter, info->uri, info->meta); } /** * 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 path the tree path that selects where to add * the information, NULL for top-level */ void displaySearchResult(const ECRS_FileInfo * info, const struct ECRS_URI * uri, GtkTreeRowReference * row) { SearchList * list; struct ECRS_URI * euri; unsigned int count; GtkTreeIter iter; list = head; while (list != NULL) { if (ECRS_equalsUri(list->uri, uri)) break; list = list->next; } if (list == NULL) { BREAK(); return; } addSearchResultToModel(info, list->treeview, row); if (! gtk_tree_model_get_iter_first(GTK_TREE_MODEL(summary), &iter)) { BREAK(); return; } do { gtk_tree_model_get(GTK_TREE_MODEL(summary), &iter, SER_SUM_COUNT, &count, SER_SUM_URI, &euri, -1); if (ECRS_equalsUri(euri, uri)) { count++; gtk_list_store_set(GTK_LIST_STORE(summary), &iter, SER_SUM_COUNT, count, -1); return; } } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(summary), &iter)); BREAK(); } void on_closeSearchButton_clicked(GtkWidget * searchPage, GtkWidget * closeButton) { GtkWidget * notebook; int index; int i; struct ECRS_URI * uri; SearchList * list; SearchList * prev; GtkTreeIter iter; struct ECRS_URI * euri; list = head; prev = NULL; while (list != NULL) { if (list->searchpage == searchPage) break; prev = list; list = list->next; } GNUNET_ASSERT(list != NULL); uri = list->uri; FSUI_stopSearch(ctx, uri); if (prev == NULL) head = list->next; else prev->next = list->next; FREE(list); notebook = glade_xml_get_widget(getMainXML(), "downloadNotebook"); index = -1; for (i=gtk_notebook_get_n_pages(GTK_NOTEBOOK(notebook))-1;i>=0;i--) if (searchPage == gtk_notebook_get_nth_page(GTK_NOTEBOOK(notebook), i)) index = i; if (index != -1) { gtk_notebook_remove_page(GTK_NOTEBOOK(notebook), index); } else { BREAK(); } if (! gtk_tree_model_get_iter_first(GTK_TREE_MODEL(summary), &iter)) { BREAK(); ECRS_freeUri(uri); return; } do { gtk_tree_model_get(GTK_TREE_MODEL(summary), &iter, SER_SUM_URI, &euri, -1); if (ECRS_equalsUri(euri, uri)) { gtk_list_store_remove(GTK_LIST_STORE(summary), &iter); ECRS_freeUri(euri); ECRS_freeUri(uri); return; } } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(summary), &iter)); ECRS_freeUri(uri); BREAK(); } static GtkWidget * makeResultFrame(GtkWidget ** treeview, GtkWidget ** anonSpin) { GtkWidget * window; GtkWidget * child; GtkWidget * resultList; GtkCellRenderer * renderer; GtkTreeStore * tree; GladeXML * searchXML; searchXML = glade_xml_new(getGladeFileName(), "searchResultsFrame", PACKAGE_NAME); connectGladeWithPlugins(searchXML); window = glade_xml_get_widget(searchXML, "searchResultsFrame"); resultList = glade_xml_get_widget(searchXML, "searchResults"); *anonSpin = glade_xml_get_widget(searchXML, "downloadAnonymitySpinButton"); if (treeview != NULL) (*treeview) = GTK_WIDGET(GTK_TREE_VIEW(resultList)); tree = gtk_tree_store_new(SEARCH_NUM, G_TYPE_STRING, /* name */ G_TYPE_UINT64, /* 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_POINTER); /* internal: download info/NULL */ gtk_tree_view_set_model(GTK_TREE_VIEW(resultList), GTK_TREE_MODEL(tree)); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(resultList), -1, _("Name"), renderer, "text", SEARCH_NAME, NULL); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(resultList), -1, _("Size"), renderer, "text", SEARCH_SIZE, NULL); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(resultList), -1, _("Mime-type"), renderer, "text", SEARCH_MIME, NULL); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(resultList), -1, _("Meta-data"), renderer, "text", SEARCH_DESC, NULL); renderer = gtk_cell_renderer_pixbuf_new(); gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(resultList), -1, _("Preview"), renderer, "pixbuf", SEARCH_PIXBUF, NULL); child = gtk_bin_get_child(GTK_BIN(window)); gtk_widget_ref(GTK_WIDGET(child)); gtk_container_remove(GTK_CONTAINER(window), child); gtk_widget_destroy(window); UNREF(searchXML); return child; } void on_fssearchbutton_clicked(gpointer dummy2, GtkWidget * searchButton) { GtkWidget * searchKeywordGtkCB; GtkWidget * searchNamespaceGtkCB; GtkWidget * notebook; GtkWidget * page; GtkWidget * label; GtkWidget * entry; GtkWidget * spin; GtkListStore * model; GtkTreeIter iter; struct ECRS_URI * uri; const char * ss; const char * ns; gint pages; gint i; char * tabtxt; SearchList * list; searchKeywordGtkCB = glade_xml_get_widget(getMainXML(), "fssearchKeywordComboBoxEntry"); entry = gtk_bin_get_child(GTK_BIN(searchKeywordGtkCB)); ss = gtk_entry_get_text(GTK_ENTRY(entry)); if (ss == NULL) { LOG(LOG_ERROR, "Need a keyword to search!\n"); return; } i = gtk_combo_box_get_active(GTK_COMBO_BOX(searchKeywordGtkCB)); if (i == -1) { model = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX(searchKeywordGtkCB))); gtk_list_store_prepend(model, &iter); gtk_list_store_set(model, &iter, 0, ss, -1); } searchNamespaceGtkCB = glade_xml_get_widget(getMainXML(), "searchNamespaceComboBoxEntry"); entry = gtk_bin_get_child(GTK_BIN(searchNamespaceGtkCB)); ns = gtk_entry_get_text(GTK_ENTRY(entry)); if ( (ns != NULL) && (0 == strcmp(ns, _("globally"))) ) ns = NULL; if (ns != NULL) { char * ustring; GNUNET_ASSERT(strlen(ns) > sizeof(EncName)); ns = &ns[strlen(ns) - sizeof(EncName) + 1]; ustring = MALLOC(strlen(ss) + 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, ss); uri = ECRS_stringToUri(ustring); if (uri == NULL) { LOG(LOG_ERROR, _("Failed to create namespace URI from '%s'.\n"), ustring); } FREE(ustring); } else { uri = FSUI_parseCharKeywordURI(ss); } if (uri == NULL) return; if (ns == NULL) { tabtxt = STRDUP(ss); } else { tabtxt = MALLOC(strlen(ss) + strlen(ns) + 2); SNPRINTF(tabtxt, strlen(ss) + strlen(ns) + 2, "%s/%s", ns, ss); } notebook = glade_xml_get_widget(getMainXML(), "downloadNotebook"); list = head; pages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(notebook)); while (list != NULL) { if (ECRS_equalsUri(list->uri, uri)) { for (i=0;isearchpage) { gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), i); ECRS_freeUri(uri); FREE(tabtxt); return; } } BREAK(); } list = list->next; } list = MALLOC(sizeof(SearchList)); list->searchpage = makeResultFrame(&list->treeview, &spin); list->next = head; list->uri = uri; list->model = gtk_tree_view_get_model(GTK_TREE_VIEW(list->treeview)); list->anonymityButton = spin; head = list; gtk_list_store_append(summary, &iter); gtk_list_store_set(summary, &iter, SER_SUM_NAME, tabtxt, SER_SUM_COUNT, 0, SER_SUM_URI, ECRS_dupUri(uri), -1); label = gtk_label_new(tabtxt); gtk_notebook_append_page(GTK_NOTEBOOK(notebook), list->searchpage, label); gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), pages); gtk_widget_show(notebook); spin = glade_xml_get_widget(getMainXML(), "searchAnonymitySelectionSpinButton"); FSUI_startSearch(ctx, gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON(spin)), uri); FREE(tabtxt); } static int addNamespace(GtkListStore * model, const char * namespaceName, const HashCode512 * namespaceId, const struct ECRS_MetaData * md, int rating) { GtkTreeIter iter; EncName enc; char * name; hash2enc(namespaceId, &enc); name = MALLOC(strlen(namespaceName) + sizeof(EncName) + 2); SNPRINTF(name, strlen(namespaceName) + sizeof(EncName) + 2, "%s: %s", &enc, namespaceName); gtk_list_store_append(model, &iter); gtk_list_store_set(model, &iter, 0, name, -1); FREE(name); return OK; } /** * cron job that periodically updates the model for the * namespace selection in the search vbox. */ static void updateNCBModelSafe(void * unused) { GtkWidget * searchNamespaceCB; GtkListStore * model; GtkTreeIter iter; model = gtk_list_store_new(1, G_TYPE_STRING); gtk_list_store_append(model, &iter); gtk_list_store_set(model, &iter, 0, _("globally"), -1); FSUI_listNamespaces(ctx, NO, (FSUI_NamespaceIterator)&addNamespace, model); searchNamespaceCB = glade_xml_get_widget(getMainXML(), "searchNamespaceComboBoxEntry"); gtk_combo_box_set_model(GTK_COMBO_BOX(searchNamespaceCB), GTK_TREE_MODEL(model)); if (gtk_combo_box_entry_get_text_column(GTK_COMBO_BOX_ENTRY(searchNamespaceCB)) == -1) gtk_combo_box_entry_set_text_column(GTK_COMBO_BOX_ENTRY(searchNamespaceCB), 0); if (-1 == gtk_combo_box_get_active(GTK_COMBO_BOX(searchNamespaceCB))) gtk_combo_box_set_active(GTK_COMBO_BOX(searchNamespaceCB), 0); } static void updateNCBModel(void * dummy) { gtkSaveCall(&updateNCBModelSafe, NULL); } /** * Open a tab for the given search * (and display the results). */ static int openTabForSearch(void * unused, const struct ECRS_URI * uri, unsigned int anonymityLevel, unsigned int resultCount, const ECRS_FileInfo * results) { SearchList * list; char * description; char * dhead; GtkWidget * label; GtkWidget * spin; GtkWidget * notebook; GtkTreeStore * model; GtkTreeIter iter; int i; description = ECRS_uriToString(uri); if (description == NULL) { BREAK(); return SYSERR; } GNUNET_ASSERT(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)]; label = gtk_label_new(dhead); gtk_list_store_append(summary, &iter); gtk_list_store_set(summary, &iter, SER_SUM_NAME, dhead, SER_SUM_COUNT, resultCount, SER_SUM_URI, ECRS_dupUri(uri), -1); FREE(description); list = MALLOC(sizeof(SearchList)); list->uri = ECRS_dupUri(uri); list->next = head; list->searchpage = makeResultFrame(&list->treeview, &spin); list->anonymityButton = spin; model = GTK_TREE_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW(list->treeview))); list->model = GTK_TREE_MODEL(model); notebook = glade_xml_get_widget(getMainXML(), "downloadNotebook"); gtk_notebook_append_page(GTK_NOTEBOOK(notebook), list->searchpage, label); gtk_widget_show(notebook); head = list; for (i=0;itreeview, NULL); } return OK; } void fs_search_start() { GtkWidget * searchCB; GtkListStore * model; GtkWidget * searchList; GtkCellRenderer * renderer; searchCB = glade_xml_get_widget(getMainXML(), "fssearchKeywordComboBoxEntry"); model = gtk_list_store_new(1, G_TYPE_STRING); gtk_combo_box_set_model(GTK_COMBO_BOX(searchCB), GTK_TREE_MODEL(model)); gtk_combo_box_entry_set_text_column(GTK_COMBO_BOX_ENTRY(searchCB), 0); addCronJob(&updateNCBModel, 0, 5 * cronMINUTES, NULL); searchList = glade_xml_get_widget(getMainXML(), "activeSearchesSummary"); summary = gtk_list_store_new(SER_SUM_NUM, G_TYPE_STRING, /* name */ G_TYPE_INT, /* # results */ G_TYPE_POINTER); /* internal: uri */ gtk_tree_view_set_model(GTK_TREE_VIEW(searchList), GTK_TREE_MODEL(summary)); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(searchList), -1, _("Query"), renderer, "text", SER_SUM_NAME, NULL); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(searchList), -1, _("Results"), renderer, "text", SER_SUM_COUNT, NULL); FSUI_listSearches(ctx, &openTabForSearch, NULL); } void fs_search_stop() { SearchList * list; delCronJob(&updateNCBModel, 5 * cronMINUTES, NULL); while (head != NULL) { list = head; head = head->next; ECRS_freeUri(list->uri); FREE(list); } } /* end of search.c */