/*
     This file is part of GNUnet.
     Copyright (C) 2010-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_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"
#include "gnunet-fs-gtk_open-uri.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,

  /**
     * A gboolean.
     */
  SEARCH_TAB_MC_SHOW_NS_ASSOCIATION = 19

};


/**
 * 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_GTK_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 search results with a manually-activated probe.
 */
struct SearchResult *pl_head;

/**
 * Tail of search results with a manually-activated probe.
 */
struct SearchResult *pl_tail;

/**
 * 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
 * opened directory files.
 */
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;

/**
 * Animation to display while publishing.
 */
static struct GNUNET_GTK_AnimationContext *animation_publishing;

/**
 * Animation to display after publishing is complete.
 */
static struct GNUNET_GTK_AnimationContext *animation_published;

/**
 * Animation to display while downloading.
 */
static struct GNUNET_GTK_AnimationContext *animation_downloading;

/**
 * Animation to display after downloading is complete.
 */
static struct GNUNET_GTK_AnimationContext *animation_downloaded;

/**
 * Animation to display if a download has stalled.
 */
static struct GNUNET_GTK_AnimationContext *animation_download_stalled;

/**
 * Animation to display while searching for sources to download from.
 */
static struct GNUNET_GTK_AnimationContext *animation_searching_sources;

/**
 * Animation to display if we found sources to download from.
 */
static struct GNUNET_GTK_AnimationContext *animation_found_sources;

/**
 * Animation to display if we encountered a hard error.
 */
static struct GNUNET_GTK_AnimationContext *animation_error;


/**
 * Get the handle to the current search tab.
 *
 * @return handle to the current search tab.
 */
struct SearchTab *
GNUNET_FS_GTK_get_current_search_tab ()
{
  return current_search_tab;
}


/* ***************** Search event handling ****************** */

/**
 * Load an animation.
 *
 * @param basename basename of the animation file to load
 * @return handle to the animation.
 */
static struct GNUNET_GTK_AnimationContext *
load_animation (const char *basename)
{
  struct GNUNET_GTK_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 @a buffer up to @a size bytes, returns a pointer to it.
 * Buffer will be NUL-terminated, if not NULL.
 *
 * @param buffer where to store the download directory name
 * @param size number of bytes available in @a buffer
 * @return
 */
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;
}

/**
 *
 *
 * @param tm which tree model are we inspecting
 * @param iter location in the tree model we are getting data from
 * @param 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.
 * @param 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).
 *    Returned strings should be freed with GNUNET_free() if not NULL.
 * @param 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;
 *    Returned strings should be freed with GNUNET_free() if not NULL.
 * @param anonymity anonymity level of one of the *parents. Initialize to -1. If none were downloaded, remains -1.
 */
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, NULL for
   * the downloads list.
   */
  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);


/**
 * Setup an entry for the download in the download list.
 * Initializes the 'rr' field of @a de.
 *
 * @param de the download entry to initialize
 * @param pde download entry of the parent, or NULL for top-level
 * @param sr associated search result to add to the model
 */
static void
setup_download_list_entry (struct DownloadEntry *de,
                           struct DownloadEntry *pde,
                           struct SearchResult *sr)
{
  GtkTreeIter download_iter;
  GtkTreeIter download_piter;
  GtkTreePath *path;

  /* created row in download list */
  if (NULL == pde)
  {
    /* top level */
    gtk_tree_store_insert (downloads_treestore, &download_iter, NULL, G_MAXINT);
  }
  else
  {
    /* below parent */
    path = gtk_tree_row_reference_get_path (pde->rr);
    GNUNET_assert (
      gtk_tree_model_get_iter (GTK_TREE_MODEL (downloads_treestore),
                               &download_piter,
                               path));
    gtk_tree_path_free (path);
    gtk_tree_store_insert (downloads_treestore,
                           &download_iter,
                           &download_piter,
                           G_MAXINT);
  }
  gtk_tree_store_set (downloads_treestore,
                      &download_iter,
                      SEARCH_TAB_MC_SEARCH_RESULT,
                      sr,
                      -1);
  path = gtk_tree_model_get_path (GTK_TREE_MODEL (downloads_treestore),
                                  &download_iter);
  de->rr =
    gtk_tree_row_reference_new (GTK_TREE_MODEL (downloads_treestore), path);
  gtk_tree_path_free (path);
}


/**
 * Obtain the iterator for the respective download entry.
 *
 * @param de the download entry
 * @param[out] iter set to the respective position in the #downloads_treestore
 */
static void
get_download_list_entry (struct DownloadEntry *de, GtkTreeIter *iter)
{
  GtkTreePath *path;

  path = gtk_tree_row_reference_get_path (de->rr);
  GNUNET_assert (
    gtk_tree_model_get_iter (GTK_TREE_MODEL (downloads_treestore), iter, path));
  gtk_tree_path_free (path);
}


/**
 *
 * @param save_as #GNUNET_YES to open SaveAs dialog, #GNUNET_NO to start downloading.
 * @param 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 ();
  GtkTreeView *tv;
  GtkTreeSelection *sel;
  GtkTreeModel *model;
  GtkTreeIter iter;
  GtkTreeIter parent_iter;
  struct SearchResult *sr;
  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;
  gtk_tree_model_get (model, &iter, SEARCH_TAB_MC_SEARCH_RESULT, &sr, -1);
  if (NULL == sr)
  {
    GNUNET_break (0);
    return;
  }
  GNUNET_assert (NULL != sr->uri);
  if (GNUNET_FS_uri_test_ksk (sr->uri) || GNUNET_FS_uri_test_sks (sr->uri))
  {
    GNUNET_FS_GTK_handle_uri (sr->uri, 1);
    return;
  }

  if (! ((NULL == sr->download) && (NULL != sr->uri) &&
         ((GNUNET_FS_uri_test_chk (sr->uri) ||
           GNUNET_FS_uri_test_loc (sr->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_new (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 */
  setup_download_list_entry (de, de->pde, sr);

  de->uri = GNUNET_FS_uri_dup (sr->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 (sr->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?
 * @param 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);
}


/**
 * "Get root element of associated namespace X" was selected in the
 * current search context menu.  Handle the respective URI by
 * displaying it in the search bar.
 *
 * @param item the menu item that was selected
 * @param user_data NULL
 */
static void
handle_uri_from_ctx_menu (GtkMenuItem *item, gpointer user_data)
{
  struct GNUNET_FS_Uri *uri;
  char *uris;

  uri = g_object_get_data (G_OBJECT (item), "fs-uri");
  if (NULL == uri)
    return;
  uris = GNUNET_FS_uri_to_string (uri);
  if (NULL == uris)
    return;
  GNUNET_break (GNUNET_OK == GNUNET_FS_GTK_handle_uri_string (uris, 1));
  GNUNET_free (uris);
  /* GObject will clean up the URI */
}


/**
 * "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);
}


/**
 * Selected row has changed in downloads tree view, update preview
 * and metadata areas.
 *
 * @param tv the tree view in a search tab where the selection changed
 * @param user_data unused
 */
void
GNUNET_FS_GTK_download_frame_treeview_cursor_changed_cb (GtkTreeView *tv,
                                                         gpointer user_data)
{
  GNUNET_FS_GTK_search_treeview_cursor_changed (tv, NULL);
}


/**
 * Download "abort" was selected in the current search context menu.
 *
 * @param item the 'abort' menu item
 * @param 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 (NULL != de->dc);
  GNUNET_FS_download_stop (de->dc, GNUNET_YES);
  if (NULL != spc->tab)
  {
    tv = GTK_TREE_VIEW (
      gtk_builder_get_object (spc->tab->builder, "_search_result_frame"));
    GNUNET_FS_GTK_search_treeview_cursor_changed (tv, spc->tab);
  }
  else
  {
    tv = GTK_TREE_VIEW (
      GNUNET_FS_GTK_get_main_window_object ("GNUNET_GTK_download_frame"));
    GNUNET_FS_GTK_download_frame_treeview_cursor_changed_cb (tv, NULL);
  }
}


/**
 * Copy current URI to clipboard was selected in the current context menu.
 *
 * @param item the 'copy-to-clipboard' menu item
 * @param 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);
  if (NULL != spc->tab)
    tv = GTK_TREE_VIEW (
      gtk_builder_get_object (spc->tab->builder, "_search_result_frame"));
  else
    tv = GTK_TREE_VIEW (
      GNUNET_FS_GTK_get_main_window_object ("GNUNET_GTK_download_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);
}


/**
 * Callback invoked for each embedded URIs.
 *
 * @return 0 to continue iteration, 1 to abort
 */
typedef int (*EmbeddedUriCallback) (void *cls, const struct GNUNET_FS_Uri *uri);


/**
 * Closure for #check_for_embedded_uri.
 */
struct UriScannerCallbackContext
{
  /**
   * Function to call with URIs that were found.
   */
  EmbeddedUriCallback cb;

  /**
   * Closure for @e cb
   */
  void *cls;
};


/**
 * Metadata callback. Checks metadata item for being an FS URI,
 * invokes the callback if so.
 *
 * @param cls the `struct UriScannerCallbackContext`
 * @param plugin_name name of the plugin that did the extraction (ignored)
 * @param type type of meta data
 * @param format format of the meta data
 * @param data_mime_type mime type of the meta data
 * @param data the meta data value
 * @param data_size number of bytes in @a data
 * @return 0 we did not invoke the callback, otherwise
 *         whatever the callback returned
 */
static int
check_for_embedded_uri (void *cls,
                        const char *plugin_name,
                        enum EXTRACTOR_MetaType type,
                        enum EXTRACTOR_MetaFormat format,
                        const char *data_mime_type,
                        const char *data,
                        size_t data_size)
{
  struct UriScannerCallbackContext *ctx = cls;
  int result;
  char *emsg;
  struct GNUNET_FS_Uri *uri;

  if ((EXTRACTOR_METATYPE_URI != type) ||
      (EXTRACTOR_METAFORMAT_UTF8 != format) || (NULL == data_mime_type) ||
      (0 != strcmp ("text/plain", data_mime_type)) || (0 == data_size) ||
      ('\0' != data[data_size - 1]))
    return 0;
  emsg = NULL;
  uri = GNUNET_FS_uri_parse (data, &emsg);
  if (NULL == uri)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                _ ("Failed to parse URI `%.*s': %s\n"),
                (int) data_size,
                data,
                emsg);
    GNUNET_free (emsg);
    return 0;
  }
  result = ctx->cb (ctx->cls, uri);
  GNUNET_FS_uri_destroy (uri);
  return result;
}


/**
 * Search metadata for URIs, invoke the callback for each one.
 *
 * @param meta meta data to inspect
 * @param cb function to call on URIs that were found
 * @param cls closure for @a cb
 */
static void
find_embedded_uris (const struct GNUNET_CONTAINER_MetaData *meta,
                    EmbeddedUriCallback cb,
                    void *cls)
{
  struct UriScannerCallbackContext ctx;

  ctx.cb = cb;
  ctx.cls = cls;
  GNUNET_CONTAINER_meta_data_iterate (meta, &check_for_embedded_uri, &ctx);
}


/**
 * Closure for #populate_popup_with_uri_items
 */
struct UriPopulationContext
{
  /**
   * Popup menu we are populating.
   */
  GtkMenu *menu;

  /**
   * Counter used to limit the number of entries we add.
   */
  int counter;
};


/**
 * Called for every URI in metadata. Adds up to 3 menu items
 * that will initiate downloads for these URIs
 *
 * @param cls the `struct UriPopulationContext`
 * @param uri URI to download
 * @return 0 if we might add more iterms,
 *         1 if we have reached the limit
 */
static int
populate_popup_with_uri_items (void *cls, const struct GNUNET_FS_Uri *uri)
{
  struct UriPopulationContext *ctx = cls;
  GtkWidget *child;
  char *labels;
  GtkWidget *ns_association_icon;
  char *uris;
  GtkWidget *box;
  GtkWidget *label;
  GtkAccelGroup *accel_group;

  ctx->counter++;
  uris = GNUNET_FS_uri_to_string (uri);
  GNUNET_asprintf (&labels, _ ("URI #%d: %s"), ctx->counter, uris);
  GNUNET_free (uris);
  label = gtk_accel_label_new (labels);
  GNUNET_free (labels);
  box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
  child = gtk_menu_item_new ();
  ns_association_icon =
    gtk_image_new_from_icon_name ("gnunet-fs-gtk-ns-association",
                                  GTK_ICON_SIZE_MENU);
  gtk_container_add (GTK_CONTAINER (box), ns_association_icon);
#if GTK_CHECK_VERSION(3, 16, 0)
  gtk_label_set_xalign (GTK_LABEL (label), 0.0);
#endif
  accel_group = gtk_accel_group_new ();
  gtk_widget_add_accelerator (child,
                              "activate",
                              accel_group,
                              GDK_KEY_m,
                              GDK_CONTROL_MASK,
                              GTK_ACCEL_VISIBLE);
  gtk_accel_label_set_accel_widget (GTK_ACCEL_LABEL (label), child);
  gtk_box_pack_end (GTK_BOX (box), label, TRUE, TRUE, 0);
  gtk_container_add (GTK_CONTAINER (child), box);

  g_object_set_data_full (G_OBJECT (child),
                          "fs-uri",
                          GNUNET_FS_uri_dup (uri),
                          (GDestroyNotify) &GNUNET_FS_uri_destroy);
  g_signal_connect (child,
                    "activate",
                    G_CALLBACK (handle_uri_from_ctx_menu),
                    NULL);
  gtk_widget_show (child);
  gtk_menu_shell_append (GTK_MENU_SHELL (ctx->menu), child);

  return (ctx->counter < 4) ? 0 : 1;
}


/**
 * 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, NULL for download tab
 * @param iter location in the tree model selected at the time
 * @return NULL if no menu could be created,
 *         otherwise the a pop-up menu
 */
static GtkMenu *
search_list_get_popup (GtkTreeModel *tm,
                       struct SearchTab *tab,
                       GtkTreeIter *iter)
{
  GtkMenu *menu;
  GtkWidget *child;
  GtkTreePath *path;
  struct SearchResult *sr;
  struct SearchListPopupContext *spc;
  int is_directory = GNUNET_NO;
  struct UriPopulationContext uri_pop_ctx;

  spc = GNUNET_new (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_SEARCH_RESULT, &sr, -1);
  if (NULL == sr)
  {
    GNUNET_break (0);
    return NULL;
  }
  if (NULL != sr->meta)
    is_directory = GNUNET_FS_meta_data_test_for_directory (sr->meta);

  spc->sr = sr;
  menu = GTK_MENU (gtk_menu_new ());
  if ((NULL == sr->download) && (NULL != sr->uri) &&
      ((GNUNET_FS_uri_test_chk (sr->uri) || GNUNET_FS_uri_test_loc (sr->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 == GNUNET_YES)
    {
      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);
  }

  /* Insert a separator */
  child = gtk_separator_menu_item_new ();
  gtk_widget_show (child);
  gtk_menu_shell_append (GTK_MENU_SHELL (menu), child);

  /* check for embedded URIs */
  uri_pop_ctx.counter = 0;
  uri_pop_ctx.menu = menu;
  find_embedded_uris (sr->meta, &populate_popup_with_uri_items, &uri_pop_ctx);
  if (0 < uri_pop_ctx.counter)
  {
    /* Insert another separator */
    child = gtk_separator_menu_item_new ();
    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 != sr->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);
  return menu;
}


/**
 * 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;
  GtkMenu *menu;

  sel = gtk_tree_view_get_selection (tv);
  if (! gtk_tree_selection_get_selected (sel, &tm, &iter))
    return FALSE; /* nothing selected */
  menu = search_list_get_popup (tm, tab, &iter);
  if (NULL == menu)
    return FALSE;
  gtk_menu_popup_at_widget (menu,
                            widget,
                            GDK_GRAVITY_CENTER,
                            GDK_GRAVITY_CENTER,
                            NULL);
  return TRUE;
}


/**
 * 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;
  GtkMenu *menu;

  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);
  menu = search_list_get_popup (tm, tab, &iter);
  if (NULL == menu)
    return FALSE;
  gtk_menu_popup_at_pointer (menu, event);
  return FALSE;
}


/**
 * We got a right-click on the downloads 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 UNUSED
 * @return FALSE to propagate the event further,
 *         TRUE to stop the propagation
 */
gboolean
GNUNET_GTK_download_frame_button_press_event_cb (GtkWidget *widget,
                                                 GdkEvent *event,
                                                 gpointer user_data)
{
  GtkTreeView *tv = GTK_TREE_VIEW (widget);
  GdkEventButton *event_button = (GdkEventButton *) event;
  GtkTreeModel *tm;
  GtkTreePath *path;
  GtkTreeIter iter;
  GtkMenu *menu;

  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);
  menu = search_list_get_popup (tm, NULL, &iter);
  if (NULL == menu)
    return FALSE;
  gtk_menu_popup_at_pointer (menu, event);
  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 ();
  GtkTreeIter iter;
  int index;
  int i;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Closing search tab `%s'\n",
              tab->query_txt);
  if (NULL != tab->parent)
  {
    /* not a top-level search (namespace update search), do not close
       tab here! */
    tab->parent->tab = NULL;
    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;
  if (gtk_tree_model_iter_children (GTK_TREE_MODEL (tab->ts), &iter, NULL))
  {
    do
    {
      struct SearchResult *sr;

      gtk_tree_model_get (GTK_TREE_MODEL (tab->ts),
                          &iter,
                          SEARCH_TAB_MC_SEARCH_RESULT,
                          &sr,
                          -1);
      if (NULL != sr)
        sr->tab = NULL;
    } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (tab->ts), &iter));
  }
  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))
  {
    struct GNUNET_GTK_MainWindowContext *mctx =
      GNUNET_FS_GTK_get_main_context ();
    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;

    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;
  }
}


/**
 * Close the 'uri_tab'.
 */
void
GNUNET_FS_GTK_close_uri_tab_ ()
{
  if (NULL != uri_tab)
    close_search_tab (uri_tab);
}


/**
 * Handle the case where an active download lost its
 * search parent; need to remember that the respective
 * tab and row reference no longer exist.
 *
 * @param de download where the parent (i.e. search) was lost
 */
static void
download_lost_parent (struct DownloadEntry *de)
{
  GNUNET_log (
    GNUNET_ERROR_TYPE_DEBUG,
    "Download %p lost search parent; removing from search result %p list.\n",
    de,
    de->sr);
  if (NULL != de->sr->rr)
  {
    gtk_tree_row_reference_free (de->sr->rr);
    de->sr->rr = NULL;
  }
  de->sr = NULL;
}


/**
 * 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
remove_results_in_subtree (GtkTreeModel *tm, GtkTreeIter *iter)
{
  GtkTreeIter child;
  struct SearchResult *sr;

  if (gtk_tree_model_iter_children (tm, &child, iter))
  {
    do
    {
      gtk_tree_model_get (tm, &child, SEARCH_TAB_MC_SEARCH_RESULT, &sr, -1);
      remove_results_in_subtree (tm, &child);
      gtk_tree_row_reference_free (sr->rr);
      sr->rr = NULL;
      if (NULL != sr->probe)
      {
        GNUNET_FS_probe_stop (sr->probe);
        sr->probe = NULL;
        GNUNET_CONTAINER_DLL_remove (pl_head, pl_tail, 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);
      if (NULL != sr->download)
      {
        /* 'sr' still referenced from download; do not free */
        sr->tab = NULL;
        continue;
      }
      if (NULL != sr->uri)
      {
        GNUNET_FS_uri_destroy (sr->uri);
        sr->uri = NULL;
      }
      if (NULL != sr->meta)
      {
        GNUNET_CONTAINER_meta_data_destroy (sr->meta);
        sr->meta = NULL;
      }
      GNUNET_free (sr);
    } while (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;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Freeing a search result SR=%p\n", sr);
  if (NULL == sr)
  {
    GNUNET_break (0);
    return;
  }
  if ((NULL != sr->rr) &&
      (NULL != (tm = gtk_tree_row_reference_get_model (sr->rr))) &&
      (NULL != (tp = gtk_tree_row_reference_get_path (sr->rr))))
  {
    if (! gtk_tree_model_get_iter (tm, &iter, tp))
    {
      GNUNET_break (0);
      gtk_tree_path_free (tp);
      return;
    }
    /* get ready for later removal of the tree */
    gtk_tree_store_set (GTK_TREE_STORE (tm),
                        &iter,
                        SEARCH_TAB_MC_METADATA,
                        NULL,
                        SEARCH_TAB_MC_URI,
                        NULL,
                        SEARCH_TAB_MC_SEARCH_RESULT,
                        NULL,
                        -1);
    gtk_tree_path_free (tp);
    gtk_tree_row_reference_free (sr->rr);
    sr->rr = NULL;
    remove_results_in_subtree (tm, &iter);
    GNUNET_FS_GTK_remove_treestore_subtree (GTK_TREE_STORE (tm), &iter);
  }
  if (NULL != sr->probe)
  {
    GNUNET_FS_probe_stop (sr->probe);
    sr->probe = NULL;
    GNUNET_CONTAINER_DLL_remove (pl_head, pl_tail, sr);
  }
  if (NULL != sr->download)
  {
    /* 'sr' still referenced from download; do not free */
    sr->tab = NULL;
    return;
  }
  if (NULL != sr->uri)
  {
    GNUNET_FS_uri_destroy (sr->uri);
    sr->uri = NULL;
  }
  if (NULL != sr->meta)
  {
    GNUNET_CONTAINER_meta_data_destroy (sr->meta);
    sr->meta = NULL;
  }
  GNUNET_free (sr);
}


/**
 * 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 unused
 */
void
GNUNET_FS_GTK_search_treeview_cursor_changed (GtkTreeView *tv,
                                              gpointer user_data)
{
  GtkTreeSelection *sel;
  GtkTreeModel *model;
  GtkTreeIter iter;
  GdkPixbuf *pixbuf;
  GtkTreePath *selpath;
  struct SearchResult *sr;
  struct GNUNET_GTK_MainWindowContext *mctx = GNUNET_FS_GTK_get_main_context ();

  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;
  }
  pixbuf = NULL;
  gtk_tree_model_get (model,
                      &iter,
                      SEARCH_TAB_MC_PREVIEW,
                      &pixbuf,
                      SEARCH_TAB_MC_SEARCH_RESULT,
                      &sr,
                      -1);
  if (NULL == sr)
    return;
  selpath = gtk_tree_model_get_path (model, &iter);
  if ((NULL == current_selected_search_result) ||
      (0 != gtk_tree_path_compare (selpath, current_selected_search_result)))
  {
    if ((NULL == sr->download) && (NULL != sr->uri) &&
        ((GNUNET_FS_uri_test_chk (sr->uri) ||
          GNUNET_FS_uri_test_loc (sr->uri))))
    {
      char *download_directory;
      char *filename;
      int anonymity;
      int is_directory = GNUNET_NO;

      /* 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 (sr->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 == GNUNET_YES);

      /* 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 (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 != sr->meta)
    GNUNET_CONTAINER_meta_data_iterate (sr->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 (NULL != current_selected_search_result)
    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;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Stopping search `%s'\n",
              tab->query_txt);
  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)
        continue;
      if ((NULL != sr->download) && (GNUNET_YES == sr->download->is_done))
      {
        /* 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 (gtk_tree_model_iter_next (tm, &child));
  }
  return ret;
}


/**
 * User clicked on the 'clean' button of the downloads tab.
 * Stop completed downloads (or those that failed).  Should
 * iterate over the underlying tree store and stop all
 * completed entries.
 *
 * @param button the button pressed by the user
 * @param user_data
 */
void
GNUNET_FS_GTK_downloads_clear_button_clicked (GtkButton *button,
                                              gpointer user_data)
{
  struct SearchResult *sr;
  GtkTreeModel *tm;
  GtkTreeIter iter;

  tm = GTK_TREE_MODEL (downloads_treestore);
  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 (NULL == sr)
    {
      GNUNET_break (0);
      continue;
    }
    if ((NULL != sr->download) && (GNUNET_YES == sr->download->is_done))
    {
      /* got a finished download, stop it */
      GNUNET_FS_download_stop (sr->download->dc, GNUNET_YES);
      if (! gtk_tree_model_get_iter_first (tm, &iter))
        return;
    }
    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,
#if HAVE_EXTRACTOR_H
                                                        EXTRACTOR_METATYPE_FORMAT,
#endif
                                                        -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;
  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 (NULL == sr)
  {
    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);
  if (NULL != sr->meta)
  {
    GNUNET_CONTAINER_meta_data_destroy (sr->meta);
    sr->meta = NULL;
  }
  sr->meta = GNUNET_CONTAINER_meta_data_duplicate (meta);
  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,
                      sr->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_us /
                                  GNUNET_FS_PROBE_UPDATE_FREQUENCY.rel_value_us)
                        : -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 (NULL != sr->download)
  {
    get_download_list_entry (sr->download, &iter);
    gtk_tree_store_set (downloads_treestore,
                        &iter,
                        SEARCH_TAB_MC_METADATA,
                        sr->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_us /
                              GNUNET_FS_PROBE_UPDATE_FREQUENCY.rel_value_us)
                          : -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 (NULL != pixbuf)
    g_object_unref (pixbuf);
  GNUNET_free (desc);
  GNUNET_free (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);
}


/**
 * If called, sets the `gboolean` pointer in @a cls to TRUE.
 *
 * @param cls pointer to a `gboolean`
 * @param sks_uri a URI
 * @return 1 (abort iteration)
 */
static int
see_if_there_are_any_uris (void *cls, const struct GNUNET_FS_Uri *sks_uri)
{
  gboolean *gb = cls;

  *gb = TRUE;
  return 1;
}


/**
 * 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, NULL if we are only in the download tab
 * @param anonymity anonymity level to use for probes for this result
 * @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)
 */
struct SearchResult *
GNUNET_GTK_add_search_result (struct SearchTab *tab,
                              uint32_t anonymity,
                              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;
  gboolean show_uri_association;

  show_uri_association = FALSE;
  if (NULL == uri)
  {
    /* opened directory file */
    fsize = 0;
    status_colour = "gray";
    mime = GNUNET_strdup (GNUNET_FS_DIRECTORY_MIME);
    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
    {
      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);
  find_embedded_uris (meta, &see_if_there_are_any_uris, &show_uri_association);

  sr = GNUNET_new (struct SearchResult);
  sr->result = result;
  if ( (NULL == result) &&
       (NULL != uri) )
  {
    sr->probe = GNUNET_FS_probe (GNUNET_FS_GTK_get_fs_handle (),
                                 uri,
                                 meta,
                                 sr,
                                 anonymity);
    GNUNET_CONTAINER_DLL_insert (pl_head, pl_tail, sr);
  }
  sr->tab = tab;
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Allocated a search result SR=%p\n", sr);
  if (NULL != parent_rr)
  {
    /* 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;
    if (NULL != tab)
      ts = tab->ts;
    else
      ts = downloads_treestore;
  }
  sr->uri = (uri == NULL) ? NULL : GNUNET_FS_uri_dup (uri);
  sr->meta = GNUNET_CONTAINER_meta_data_duplicate (meta);
  gtk_tree_store_insert_with_values (ts,
                                     &iter,
                                     pitr,
                                     G_MAXINT,
                                     SEARCH_TAB_MC_METADATA,
                                     sr->meta,
                                     SEARCH_TAB_MC_URI,
                                     sr->uri,
                                     SEARCH_TAB_MC_FILESIZE,
                                     fsize,
                                     SEARCH_TAB_MC_PREVIEW,
                                     pixbuf,
                                     SEARCH_TAB_MC_PERCENT_PROGRESS,
                                     0,
                                     SEARCH_TAB_MC_PERCENT_AVAILABILITY,
                                     (0 == fsize) ? 100 : 50,
                                     SEARCH_TAB_MC_UNKNOWN_AVAILABILITY,
                                     (0 == fsize) ? -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,
                                     SEARCH_TAB_MC_SHOW_NS_ASSOCIATION,
                                     show_uri_association,
                                     -1);
  if (NULL != pixbuf)
    g_object_unref (pixbuf);
  GNUNET_free (uris);
  GNUNET_free (desc);
  GNUNET_free (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) */
  if (NULL != tab)
  {
    while (NULL != tab->parent)
      tab = tab->parent->tab;
    tab->num_results++;
  }
  return sr;
}


/**
 * Sets downloaded name on an item referenced by @a rr
 * in a tree store @a ts to @a 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 (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 anonymity anonymity level to use for probes for this result
 * @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,
                       uint32_t anonymity,
                       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,
                                     anonymity,
                                     (parent != NULL) ? parent->rr : NULL,
                                     uri,
                                     meta,
                                     result,
                                     applicability_rank);
  update_search_label (tab);
  GNUNET_break (NULL != sr);
  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_new (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_new (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 anonymity anonymity level to use for probes
 * @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 (uint32_t anonymity,
                           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,
                                       anonymity,
                                       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;
  struct DownloadEntry *deep;
  struct SearchTab *tab;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Changing download DE=%p color to %s\n",
              de,
              color);
  deep = de;
  while (NULL != deep->pde)
    deep = deep->pde;
  tab = NULL;
  if ((NULL != deep->sr) && (NULL != (tab = deep->sr->tab)))
  {
    path = gtk_tree_row_reference_get_path (de->sr->rr);
    GNUNET_assert (
      gtk_tree_model_get_iter (GTK_TREE_MODEL (tab->ts), &iter, path));
    gtk_tree_path_free (path);
    gtk_tree_store_set (tab->ts, &iter, SEARCH_TAB_MC_STATUS_COLOUR, color, -1);
  }
  /* do the same update to the downloads tree store */
  get_download_list_entry (de, &iter);
  gtk_tree_store_set (downloads_treestore,
                      &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;
  struct DownloadEntry *deep;
  struct SearchTab *tab;

  deep = de;
  while (NULL != deep->pde)
    deep = deep->pde;
  tab = NULL;
  if ((NULL != deep->sr) && (NULL != (tab = deep->sr->tab)))
  {
    path = gtk_tree_row_reference_get_path (de->sr->rr);
    GNUNET_assert (
      gtk_tree_model_get_iter (GTK_TREE_MODEL (tab->ts), &iter, path));
    gtk_tree_path_free (path);
    gtk_tree_store_set (tab->ts, &iter, SEARCH_TAB_MC_STATUS_ICON, icon, -1);
  }
  /* do the same update to the downloads tree store */
  get_download_list_entry (de, &iter);
  gtk_tree_store_set (downloads_treestore,
                      &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;

  /**
   * Anonymity level to use for probes in this directory.
   */
  uint32_t anonymity;

  /**
   * Do we need to check if the given entry already exists to
   * avoid adding it twice?  Set to #GNUNET_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 meta 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 (@a 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 (GNUNET_YES == ade->check_duplicates)
  {
    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 (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 (gtk_tree_model_iter_next (tm, &iter));
    }
  }
  GNUNET_GTK_add_search_result (ade->tab,
                                ade->anonymity,
                                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 @a 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;
  GtkTreeIter diter;
  GtkTreePath *path;
  struct DownloadEntry *deep;
  struct SearchTab *tab;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Marking download progress for DE=%p, %llu/%llu, %llu@%llu depth=%u\n",
              de,
              (unsigned long long) completed,
              (unsigned long long) size,
              (unsigned long long) block_size,
              (unsigned long long) offset,
              depth);
  deep = de;
  while (NULL != deep->pde)
    deep = deep->pde;
  tab = NULL;
  if ((NULL != deep->sr) && (NULL != (tab = deep->sr->tab)))
  {
    path = gtk_tree_row_reference_get_path (de->sr->rr);
    GNUNET_assert (
      gtk_tree_model_get_iter (GTK_TREE_MODEL (tab->ts), &iter, path));
    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 (tab->ts,
                        &iter,
                        SEARCH_TAB_MC_PERCENT_PROGRESS,
                        (guint) ((size > 0) ? (100 * completed / size) : 100),
                        SEARCH_TAB_MC_UNKNOWN_AVAILABILITY,
                        (completed ? -1 : 0),
                        SEARCH_TAB_MC_COMPLETED,
                        completed,
                        -1);
  }
  get_download_list_entry (de, &diter);
  gtk_tree_store_set (downloads_treestore,
                      &diter,
                      SEARCH_TAB_MC_PERCENT_PROGRESS,
                      (guint) ((size > 0) ? (100 * completed / size) : 100),
                      SEARCH_TAB_MC_UNKNOWN_AVAILABILITY,
                      (completed ? -1 : 0),
                      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 = tab;
      ade.prr = de->sr->rr;
      ade.check_duplicates = GNUNET_NO;
      ade.anonymity = de->anonymity;
      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;
        if (NULL != tab)
          gtk_tree_store_set (tab->ts, &iter, SEARCH_TAB_MC_MIMETYPE, "", -1);
        gtk_tree_store_set (downloads_treestore,
                            &diter,
                            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 = 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;
  struct DownloadEntry *deep;
  struct SearchTab *tab;

  change_download_color (de, "red");
  de->is_done = GNUNET_YES;
  if (NULL == animation_error)
    animation_error = load_animation ("error");
  deep = de;
  while (NULL != deep->pde)
    deep = deep->pde;
  tab = NULL;
  if ((NULL != deep->sr) && (NULL != (tab = deep->sr->tab)))
  {
    path = gtk_tree_row_reference_get_path (de->sr->rr);
    GNUNET_assert (
      gtk_tree_model_get_iter (GTK_TREE_MODEL (tab->ts), &iter, path));
    gtk_tree_path_free (path);
    gtk_tree_store_set (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);
  }
  get_download_list_entry (de, &iter);
  gtk_tree_store_set (downloads_treestore,
                      &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;
  struct DownloadEntry *deep;
  struct SearchTab *tab;

  de->is_done = GNUNET_YES;
  change_download_color (de, "green");

  deep = de;
  while (NULL != deep->pde)
    deep = deep->pde;
  tab = NULL;
  if ((NULL != deep->sr) && (NULL != (tab = deep->sr->tab)))
  {
    path = gtk_tree_row_reference_get_path (de->sr->rr);
    GNUNET_assert (
      gtk_tree_model_get_iter (GTK_TREE_MODEL (tab->ts), &iter, path));
    gtk_tree_path_free (path);
    gtk_tree_store_set (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);
  }
  get_download_list_entry (de, &iter);
  gtk_tree_store_set (downloads_treestore,
                      &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 anonymity anonymity level for the download
 * @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 @a de if @a de was not NULL)
 */
static struct DownloadEntry *
setup_download (struct DownloadEntry *de,
                struct DownloadEntry *pde,
                struct SearchResult *sr,
                uint32_t anonymity,
                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;
  GtkTreeModel *tm;
  struct DownloadEntry *deep;

  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,
    (unsigned long long) completed,
    (unsigned long long) size,
    filename);
  GNUNET_assert (NULL != uri);
  if (NULL == de)
  {
    /* no existing download entry to build on, create a fresh one */
    de = GNUNET_new (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);
    }
  }
  deep = de;
  while (NULL != deep->pde)
    deep = deep->pde;
  if ((NULL == de->sr) && (NULL != pde) && (NULL != deep->sr->tab))
  {
    /* 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 (deep->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 */
    de->sr =
      GNUNET_GTK_add_search_result (NULL, anonymity, NULL, uri, meta, NULL, 0);
    GNUNET_FS_probe_stop (de->sr->probe);
    de->sr->probe = NULL;
    GNUNET_CONTAINER_DLL_remove (pl_head, pl_tail, de->sr);
    de->sr->download = de;
    path = gtk_tree_row_reference_get_path (de->sr->rr);
    tm = gtk_tree_row_reference_get_model (de->sr->rr);
    if (! gtk_tree_model_get_iter (tm, &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);
    tm = gtk_tree_row_reference_get_model (de->sr->rr);
    if (! gtk_tree_model_get_iter (tm, &iter, path))
    {
      GNUNET_break (0);
      gtk_tree_path_free (path);
      return de;
    }
    gtk_tree_model_get (tm, &iter, SEARCH_TAB_MC_METADATA, &meta, -1);
    de->is_directory = GNUNET_FS_meta_data_test_for_directory (meta);
  }
  if (NULL == de->rr)
    setup_download_list_entry (de, pde, de->sr);
  gtk_tree_path_free (path);
  gtk_tree_store_set (GTK_TREE_STORE (tm),
                      &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_UNKNOWN_AVAILABILITY,
                      -1,
                      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);
  /* also update downloads tab */
  get_download_list_entry (de, &iter);
  gtk_tree_store_set (downloads_treestore,
                      &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_UNKNOWN_AVAILABILITY,
                      -1,
                      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,
                      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 (NULL != pe->uri)
  {
    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_new (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_new (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
 * @param 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
 * @param 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 generate
 * an appropriate menu.
 *
 * @param tm tree model underlying the tree view where the event happened
 * @param tab tab where the event happened
 * @param iter selected element in @a tm for the popup
 * @return NULL if no menu could be created,
 *         otherwise the pop-up menu
 */
static GtkMenu *
publish_list_get_popup (GtkTreeModel *tm,
                        struct PublishTab *tab,
                        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 NULL;
  }

  ppc = GNUNET_new (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);
  return menu;
}


/**
 * 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;
  GtkMenu *menu;

  sel = gtk_tree_view_get_selection (tv);
  if (! gtk_tree_selection_get_selected (sel, &tm, &iter))
    return FALSE; /* nothing selected */
  menu = publish_list_get_popup (tm, tab, &iter);
  if (NULL == menu)
    return FALSE;
  gtk_menu_popup_at_widget (menu,
                            widget,
                            GDK_GRAVITY_CENTER,
                            GDK_GRAVITY_CENTER,
                            NULL);
  return TRUE;
}


/**
 * 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;
  GtkMenu *menu;

  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);
  menu = publish_list_get_popup (tm, tab, &iter);
  if (NULL == menu)
    return FALSE;
  gtk_menu_popup_at_pointer (menu, event);
  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 `struct GNUNET_FS_ProgressInfo`.
 */
void *
GNUNET_GTK_fs_event_handler (void *cls,
                             const struct GNUNET_FS_ProgressInfo *info)
{
  void *ret;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got FS event %d\n", info->status);
  GNUNET_FS_GTK_set_fs_handle (info->fsh);
  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_PROGRESS_DIRECTORY:
    mark_publish_progress (info->value.publish.cctx,
                           info->value.publish.specifics.progress_directory
                             .total,
                           info->value.publish.specifics.progress_directory
                             .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.anonymity,
                           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.anonymity,
                          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.anonymity,
                             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);
    GNUNET_break (NULL != ret);
    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.anonymity,
                                  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:
    /* info->value.unindex.cctx is NULL if the unindexing was
       triggered by a *failed* publishing operation; in this case
       we don't do anything in the GUI but just clean up when
       the operation is complete (or fails) */
    if (NULL != info->value.unindex.cctx)
      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:
    if (NULL != info->value.unindex.cctx)
      GNUNET_FS_GTK_unindex_handle_error_ (info->value.unindex.cctx,
                                           info->value.unindex.specifics.error
                                             .message);
    else
      GNUNET_FS_unindex_stop (info->value.unindex.uc);
    return info->value.unindex.cctx;
  case GNUNET_FS_STATUS_UNINDEX_COMPLETED:
    if (NULL != info->value.unindex.cctx)
      GNUNET_FS_GTK_unindex_handle_completed_ (info->value.unindex.cctx);
    else
      GNUNET_FS_unindex_stop (info->value.unindex.uc);
    return info->value.unindex.cctx;
  case GNUNET_FS_STATUS_UNINDEX_STOPPED:
    if (NULL != info->value.unindex.cctx)
      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 */
