/*
     This file is part of GNUnet
     Copyright (C) 2005-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_publish-dialog.c
 * @author Christian Grothoff
 */
#include "gnunet-fs-gtk.h"
#include "gnunet-fs-gtk_common.h"
#include "gnunet-fs-gtk_publish-edit-dialog.h"
#include <gnunet/gnunet_fs_service.h>
#include <gnunet/gnunet_identity_service.h>
#include <gnunet/gnunet_util_lib.h>

#define MARKER_DIR_FILE_SIZE "-"

/**
 * Be very verbose when reporting progress (usually bad as it takes more time
 * to display this than to make progress).
 */
#define VERBOSE_PROGRESS GNUNET_GTK_EXTRA_LOGGING

/**
 * Columns in the publish model.
 */
enum PUBLISH_ModelColumns
{
  /**
   * A gchararray.
   */
  PUBLISH_MC_FILESIZE = 0,

  /**
   * A gboolean.
   */
  PUBLISH_MC_DO_INDEX = 1,

  /**
   * A gchararray.
   */
  PUBLISH_MC_FILENAME = 2,

  /**
   * A guint.
   */
  PUBLISH_MC_ANONYMITY_LEVEL = 3,

  /**
   * A guint.
   */
  PUBLISH_MC_PRIORITY = 4,

  /**
   * A gpointer.
   */
  PUBLISH_MC_FILE_INFORMATION_STRUCT = 5,

  /**
   * A guint64.
   */
  PUBLISH_MC_EXPIRATION_TIME_ABSOLUTE = 6,

  /**
   * A guint.
   */
  PUBLISH_MC_REPLICATION_LEVEL = 7
};

/**
 * Columns in the identifiers model.
 */
enum IDENTIFIERS_ModelColumns
{
  /**
   * A gchararray.
   */
  IDENTIFIERS_MC_ID = 0,

  /**
   * A gchararray.
   */
  IDENTIFIERS_MC_UPDATE_ID = 1,

  /**
   * A gchararray.
   */
  IDENTIFIERS_MC_DESCRIPTION = 2,

  /**
   * A gchararray.
   */
  IDENTIFIERS_MC_PATH_TO_ORIGINAL = 3,

  /**
   * A gchararray.
   */
  IDENTIFIERS_MC_ID_MARKED_UP = 4
};

/**
 * The columns in the "GNUNET_GTK_master_publish_dialog_ego_liststore"
 */
enum EGO_ModelColumns
{

  /**
   * A gchararray.
   */
  EGO_MC_NAME = 0,

  /**
   * A 'struct GNUNET_IDENTIFIER_Ego'.
   */
  EGO_MC_EGO = 1

};

/**
 * Context we create when we are scanning a directory.
 */
struct AddDirClientContext;

/**
 * Main handle of the dialog for a publish operation.
 */
struct MainPublishingDialogContext
{

  /**
   * Main builder for the publishing dialog.
   */
  GtkBuilder *builder;

  /**
   * Connection to the identity service.
   */
  struct GNUNET_IDENTITY_Handle *identity;

  /**
   * Handle to the main window of the publishing dialog.
   */
  GtkWindow *master_pubdialog;

  /**
   * Tree view listing files to be published.
   */
  GtkTreeView *file_info_treeview;

  /**
   * Selected file in the 'file_info_treeview'
   */
  GtkTreeSelection *file_info_selection;

  /**
   * Model with the list of files to be shared.
   */
  GtkTreeModel *file_info_treemodel;

  /**
   * Button to move selected file upwards
   */
  GtkWidget *up_button;

  /**
   * Button to move selected file downwards
   */
  GtkWidget *down_button;

  /**
   * Button to move selected file left (make sibling of current parent)
   */
  GtkWidget *left_button;

  /**
   * Button to move selected file right (make child of predecessor)
   */
  GtkWidget *right_button;

  /**
   * Button to delete selected file from the list
   */
  GtkWidget *delete_button;

  /**
   * Button to edit meta data of the selected file
   */
  GtkWidget *edit_button;

  /**
   * Checkbox that enables/diables global namespace publication.
   */
  GtkWidget *global_checkbox;

  /**
   * Checkbox that enables/diables private namespace publication.
   */
  GtkWidget *own_checkbox;

  /**
   * Treeview that shows previously-used identifiers and possible update IDs.
   */
  GtkTreeView *identifiers_treeview;

  /**
   * Model of identifiers_treeview.
   */
  GtkTreeModel *identifiers_treemodel;

  /**
   * Selection of identifiers_treeview.
   */
  GtkTreeSelection *identifiers_selection;

  /**
   * Entry that allows users to specify the identifier.
   */
  GtkWidget *identifier_entry;

  /**
   * Checkbox that enables/diables updateable private ns publications.
   */
  GtkWidget *updateable_checkbox;

  /**
   * Entry that allows users to specify the update identifier.
   */
  GtkWidget *update_id_entry;

  /**
   * VBox into which ns-related widgets are packed.
   */
  GtkWidget *own_vbox;

  /**
   * HBox into which updateability-related widgets are packed.
   */
  GtkWidget *update_id_hbox;

  /**
   * Name of the default namespace. May be NULL.
   */
  gchar *ns_name;

  /**
   * Default namespace. May be NULL.
   */
  struct GNUNET_IDENTITY_Ego *ns;

  /**
   * Stores the value that was in identifier_entry previously.
   */
  gchar *previous_id;

  /**
   * The top-level paned widget.
   */
  GtkWidget *vpaned;

  /**
   * The frame that is packed at the bottom of vpaned.
   */
  GtkWidget *bottom_frame;

  /**
   * Scrolled window into which identifiers_treeview is packed.
   */
  GtkWidget *identifiers_scrolled;

  /**
   * Expander that controls identifiers_treeview visibility.
   */
  GtkWidget *identifiers_expander;

  /**
   * Button to publish all files from the dialog
   */
  GtkWidget *execute_button;

  /**
   * Button to abort the publishing operation
   */
  GtkWidget *cancel_button;

  /**
   * Builder for the open directory dialog (non-NULL while the dialog is open)
   */
  GtkBuilder *open_directory_builder;

  /**
   * Builder for the open file dialog (non-NULL while the dialog is open)
   */
  GtkBuilder *open_file_builder;

  /**
   * Head of linked list of active open-directory operations.
   */
  struct AddDirClientContext *adddir_head;

  /**
   * Tail of linked list of active open-directory operations.
   */
  struct AddDirClientContext *adddir_tail;
};

/**
 * Context we create when we are scanning a directory.
 */
struct AddDirClientContext
{
  /**
   * This is a doubly-linked list.
   */
  struct AddDirClientContext *next;

  /**
   * This is a doubly-linked list.
   */
  struct AddDirClientContext *prev;

  /**
   * Handle of the master publish window.
   */
  struct MainPublishingDialogContext *ctx;

  /**
   * Builder for the progress dialog that is displayed during the scan.
   */
  GtkBuilder *progress_dialog_builder;

  /**
   * The progress dialog itself.
   */
  GtkWidget *progress_dialog;

  /**
   * The progress bar of the progress dialog.
   */
  GtkProgressBar *progress_dialog_bar;

  /**
   * Text view in the progress dialog (for error messages).
   */
  GtkTextView *progress_dialog_textview;

  /**
   * Text buffer of the text view in the progress dialog.
   */
  GtkTextBuffer *progress_dialog_textbuffer;

  /**
   * Adjustment (for scrolling) of the text view in the progress dialog.
   */
  GtkAdjustment *textview_vertical_adjustment;

  /**
   * Handle to the active directory scanning operation.
   */
  struct GNUNET_FS_DirScanner *ds;

  /**
   * Task scheduled to stop the scanner on errors.
   */
  struct GNUNET_SCHEDULER_Task *kill_task;

  /**
   * Default options to use for sharing when adding files during the scan.
   */
  struct GNUNET_FS_BlockOptions directory_scan_bo;

  /**
   * Default "index" option to use for sharing when adding files during the
   * scan.
   */
  int directory_scan_do_index;

  /**
   * Number of files that have had their meta data extracted (once done==total
   * we're finished processing).
   */
  unsigned int done;

  /**
   * Total number of files that we have found in the directory structure and
   * that will need to be processed.
   */
  unsigned int total;
};

static void
clear_keywords_from_tm (struct MainPublishingDialogContext *ctx);

/* ************************ editing operations inside the master dialog ***** */

/**
 * Check if two GtkTreeIters refer to the same element.
 *
 * @param tm tree model of the iterators
 * @param i1 first iterator
 * @param i2 second iterator
 * @return #GNUNET_YES if they are equal
 */
static int
gtk_tree_iter_equals (GtkTreeModel *tm, GtkTreeIter *i1, GtkTreeIter *i2)
{
  GtkTreePath *p1;
  GtkTreePath *p2;
  int ret;

  p1 = gtk_tree_model_get_path (tm, i1);
  p2 = gtk_tree_model_get_path (tm, i2);
  ret = gtk_tree_path_compare (p1, p2);
  gtk_tree_path_free (p1);
  gtk_tree_path_free (p2);
  return (0 == ret) ? GNUNET_YES : GNUNET_NO;
}


/**
 * Update selectivity of execute/cancel buttons in the master dialog.
 *
 * @param ctx master dialog to update selectivity for
 */
static void
update_selectivity_execute_cancel (struct MainPublishingDialogContext *ctx)
{
  gboolean pub_in_global;
  gboolean pub_in_own;
  gboolean updateable;
  const gchar *ns_id;
  GtkTreeIter iter;

  pub_in_global =
    gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ctx->global_checkbox));
  pub_in_own =
    gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ctx->own_checkbox));
  updateable =
    gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ctx->updateable_checkbox));
  gtk_widget_set_sensitive (ctx->update_id_hbox, updateable);
  gtk_widget_set_sensitive (ctx->own_vbox, pub_in_own);

  ns_id = gtk_entry_get_text (GTK_ENTRY (ctx->identifier_entry));

  /* Don't let the user close the dialog until all scanners are finished and
     their windows are closed */
  if ((gtk_tree_model_get_iter_first (ctx->file_info_treemodel, &iter)) &&
      (pub_in_global || pub_in_own) &&
      ((! pub_in_own) ||
       ((NULL != ctx->ns) && (NULL != ns_id) && ('\0' != ns_id[0]))) &&
      (NULL == ctx->adddir_head))
    gtk_widget_set_sensitive (ctx->execute_button, TRUE);
  else
    gtk_widget_set_sensitive (ctx->execute_button, FALSE);

  /* if an 'edit' operation is open, don't even allow "cancel" */
  if (ctx->adddir_head == NULL)
    gtk_widget_set_sensitive (ctx->cancel_button, TRUE);
  else
    gtk_widget_set_sensitive (ctx->cancel_button, FALSE);
}


/**
 * Update selectivity of up/down/left/right buttons in the master dialog.
 *
 * @param ctx master dialog to update selectivity for
 */
static void
update_selectivity_edit (struct MainPublishingDialogContext *ctx)
{
  GtkTreeIter iter;
  GtkTreeIter parent;
  GtkTreeIter pred;
  int is_dir;
  struct GNUNET_FS_FileInformation *fip;

  if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter))
  {
    gtk_widget_set_sensitive (ctx->up_button, FALSE);
    gtk_widget_set_sensitive (ctx->down_button, FALSE);
    gtk_widget_set_sensitive (ctx->left_button, FALSE);
    gtk_widget_set_sensitive (ctx->right_button, FALSE);
    gtk_widget_set_sensitive (ctx->delete_button, FALSE);
    gtk_widget_set_sensitive (ctx->edit_button, FALSE);
    return;
  }
  gtk_widget_set_sensitive (ctx->delete_button, TRUE);
  gtk_widget_set_sensitive (ctx->edit_button, TRUE);

  /* figure out which move operations are currently legal */
  GNUNET_assert (
    gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter));
  if (gtk_tree_model_iter_next (ctx->file_info_treemodel, &iter))
    gtk_widget_set_sensitive (ctx->down_button, TRUE);
  else
    gtk_widget_set_sensitive (ctx->down_button, FALSE);
  GNUNET_assert (
    gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter));
  if (gtk_tree_model_iter_parent (ctx->file_info_treemodel, &parent, &iter))
  {
    gtk_widget_set_sensitive (ctx->left_button, TRUE);
    GNUNET_assert (
      gtk_tree_model_iter_children (ctx->file_info_treemodel, &pred, &parent));
  }
  else
  {
    gtk_widget_set_sensitive (ctx->left_button, FALSE);
    GNUNET_assert (
      gtk_tree_model_get_iter_first (ctx->file_info_treemodel, &pred));
  }
  /* iterate over 'next' of pred to find out if our
   * predecessor is a directory! */
  is_dir = GNUNET_SYSERR;
  while (GNUNET_YES !=
         gtk_tree_iter_equals (ctx->file_info_treemodel, &pred, &iter))
  {
    gtk_tree_model_get (ctx->file_info_treemodel,
                        &pred,
                        PUBLISH_MC_FILE_INFORMATION_STRUCT,
                        &fip,
                        -1);
    is_dir = GNUNET_FS_file_information_is_directory (fip);
    GNUNET_assert (gtk_tree_model_iter_next (ctx->file_info_treemodel, &pred));
  }
  if (GNUNET_YES == is_dir)
    gtk_widget_set_sensitive (ctx->right_button, TRUE);
  else
    gtk_widget_set_sensitive (ctx->right_button, FALSE);
  if (GNUNET_SYSERR != is_dir)
    gtk_widget_set_sensitive (ctx->up_button, TRUE);
  else
    gtk_widget_set_sensitive (ctx->up_button, FALSE);
}


/**
 * The selection in the file list tree view changed; update the button
 * sensitivity.
 *
 * @param ts the changed selection
 * @param user_data master publishing dialog context of our window
 */
void
GNUNET_GTK_master_publish_dialog_file_informatino_treeview_selection_changed_cb (
  GtkTreeSelection *ts,
  gpointer user_data)
{
  struct MainPublishingDialogContext *ctx = user_data;

  update_selectivity_edit (ctx);
}


/**
 * Add an empty directory to the tree model.
 *
 * @param ctx master publishing dialog context of our window
 * @param name name for the directory
 * @param bo block options
 * @param iter parent entry, or NULL for top-level addition
 * @param pos iterator to set to the location of the new element
 */
static void
create_dir_at_iter (struct MainPublishingDialogContext *ctx,
                    const char *name,
                    const struct GNUNET_FS_BlockOptions *bo,
                    GtkTreeIter *iter,
                    GtkTreeIter *pos)
{
  struct GNUNET_FS_FileInformation *fi;
  GtkTreeRowReference *row_reference;
  GtkTreePath *path;
  struct GNUNET_FS_MetaData *meta;

  meta = GNUNET_FS_meta_data_create ();
  GNUNET_FS_meta_data_make_directory (meta);
  GNUNET_FS_meta_data_insert (meta,
                              "<gnunet-gtk>",
                              EXTRACTOR_METATYPE_GNUNET_ORIGINAL_FILENAME,
                              EXTRACTOR_METAFORMAT_UTF8,
                              "text/plain",
                              name,
                              strlen (name) + 1);
  gtk_tree_store_insert_before (GTK_TREE_STORE (ctx->file_info_treemodel),
                                pos,
                                iter,
                                NULL);
  path = gtk_tree_model_get_path (ctx->file_info_treemodel, pos);
  row_reference = gtk_tree_row_reference_new (ctx->file_info_treemodel, path);
  gtk_tree_path_free (path);
  fi = GNUNET_FS_file_information_create_empty_directory (
    GNUNET_FS_GTK_get_fs_handle (),
    row_reference,
    NULL,
    meta,
    bo,
    name);
  GNUNET_FS_meta_data_destroy (meta);
  gtk_tree_store_set (GTK_TREE_STORE (ctx->file_info_treemodel),
                      pos,
                      PUBLISH_MC_FILESIZE,
                      MARKER_DIR_FILE_SIZE,
                      PUBLISH_MC_DO_INDEX,
                      (gboolean) GNUNET_NO,
                      PUBLISH_MC_FILENAME,
                      name,
                      PUBLISH_MC_ANONYMITY_LEVEL,
                      (guint) bo->anonymity_level,
                      PUBLISH_MC_PRIORITY,
                      (guint) bo->content_priority,
                      PUBLISH_MC_FILE_INFORMATION_STRUCT,
                      fi,
                      PUBLISH_MC_EXPIRATION_TIME_ABSOLUTE,
                      (guint64) bo->expiration_time.abs_value_us,
                      PUBLISH_MC_REPLICATION_LEVEL,
                      (guint) bo->replication_level,
                      -1);
  update_selectivity_edit (ctx);
  update_selectivity_execute_cancel (ctx);
}


/**
 * Copy an entry in the tree from the 'old' position to the 'new'
 * position.  All of the fields are copied, plain pointers will be
 * aliased (model will thus be inconsistent until the subtree remover
 * is called on the 'old' entry).
 *
 * @param ctx main publishing context
 * @param tm tree model for the move operation
 * @param old old position (source of the copy operation)
 * @param newpos destination of the copy operation
 * @param dsel #GNUNET_YES for the top-level operation,
 *             #GNUNET_NO for the recursive calls; if #GNUNET_YES,
 *             we ensure that the tree view is expanded to cover
 *             the element; the element is also then selected
 */
static void
copy_entry (struct MainPublishingDialogContext *ctx,
            GtkTreeModel *tm,
            GtkTreeIter *old,
            GtkTreeIter *newpos,
            int dsel)
{
  GtkTreePath *path;
  GtkTreeIter child;
  GtkTreeRowReference *rr;

  /* first, move the data */
  {
    struct GNUNET_FS_FileInformation *fip;
    gint do_index;
    gchar *short_fn;
    guint anonymity_level;
    guint priority;
    guint replication_level;
    guint64 expiration_time_abs;
    char *fsf;

    gtk_tree_model_get (tm,
                        old,
                        PUBLISH_MC_FILESIZE,
                        &fsf,
                        PUBLISH_MC_DO_INDEX,
                        &do_index,
                        PUBLISH_MC_FILENAME,
                        &short_fn,
                        PUBLISH_MC_ANONYMITY_LEVEL,
                        &anonymity_level,
                        PUBLISH_MC_PRIORITY,
                        &priority,
                        PUBLISH_MC_FILE_INFORMATION_STRUCT,
                        &fip,
                        PUBLISH_MC_EXPIRATION_TIME_ABSOLUTE,
                        &expiration_time_abs,
                        PUBLISH_MC_REPLICATION_LEVEL,
                        &replication_level,
                        -1);
    gtk_tree_store_set (GTK_TREE_STORE (tm),
                        newpos,
                        PUBLISH_MC_FILESIZE,
                        fsf,
                        PUBLISH_MC_DO_INDEX,
                        do_index,
                        PUBLISH_MC_FILENAME,
                        short_fn,
                        PUBLISH_MC_ANONYMITY_LEVEL,
                        anonymity_level,
                        PUBLISH_MC_PRIORITY,
                        priority,
                        PUBLISH_MC_FILE_INFORMATION_STRUCT,
                        fip,
                        PUBLISH_MC_EXPIRATION_TIME_ABSOLUTE,
                        expiration_time_abs,
                        PUBLISH_MC_REPLICATION_LEVEL,
                        replication_level,
                        -1);
    g_free (short_fn);
    g_free (fsf);
  }

  /* remember our destination location if needed */
  if (dsel == GNUNET_YES)
  {
    path = gtk_tree_model_get_path (tm, newpos);
    rr = gtk_tree_row_reference_new (tm, path);
    gtk_tree_path_free (path);
  }
  else
  {
    rr = NULL;
  }

  /* recursively move children */
  if (gtk_tree_model_iter_children (tm, &child, old))
  {
    do
    {
      GtkTreeRowReference *crr;
      GtkTreeIter cnewpos;

      path = gtk_tree_model_get_path (tm, &child);
      crr = gtk_tree_row_reference_new (tm, path);
      gtk_tree_path_free (path);
      gtk_tree_store_insert_before (GTK_TREE_STORE (tm),
                                    &cnewpos,
                                    newpos,
                                    NULL);
      copy_entry (ctx, tm, &child, &cnewpos, GNUNET_NO);
      path = gtk_tree_row_reference_get_path (crr);
      gtk_tree_row_reference_free (crr);
      GNUNET_assert (gtk_tree_model_get_iter (tm, &child, path));
      gtk_tree_path_free (path);
    } while (gtk_tree_model_iter_next (tm, &child));
  }

  /* update selection, etc. if applicable */
  if (dsel == GNUNET_YES)
  {
    path = gtk_tree_row_reference_get_path (rr);
    gtk_tree_row_reference_free (rr);
    gtk_tree_view_expand_to_path (ctx->file_info_treeview, path);
    GNUNET_assert (gtk_tree_model_get_iter (tm, newpos, path));
    gtk_tree_path_free (path);
    gtk_tree_selection_select_iter (ctx->file_info_selection, newpos);
    update_selectivity_edit (ctx);
  }
}


/**
 * Called when global ns publication checkbox is toggled.
 * Adjusts execute/cancel button sensitivity.
 *
 * @param togglebutton button that was toggled
 * @param user_data context
 */
void
GNUNET_GTK_master_publish_dialog_global_checkbox_toggled_cb (
  GtkToggleButton *togglebutton,
  gpointer user_data)
{
  update_selectivity_execute_cancel (user_data);
}


/**
 * Called when private ns publication checkbox is toggled.
 * Adjusts execute/cancel button sensitivity.
 *
 * @param togglebutton button that was toggled
 * @param user_data context
 */
void
GNUNET_GTK_master_publish_dialog_own_checkbox_toggled_cb (
  GtkToggleButton *togglebutton,
  gpointer user_data)
{
  update_selectivity_execute_cancel (user_data);
}


/**
 * Called when updateability checkbox is toggled.
 * Adjusts execute/cancel button sensitivity.
 *
 * @param togglebutton button that was toggled
 * @param user_data context
 */
void
GNUNET_GTK_master_publish_dialog_updateable_checkbox_toggled_cb (
  GtkToggleButton *togglebutton,
  gpointer user_data)
{
  update_selectivity_execute_cancel (user_data);
}


/**
 * Generates an update id from a new id.
 *
 * @param new_text new id for which to generate update id.
 * @return new update id (free with g_free()).
 */
static gchar *
generate_update_id (const gchar *new_text)
{
  gchar *dash;
  gint64 num;
  gchar *new_update_id;
  gchar *copy_part;

  copy_part = g_strdup (new_text);
  dash = strrchr (copy_part, '-');
  if (! dash)
    num = 0;
  else
  {
    gchar *endptr;
    num = g_ascii_strtoll (&dash[1], &endptr, 10);
    if (((0 <= num) && (endptr == &dash[1])) || (G_MAXINT64 == num) ||
        ('\0' != endptr[0]))
      num = 0;
    else
      dash[0] = '\0';
  }
  if ('\0' != new_text[0])
    new_update_id =
      g_strdup_printf ("%s-%" G_GINT64_FORMAT, copy_part, num + 1);
  else
    new_update_id = g_strdup ("");
  g_free (copy_part);
  return new_update_id;
}


/**
 * Checks whether existing update id was generated or not.
 * Generates an update id from the previous id, then checks if
 * it matches the one that is currently specified.
 * If it does, then current update id was autogenerated.
 *
 * @param existing_update_id current update id
 * @param previous_id previous value of the identifier, from which
 *        current update id might have been generated
 * @return TRUE if existing_update_id was generated from previous_id,
 *         FALSE otherwise
 */
static gboolean
update_id_is_autofilled (const gchar *existing_update_id,
                         const gchar *previous_id)
{
  gboolean result;
  gchar *gen_update_id;

  result = TRUE;
  gen_update_id = generate_update_id (previous_id);
  if (0 != strcmp (gen_update_id, existing_update_id))
    result = FALSE;
  g_free (gen_update_id);
  return result;
}


/**
 * Generates a new update id and fills the entry, if necessary.
 * Stores new identifier as previous_id in the context struct.
 *
 * @param ctx context
 * @param new_text new identifier
 */
static void
maybe_change_update_id (struct MainPublishingDialogContext *ctx,
                        const gchar *new_text)
{
  const gchar *existing_update_id;
  gchar *new_update_id;

  new_update_id = NULL;
  existing_update_id = gtk_entry_get_text (GTK_ENTRY (ctx->update_id_entry));

  if (((NULL == ctx->previous_id) && ('\0' == existing_update_id[0])) ||
      ((NULL != ctx->previous_id) &&
       update_id_is_autofilled (existing_update_id, ctx->previous_id)))
    new_update_id = generate_update_id (new_text);
  if (NULL != new_update_id)
  {
    gtk_entry_set_text (GTK_ENTRY (ctx->update_id_entry), new_update_id);
    g_free (new_update_id);
  }
  g_free (ctx->previous_id);
  ctx->previous_id = g_strdup (new_text);
}


/**
 * Called when identifier entry contents are changed by anything.
 * Generates a new update id and fills the entry, if necessary.
 * Updates execute/cancel buttons sensitivity.
 *
 * @param widget the entry that was changed
 * @param user_data our `struct MainPublishingDialogContext`
 */
void
GNUNET_GTK_master_publish_dialog_identifier_entry_changed_cb (
  GtkWidget *widget,
  gpointer user_data)
{
  struct MainPublishingDialogContext *ctx = user_data;
  const gchar *new_text;

  new_text = gtk_entry_get_text (GTK_ENTRY (widget));
  if (NULL == new_text)
    return;
  maybe_change_update_id (ctx, new_text);
  update_selectivity_execute_cancel (ctx);
}


/**
 * The selection in the identifier tree view changed.
 * Copy text into identifier entry, or
 * adjust selection, if selected item is a clone.
 *
 * @param ts the changed selection
 * @param user_data master publishing dialog context of our window
 */
void
GNUNET_GTK_master_publish_dialog_previous_identifiers_treeview_selection_changed_cb (
  GtkTreeSelection *ts,
  gpointer user_data)
{
  struct MainPublishingDialogContext *ctx = user_data;
  GtkTreeIter iter;
  gchar *new_text;
  gchar *spath;
  GtkTreePath *path;

  if (! gtk_tree_selection_get_selected (ts, NULL, &iter))
    return;
  gtk_tree_model_get (ctx->identifiers_treemodel,
                      &iter,
                      IDENTIFIERS_MC_ID,
                      &new_text,
                      IDENTIFIERS_MC_PATH_TO_ORIGINAL,
                      &spath,
                      -1);
  path = NULL;
  if (spath)
    path = gtk_tree_path_new_from_string (spath);
  if (path)
  {
    gtk_tree_selection_select_path (ts, path);
    gtk_tree_path_free (path);
  }
  else if (new_text)
    gtk_entry_set_text (GTK_ENTRY (ctx->identifier_entry), new_text);
  g_free (new_text);
}


/**
 * User has clicked on the 'right' button to move files in the master
 * edit dialog tree view.  Execute the move.
 *
 * @param dummy the button
 * @param user_data master publishing dialog context of our window
 */
void
GNUNET_GTK_master_publish_dialog_right_button_clicked_cb (GtkWidget *dummy,
                                                          gpointer user_data)
{
  struct MainPublishingDialogContext *ctx = user_data;
  GtkTreeIter iter;
  GtkTreeIter parent;
  GtkTreeIter pred;
  GtkTreeIter prev;
  GtkTreeIter pos;

  if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter))
  {
    GNUNET_break (0);
    return;
  }
  if (gtk_tree_model_iter_parent (ctx->file_info_treemodel, &parent, &iter))
    GNUNET_assert (
      gtk_tree_model_iter_children (ctx->file_info_treemodel, &pred, &parent));
  else if (! gtk_tree_model_get_iter_first (ctx->file_info_treemodel, &pred))
  {
    GNUNET_break (0);
    return;
  }
  /* iterate over 'next' of pred to find out who our predecessor is! */
  memset (&prev, 0, sizeof (GtkTreeIter));
  while (GNUNET_YES !=
         gtk_tree_iter_equals (ctx->file_info_treemodel, &pred, &iter))
  {
    prev = pred;
    GNUNET_assert (gtk_tree_model_iter_next (ctx->file_info_treemodel, &pred));
  }
  gtk_tree_store_insert_before (GTK_TREE_STORE (ctx->file_info_treemodel),
                                &pos,
                                &prev,
                                NULL);
  if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter))
  {
    GNUNET_break (0);
    return;
  }
  copy_entry (ctx, ctx->file_info_treemodel, &iter, &pos, GNUNET_YES);
  GNUNET_FS_GTK_remove_treestore_subtree (GTK_TREE_STORE (
                                            ctx->file_info_treemodel),
                                          &iter);
}


/**
 * User has clicked on the 'left' button to move files in the master
 * edit dialog tree view.  Execute the move.
 *
 * @param dummy the button
 * @param user_data master publishing dialog context of our window
 */
void
GNUNET_GTK_master_publish_dialog_left_button_clicked_cb (GtkWidget *dummy,
                                                         gpointer user_data)
{
  struct MainPublishingDialogContext *ctx = user_data;
  GtkTreeIter iter;
  GtkTreeIter parent;
  GtkTreeIter pos;

  if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter))
  {
    GNUNET_break (0);
    return;
  }
  if (! gtk_tree_model_iter_parent (ctx->file_info_treemodel, &parent, &iter))
  {
    GNUNET_break (0);
    return;
  }
  gtk_tree_store_insert_after (GTK_TREE_STORE (ctx->file_info_treemodel),
                               &pos,
                               NULL,
                               &parent);
  if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter))
  {
    GNUNET_break (0);
    return;
  }
  copy_entry (ctx, ctx->file_info_treemodel, &iter, &pos, GNUNET_YES);
  GNUNET_FS_GTK_remove_treestore_subtree (GTK_TREE_STORE (
                                            ctx->file_info_treemodel),
                                          &iter);
}


/**
 * User has clicked on the 'up' button to move files in the master
 * edit dialog tree view.  Execute the move.
 *
 * @param dummy the button
 * @param user_data master publishing dialog context of our window
 */
void
GNUNET_GTK_master_publish_dialog_up_button_clicked_cb (GtkWidget *dummy,
                                                       gpointer user_data)
{
  struct MainPublishingDialogContext *ctx = user_data;
  GtkTreeIter iter;
  GtkTreeIter parent;
  GtkTreeIter pred;
  GtkTreeIter prev;
  GtkTreeIter *pprev;
  GtkTreeIter pos;

  if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter))
  {
    GNUNET_break (0);
    return;
  }
  if (gtk_tree_model_iter_parent (ctx->file_info_treemodel, &parent, &iter))
  {
    GNUNET_assert (
      TRUE ==
      gtk_tree_model_iter_children (ctx->file_info_treemodel, &pred, &parent));
    pprev = &parent;
  }
  else if (! gtk_tree_model_get_iter_first (ctx->file_info_treemodel, &pred))
  {
    GNUNET_break (0);
    return;
  }
  pprev = NULL;
  /* iterate over 'next' of pred to find out who our predecessor is! */
  while (GNUNET_YES !=
         gtk_tree_iter_equals (ctx->file_info_treemodel, &pred, &iter))
  {
    prev = pred;
    pprev = &prev;
    GNUNET_assert (gtk_tree_model_iter_next (ctx->file_info_treemodel, &pred));
  }
  gtk_tree_store_insert_before (GTK_TREE_STORE (ctx->file_info_treemodel),
                                &pos,
                                NULL,
                                pprev);
  if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter))
  {
    GNUNET_break (0);
    return;
  }
  copy_entry (ctx, ctx->file_info_treemodel, &iter, &pos, GNUNET_YES);
  GNUNET_FS_GTK_remove_treestore_subtree (GTK_TREE_STORE (
                                            ctx->file_info_treemodel),
                                          &iter);
}


/**
 * User has clicked on the 'down' button to move files in the master
 * edit dialog tree view.  Execute the move.
 *
 * @param dummy the button
 * @param user_data master publishing dialog context of our window
 */
void
GNUNET_GTK_master_publish_dialog_down_button_clicked_cb (GtkWidget *dummy,
                                                         gpointer user_data)
{
  struct MainPublishingDialogContext *ctx = user_data;
  GtkTreeIter iter;
  GtkTreeIter next;
  GtkTreeIter pos;

  if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter))
  {
    GNUNET_break (0);
    return;
  }
  if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &next))
  {
    GNUNET_break (0);
    return;
  }
  GNUNET_assert (gtk_tree_model_iter_next (ctx->file_info_treemodel, &next));
  gtk_tree_store_insert_after (GTK_TREE_STORE (ctx->file_info_treemodel),
                               &pos,
                               NULL,
                               &next);
  if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter))
  {
    GNUNET_break (0);
    return;
  }
  copy_entry (ctx, ctx->file_info_treemodel, &iter, &pos, GNUNET_YES);
  GNUNET_FS_GTK_remove_treestore_subtree (GTK_TREE_STORE (
                                            ctx->file_info_treemodel),
                                          &iter);
}


/**
 * User has clicked on the 'new' button to add an empty directory in the master
 * edit dialog tree view.  Add an empty directory.
 *
 * @param dummy the button
 * @param user_data master publishing dialog context of our window
 */
void
GNUNET_GTK_master_publish_dialog_new_button_clicked_cb (GtkWidget *dummy,
                                                        gpointer user_data)
{
  struct MainPublishingDialogContext *ctx = user_data;
  GtkTreeIter iter;
  GtkTreeIter pos;
  struct GNUNET_FS_BlockOptions bo;

  /* FIXME-FEATURE: consider opening a dialog to get anonymity,
     priority and expiration prior to calling this function (currently
     we use default values for those).  Or getting these values from
     the configuration. */
  bo.anonymity_level = 1;
  bo.content_priority = 1000;
  bo.expiration_time =
    GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_YEARS);
  bo.replication_level = 1;
  if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter))
    create_dir_at_iter (ctx, "unnamed/", &bo, NULL, &pos);
  else
    create_dir_at_iter (ctx, "unnamed/", &bo, &iter, &pos);
}


/**
 * Free row reference stored in the file information's
 * client-info pointer.
 *
 * @param cls always NULL
 * @param fi the file information that is being destroyed, unused
 * @param length length of the file, unused
 * @param meta meta data, unused
 * @param uri keyword URI, unused
 * @param bo publishing options, unused
 * @param do_index indexing option, unused
 * @param client_info pointer to the GtkTreeRowReference, freed
 * @return GNUNET_OK to traverse entire subtree
 */
static int
free_fi_row_reference (void *cls,
                       struct GNUNET_FS_FileInformation *fi,
                       uint64_t length,
                       struct GNUNET_FS_MetaData *meta,
                       struct GNUNET_FS_Uri **uri,
                       struct GNUNET_FS_BlockOptions *bo,
                       int *do_index,
                       void **client_info)
{
  GtkTreeRowReference *row = *client_info;

  if (NULL == row)
  {
    GNUNET_break (0);
    return GNUNET_OK;
  }
  gtk_tree_row_reference_free (row);
  *client_info = NULL;
  return GNUNET_OK;
}


/**
 * User has clicked on the 'delete' button to delete a file or directory in the
 * master edit dialog tree view.  Delete the selected entry.
 *
 * @param dummy the button
 * @param user_data master publishing dialog context of our window
 */
void
GNUNET_GTK_master_publish_dialog_delete_button_clicked_cb (GtkWidget *dummy,
                                                           gpointer user_data)
{
  struct MainPublishingDialogContext *ctx = user_data;
  GtkTreeIter iter;
  struct GNUNET_FS_FileInformation *fip;
  GtkTreeRowReference *rr;
  GtkTreePath *path;

  /* initially, both 'iter' and 'next' point to the selected row */
  if (! gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter))
  {
    GNUNET_break (0);
    return;
  }

  path = gtk_tree_model_get_path (ctx->file_info_treemodel, &iter);
  if (gtk_tree_model_iter_next (ctx->file_info_treemodel, &iter))
    gtk_tree_path_next (path);
  else
  {
    if (! gtk_tree_path_prev (path))
    {
      if ((1 == gtk_tree_path_get_depth (path)) || (! gtk_tree_path_up (path)))
      {
        gtk_tree_path_free (path);
        path = NULL;
      }
    }
  }
  if (NULL == path)
  {
    rr = NULL;
  }
  else
  {
    rr = gtk_tree_row_reference_new (ctx->file_info_treemodel, path);
    gtk_tree_path_free (path);
  }

  /* 'iter' might have again been clobbered, get it one more time... */
  GNUNET_assert (
    gtk_tree_selection_get_selected (ctx->file_info_selection, NULL, &iter));

  /* now delete the subtree */
  gtk_tree_model_get (ctx->file_info_treemodel,
                      &iter,
                      PUBLISH_MC_FILE_INFORMATION_STRUCT,
                      &fip,
                      -1);
  GNUNET_FS_file_information_destroy (fip, &free_fi_row_reference, NULL);
  GNUNET_FS_GTK_remove_treestore_subtree (GTK_TREE_STORE (
                                            ctx->file_info_treemodel),
                                          &iter);

  /* finally, select the item from 'rr' (if any) */
  if (NULL != rr)
  {
    path = gtk_tree_row_reference_get_path (rr);
    gtk_tree_row_reference_free (rr);
    gtk_tree_selection_select_path (ctx->file_info_selection, path);
    gtk_tree_path_free (path);
  }

  /* and now, depending on the selection, update the sensitivity of buttons */
  update_selectivity_execute_cancel (ctx);
  update_selectivity_edit (ctx);
}


/* ******************** progress dialog / import of directories ************* */

/**
 * Close the progress dialog and free its handle.
 * Updates all buttons sensitivity.
 *
 * @param adcc context for the progress dialog to close
 */
static void
destroy_progress_dialog (struct AddDirClientContext *adcc)
{
  GNUNET_assert (NULL == adcc->ds);
  if (NULL != adcc->kill_task)
  {
    GNUNET_SCHEDULER_cancel (adcc->kill_task);
    adcc->kill_task = NULL;
  }
  gtk_widget_destroy (adcc->progress_dialog);
  g_object_unref (G_OBJECT (adcc->progress_dialog_builder));
  GNUNET_CONTAINER_DLL_remove (adcc->ctx->adddir_head,
                               adcc->ctx->adddir_tail,
                               adcc);
  update_selectivity_execute_cancel (adcc->ctx);
  update_selectivity_edit (adcc->ctx);
  GNUNET_free (adcc);
}


/**
 * User clicked on the 'cancel' button of the progress dialog.
 * Cancel the operation.
 *
 * @param button the cancel button
 * @param user_data progress dialog context of our window
 */
void
GNUNET_FS_GTK_progress_dialog_cancel_button_clicked_cb (GtkButton *button,
                                                        gpointer user_data)
{
  struct AddDirClientContext *adcc = user_data;

  if (NULL != adcc->ds)
  {
    /* signal the scanner to finish */
    GNUNET_FS_directory_scan_abort (adcc->ds);
    adcc->ds = NULL;
  }
  destroy_progress_dialog (adcc);
}


/**
 * User attempted to close the progress dialog.  Refuse.
 *
 * @param widget the widget emitting the event
 * @param event the event
 * @param cls progress dialog context of our window
 * @return TRUE to refuse to close
 */
gboolean
GNUNET_FS_GTK_progress_dialog_delete_event_cb (GtkWidget *widget,
                                               GdkEvent *event,
                                               void *cls)
{
  /* Don't allow GTK to kill the window, until the scan is finished */
  return TRUE;
}


/**
 * Display some additional information in the text area of the
 * progress dialog.
 *
 * @param adcc  progress dialog context of our window
 * @param text text to add
 */
static void
insert_progress_dialog_text (struct AddDirClientContext *adcc, const char *text)
{
  gtk_text_buffer_insert_at_cursor (adcc->progress_dialog_textbuffer, text, -1);
  gtk_text_view_place_cursor_onscreen (adcc->progress_dialog_textview);
  gtk_adjustment_set_value (adcc->textview_vertical_adjustment,
                            gtk_adjustment_get_upper (
                              adcc->textview_vertical_adjustment));
}


/**
 * Convert a single item from the scan to an entry in the tree view.
 *
 * @param adcc progress dialog context of our window
 * @param ts tree store to add an item to
 * @param item scanned item to add
 * @param parent of the item, can be NULL (for root)
 * @param sibling predecessor of the item, can be NULL (for first)
 * @param item_iter entry to set to the added item (OUT)
 */
static void
add_item (struct AddDirClientContext *adcc,
          GtkTreeStore *ts,
          struct GNUNET_FS_ShareTreeItem *item,
          GtkTreeIter *parent,
          GtkTreeIter *sibling,
          GtkTreeIter *item_iter)
{
  char *file_size_fancy;
  struct GNUNET_FS_FileInformation *fi;
  GtkTreeRowReference *row_reference;
  GtkTreePath *path;
  struct stat sbuf;
  uint64_t fsize;

  if (0 != stat (item->filename, &sbuf))
  {
    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "stat", item->filename);
    return;
  }

  if ((item->is_directory != GNUNET_YES) &&
      (GNUNET_OK !=
       GNUNET_DISK_file_size (item->filename, &fsize, GNUNET_YES, GNUNET_YES)))
    return;
  gtk_tree_store_insert_after (ts, item_iter, parent, sibling);
  path = gtk_tree_model_get_path (GTK_TREE_MODEL (ts), item_iter);
  row_reference = gtk_tree_row_reference_new (GTK_TREE_MODEL (ts), path);
  gtk_tree_path_free (path);
  if (item->is_directory == GNUNET_YES)
  {
    /* update meta data mime type (force to be GNUnet-directory) */
    if (NULL != item->meta)
      GNUNET_FS_meta_data_delete (item->meta,
                                  EXTRACTOR_METATYPE_MIMETYPE,
                                  NULL,
                                  0);
    else
      item->meta = GNUNET_FS_meta_data_create ();
    GNUNET_FS_meta_data_make_directory (item->meta);

    fi = GNUNET_FS_file_information_create_empty_directory (
      GNUNET_FS_GTK_get_fs_handle (),
      row_reference,
      item->ksk_uri,
      item->meta,
      &adcc->directory_scan_bo,
      item->filename);
    file_size_fancy = GNUNET_strdup (MARKER_DIR_FILE_SIZE);
  }
  else
  {
    fi =
      GNUNET_FS_file_information_create_from_file (
        GNUNET_FS_GTK_get_fs_handle (),
        row_reference,
        item->filename,
        item->ksk_uri,
        item->meta,
        adcc
        ->directory_scan_do_index,
        &adcc->directory_scan_bo);
    file_size_fancy = GNUNET_STRINGS_byte_size_fancy (fsize);
  }
  gtk_tree_store_set (ts,
                      item_iter,
                      PUBLISH_MC_FILESIZE,
                      file_size_fancy,
                      PUBLISH_MC_DO_INDEX,
                      (gboolean) adcc->directory_scan_do_index,
                      PUBLISH_MC_FILENAME,
                      item->short_filename,
                      PUBLISH_MC_ANONYMITY_LEVEL,
                      (guint) adcc->directory_scan_bo.anonymity_level,
                      PUBLISH_MC_PRIORITY,
                      (guint) adcc->directory_scan_bo.content_priority,
                      PUBLISH_MC_FILE_INFORMATION_STRUCT,
                      fi,
                      PUBLISH_MC_EXPIRATION_TIME_ABSOLUTE,
                      (guint64)
                      adcc->directory_scan_bo.expiration_time.abs_value_us,
                      PUBLISH_MC_REPLICATION_LEVEL,
                      (guint) adcc->directory_scan_bo.replication_level,
                      -1);
  GNUNET_free (file_size_fancy);
}


/**
 * Recursively traverse the share tree and add it to the tree store
 *
 * @param adcc  progress dialog context of our window
 * @param toplevel root of the tree to add
 * @param parent_iter parent of the current entry to add
 */
static void
add_share_items_to_treestore (struct AddDirClientContext *adcc,
                              struct GNUNET_FS_ShareTreeItem *toplevel,
                              GtkTreeIter *parent_iter)
{
  struct MainPublishingDialogContext *ctx = adcc->ctx;
  GtkTreeStore *ts = GTK_TREE_STORE (ctx->file_info_treemodel);
  GtkTreeIter *sibling_iter;
  GtkTreeIter last_added;
  struct GNUNET_FS_ShareTreeItem *item;

  sibling_iter = NULL;
  for (item = toplevel; NULL != item; item = item->next)
  {
    add_item (adcc, ts, item, parent_iter, sibling_iter, &last_added);
    sibling_iter = &last_added;
    if (item->is_directory == GNUNET_YES)
      add_share_items_to_treestore (adcc, item->children_head, sibling_iter);
  }
}


/**
 * Function called when the scanner had some trouble and we
 * need to abort the scanning process (which we need to do
 * in a separate task).
 *
 * @param cls  progress dialog context of our window
 */
static void
stop_scanner_task (void *cls)
{
  struct AddDirClientContext *adcc = cls;

  adcc->kill_task = NULL;
  if (NULL != adcc->ds)
  {
    GNUNET_FS_directory_scan_abort (adcc->ds);
    adcc->ds = NULL;
  }
}


/**
 * Progress callback called from the directory scanner with
 * information about our progress scanning the hierarchy.
 *
 * @param cls  progress dialog context of our window
 * @param filename filename this update is about, can be NULL
 * @param is_directory is this file a directory, #GNUNET_SYSERR if not
 * applicable
 * @param reason kind of progress that was made
 */
static void
directory_scan_cb (void *cls,
                   const char *filename,
                   int is_directory,
                   enum GNUNET_FS_DirScannerProgressUpdateReason reason)
{
  struct AddDirClientContext *adcc = cls;
  static struct GNUNET_TIME_Absolute last_pulse;
  char *s;
  gdouble fraction;

  switch (reason)
  {
  case GNUNET_FS_DIRSCANNER_FILE_START:
    GNUNET_assert (NULL != filename);
    if (GNUNET_TIME_absolute_get_duration (last_pulse).rel_value_us > 100000LL)
    {
      gtk_progress_bar_pulse (adcc->progress_dialog_bar);
      last_pulse = GNUNET_TIME_absolute_get ();
    }
#if VERBOSE_PROGRESS
    if (is_directory == GNUNET_YES)
    {
      GNUNET_asprintf (&s, _ ("Scanning directory `%s'.\n"), filename);
      insert_progress_dialog_text (adcc, s);
      GNUNET_free (s);
    }
    else
      adcc->total++;
#else
    if (is_directory != GNUNET_YES)
      adcc->total++;
#endif
    break;
  case GNUNET_FS_DIRSCANNER_FILE_IGNORED:
    GNUNET_assert (NULL != filename);
    GNUNET_asprintf (&s,
                     _ ("Failed to scan `%s' (access error). Skipping.\n"),
                     filename);
#if ! VERBOSE_PROGRESS
    gtk_widget_show (GTK_WIDGET (
                       gtk_builder_get_object (adcc->progress_dialog_builder,
                                               "GNUNET_FS_GTK_progress_dialog_scrolled_window")));
#endif
    insert_progress_dialog_text (adcc, s);
    GNUNET_free (s);
    break;
  case GNUNET_FS_DIRSCANNER_ALL_COUNTED:
    fraction = (adcc->total == 0) ? 1.0 : (1.0 * adcc->done) / adcc->total;
    GNUNET_asprintf (&s,
                     "%u/%u (%3f%%)",
                     adcc->done,
                     adcc->total,
                     100.0 * fraction);
    gtk_progress_bar_set_text (adcc->progress_dialog_bar, s);
    GNUNET_free (s);
    gtk_progress_bar_set_fraction (adcc->progress_dialog_bar, fraction);
    break;
  case GNUNET_FS_DIRSCANNER_EXTRACT_FINISHED:
    GNUNET_assert (NULL != filename);
#if VERBOSE_PROGRESS
    GNUNET_asprintf (&s, _ ("Processed file `%s'.\n"), filename);
    insert_progress_dialog_text (adcc, s);
    GNUNET_free (s);
#endif
    adcc->done++;
    GNUNET_assert (adcc->done <= adcc->total);
    fraction = (adcc->total == 0) ? 1.0 : (1.0 * adcc->done) / adcc->total;
    GNUNET_asprintf (&s,
                     "%u/%u (%3f%%)",
                     adcc->done,
                     adcc->total,
                     100.0 * fraction);
    gtk_progress_bar_set_text (adcc->progress_dialog_bar, s);
    GNUNET_free (s);
    gtk_progress_bar_set_fraction (adcc->progress_dialog_bar, fraction);
    break;
  case GNUNET_FS_DIRSCANNER_INTERNAL_ERROR:
    if ((NULL != adcc->ds) && (NULL == adcc->kill_task))
    {
      insert_progress_dialog_text (adcc,
                                   _ ("Operation failed (press cancel)\n"));
      adcc->kill_task = GNUNET_SCHEDULER_add_now (&stop_scanner_task, adcc);
    }
    break;
  case GNUNET_FS_DIRSCANNER_FINISHED: {
      struct GNUNET_FS_ShareTreeItem *directory_scan_result;

      insert_progress_dialog_text (adcc, _ ("Scanner has finished.\n"));
      directory_scan_result = GNUNET_FS_directory_scan_get_result (adcc->ds);
      adcc->ds = NULL;
      GNUNET_FS_share_tree_trim (directory_scan_result);
      add_share_items_to_treestore (adcc, directory_scan_result, NULL);
      GNUNET_FS_share_tree_free (directory_scan_result);
      destroy_progress_dialog (adcc);
    }
    break;
  default:
    GNUNET_break (0);
    break;
  }
}


/**
 * Setup the context and progress dialog for scanning a file or
 * directory structure (for meta data) and importing it into
 * the tree view.
 *
 * @param ctx publishing context for the main publishing window
 * @param filename name of the file or directory to scan
 * @param bo options for the operation
 * @param do_index should we index or insert files (by default)
 */
static void
scan_file_or_directory (struct MainPublishingDialogContext *ctx,
                        gchar *filename,
                        struct GNUNET_FS_BlockOptions *bo,
                        int do_index)
{
  struct AddDirClientContext *adcc;

  adcc = GNUNET_new (struct AddDirClientContext);
  adcc->ctx = ctx;
  GNUNET_CONTAINER_DLL_insert_tail (ctx->adddir_head, ctx->adddir_tail, adcc);
  adcc->directory_scan_bo = *bo;
  adcc->directory_scan_do_index = do_index;

  /* setup the dialog and get the widgets we need most */
  adcc->progress_dialog_builder =
    GNUNET_GTK_get_new_builder ("gnunet_fs_gtk_progress_dialog.glade", adcc);
  adcc->progress_dialog =
    GTK_WIDGET (gtk_builder_get_object (adcc->progress_dialog_builder,
                                        "GNUNET_FS_GTK_progress_dialog"));
  adcc->progress_dialog_bar = GTK_PROGRESS_BAR (
    gtk_builder_get_object (adcc->progress_dialog_builder,
                            "GNUNET_FS_GTK_progress_dialog_progressbar"));
  adcc->progress_dialog_textview = GTK_TEXT_VIEW (
    gtk_builder_get_object (adcc->progress_dialog_builder,
                            "GNUNET_FS_GTK_progress_dialog_textview"));
  adcc->textview_vertical_adjustment = GTK_ADJUSTMENT (gtk_builder_get_object (
                                                         adcc->
                                                         progress_dialog_builder,
                                                         "GNUNET_FS_GTK_progress_dialog_textview_vertical_adjustment"));
  adcc->progress_dialog_textbuffer = GTK_TEXT_BUFFER (
    gtk_builder_get_object (adcc->progress_dialog_builder,
                            "GNUNET_FS_GTK_progress_dialog_textbuffer"));

  /* show the window */
  gtk_window_set_transient_for (GTK_WINDOW (adcc->progress_dialog),
                                adcc->ctx->master_pubdialog);
  gtk_window_set_title (GTK_WINDOW (adcc->progress_dialog), filename);
  gtk_window_present (GTK_WINDOW (adcc->progress_dialog));

  /* actually start the scan */
  adcc->ds = GNUNET_FS_directory_scan_start (filename,
                                             GNUNET_NO,
                                             NULL,
                                             &directory_scan_cb,
                                             adcc);

  /* disables 'cancel' button of the master dialog */
  update_selectivity_execute_cancel (ctx);
}


/**
 * Function called when the "open" (directory) dialog was closed.
 *
 * @param dialog the open dialog
 * @param response_id result of the dialog (GTK_RESPONSE_OK means to "run")
 * @param user_data master publishing dialog context of our window
 */
void
GNUNET_GTK_publish_directory_dialog_response_cb (GtkDialog *dialog,
                                                 gint response_id,
                                                 gpointer user_data)
{
  struct MainPublishingDialogContext *ctx = user_data;

  if (GTK_RESPONSE_OK == response_id)
  {
    char *filename;
    int do_index;
    struct GNUNET_FS_BlockOptions bo;

    filename =
      GNUNET_GTK_filechooser_get_filename_utf8 (GTK_FILE_CHOOSER (dialog));
    if (! GNUNET_GTK_get_selected_anonymity_level (
          ctx->open_directory_builder,
          "GNUNET_GTK_publish_directory_dialog_anonymity_combobox",
          &bo.anonymity_level))
    {
      GNUNET_break (0);
      bo.anonymity_level = 1;
    }
    bo.content_priority =
      gtk_spin_button_get_value (GTK_SPIN_BUTTON (gtk_builder_get_object (
                                                    ctx->open_directory_builder,
                                                    "GNUNET_GTK_publish_directory_dialog_priority_spin_button")));
    bo.replication_level =
      gtk_spin_button_get_value (GTK_SPIN_BUTTON (gtk_builder_get_object (
                                                    ctx->open_directory_builder,
                                                    "GNUNET_GTK_publish_directory_dialog_replication_spin_button")));
    {
      GtkSpinButton *sb;

      sb = GTK_SPIN_BUTTON (gtk_builder_get_object (
                              ctx->open_directory_builder,
                              "GNUNET_GTK_publish_directory_dialog_expiration_year_spin_button"));
      bo.expiration_time = GNUNET_GTK_get_expiration_time (sb);
    }
    do_index =
      gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (gtk_builder_get_object (
                                                         ctx->
                                                         open_directory_builder,
                                                         "GNUNET_GTK_publish_directory_dialog_do_index_checkbutton")));
    scan_file_or_directory (ctx, filename, &bo, do_index);
    g_free (filename);
  }
  gtk_widget_destroy (GTK_WIDGET (dialog));
  g_object_unref (G_OBJECT (ctx->open_directory_builder));
  ctx->open_directory_builder = NULL;
}


/**
 * Function called when the "open" (file) dialog was closed.
 *
 * @param dialog the open dialog
 * @param response_id result of the dialog (GTK_RESPONSE_OK means to "run")
 * @param user_data master publishing dialog context of our window
 */
void
GNUNET_GTK_publish_file_dialog_response_cb (GtkDialog *dialog,
                                            gint response_id,
                                            gpointer user_data)
{
  struct MainPublishingDialogContext *ctx = user_data;

  if (GTK_RESPONSE_OK == response_id)
  {
    char *filename;
    struct GNUNET_FS_BlockOptions bo;
    int do_index;

    filename =
      GNUNET_GTK_filechooser_get_filename_utf8 (GTK_FILE_CHOOSER (dialog));
    if (! GNUNET_GTK_get_selected_anonymity_level (
          ctx->open_file_builder,
          "GNUNET_GTK_publish_file_dialog_anonymity_combobox",
          &bo.anonymity_level))
    {
      GNUNET_break (0);
      bo.anonymity_level = 1;
    }
    bo.content_priority =
      gtk_spin_button_get_value (GTK_SPIN_BUTTON (gtk_builder_get_object (
                                                    ctx->open_file_builder,
                                                    "GNUNET_GTK_publish_file_dialog_priority_spin_button")));
    {
      GtkSpinButton *sb;

      sb = GTK_SPIN_BUTTON (gtk_builder_get_object (
                              ctx->open_file_builder,
                              "GNUNET_GTK_publish_file_dialog_expiration_year_spin_button"));
      bo.expiration_time = GNUNET_GTK_get_expiration_time (sb);
    }
    bo.replication_level =
      gtk_spin_button_get_value (GTK_SPIN_BUTTON (gtk_builder_get_object (
                                                    ctx->open_file_builder,
                                                    "GNUNET_GTK_publish_file_dialog_replication_spin_button")));
    do_index =
      gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (gtk_builder_get_object (
                                                         ctx->open_file_builder,
                                                         "GNUNET_GTK_publish_file_dialog_do_index_checkbutton")));

    scan_file_or_directory (ctx, filename, &bo, do_index);
    g_free (filename);
  }
  gtk_widget_destroy (GTK_WIDGET (dialog));
  g_object_unref (G_OBJECT (ctx->open_file_builder));
  ctx->open_file_builder = NULL;
}


/**
 * User clicked on the 'add' button in the master publish dialog.
 * Create the dialog to allow the user to select a file to add.
 *
 * FIXME-UGLY: lots of code duplication between files & directories here...
 *
 * @param dummy the button that was pressed
 * @param user_data master publishing dialog context of our window
 */
void
GNUNET_GTK_master_publish_dialog_add_button_clicked_cb (GtkWidget *dummy,
                                                        gpointer user_data)
{
  struct MainPublishingDialogContext *ctx = user_data;

  ctx->open_file_builder =
    GNUNET_GTK_get_new_builder ("gnunet_fs_gtk_publish_file_dialog.glade", ctx);
  GNUNET_GTK_setup_expiration_year_adjustment (ctx->open_file_builder);

  /* FIXME-FEATURE: Use some kind of adjustable defaults instead of 1000, 0 and
   * TRUE */
  gtk_spin_button_set_value (
    GTK_SPIN_BUTTON (gtk_builder_get_object (
                       ctx->open_file_builder,
                       "GNUNET_GTK_publish_file_dialog_priority_spin_button")),
    1000);
  gtk_spin_button_set_value (
    GTK_SPIN_BUTTON (gtk_builder_get_object (
                       ctx->open_file_builder,
                       "GNUNET_GTK_publish_file_dialog_replication_spin_button")),
    0);
  gtk_toggle_button_set_active (
    GTK_TOGGLE_BUTTON (gtk_builder_get_object (
                         ctx->open_file_builder,
                         "GNUNET_GTK_publish_file_dialog_do_index_checkbutton")),
    TRUE);

  {
    GtkComboBox *combo;

    combo = GTK_COMBO_BOX (
      gtk_builder_get_object (ctx->open_file_builder,
                              "GNUNET_GTK_publish_file_dialog_anonymity_combobox"));
    gtk_combo_box_set_model (combo,
                             GNUNET_FS_GTK_get_anonymity_level_list_store ());
  }

  /* show dialog */
  {
    GtkWidget *ad;

    ad = GTK_WIDGET (gtk_builder_get_object (ctx->open_file_builder,
                                             "GNUNET_GTK_publish_file_dialog"));
    gtk_window_set_transient_for (GTK_WINDOW (ad), ctx->master_pubdialog);
    gtk_window_present (GTK_WINDOW (ad));
  }
}


/**
 * User clicked on the 'open' button in the master publish dialog.
 * Create the dialog to allow the user to select a directory.
 *
 * FIXME-UGLY: lots of code duplication between files & directories here...
 *
 * @param dummy the button that was pressed
 * @param user_data master publishing dialog context of our window
 */
void
GNUNET_GTK_master_publish_dialog_open_button_clicked_cb (GtkWidget *dummy,
                                                         gpointer user_data)
{
  struct MainPublishingDialogContext *ctx = user_data;

  ctx->open_directory_builder =
    GNUNET_GTK_get_new_builder ("gnunet_fs_gtk_publish_directory_dialog.glade",
                                ctx);
  GNUNET_GTK_setup_expiration_year_adjustment (ctx->open_directory_builder);

  /* FIXME-FEATURE: Use some kind of adjustable defaults instead of 1000, 0 and
   * TRUE */
  gtk_spin_button_set_value (
    GTK_SPIN_BUTTON (gtk_builder_get_object (
                       ctx->open_directory_builder,
                       "GNUNET_GTK_publish_directory_dialog_priority_spin_button")),
    1000);
  gtk_spin_button_set_value (
    GTK_SPIN_BUTTON (gtk_builder_get_object (
                       ctx->open_directory_builder,
                       "GNUNET_GTK_publish_directory_dialog_replication_spin_button")),
    0);
  gtk_toggle_button_set_active (
    GTK_TOGGLE_BUTTON (gtk_builder_get_object (
                         ctx->open_directory_builder,
                         "GNUNET_GTK_publish_directory_dialog_do_index_checkbutton")),
    TRUE);

  {
    GtkComboBox *combo;

    combo = GTK_COMBO_BOX (gtk_builder_get_object (
                             ctx->open_directory_builder,
                             "GNUNET_GTK_publish_directory_dialog_anonymity_combobox"));
    gtk_combo_box_set_model (combo,
                             GNUNET_FS_GTK_get_anonymity_level_list_store ());
  }

  /* show dialog */
  {
    GtkWidget *ad;

    ad = GTK_WIDGET (
      gtk_builder_get_object (ctx->open_directory_builder,
                              "GNUNET_GTK_publish_directory_dialog"));
    gtk_window_set_transient_for (GTK_WINDOW (ad), ctx->master_pubdialog);
    gtk_window_present (GTK_WINDOW (ad));
  }
}


/* ********************************* editing sub-dialog ********************* */

/**
 * Context we keep while an "edit" sub-dialog is open.
 */
struct EditPublishContext
{
  /**
   * File information that is being edited
   */
  struct GNUNET_FS_FileInformation *fip;

  /**
   * Tree model that is being edited (where to store the results afterwards).
   */
  GtkTreeModel *tm;

  /**
   * Position in the tree that is being edited.
   */
  GtkTreeIter iter;
};

/**
 * Update tree view based on the information from the
 * `struct GNUNET_FS_FileInformation` for publishing.
 *
 * @param cls closure, a `struct EditPublishContext *`
 * @param fi the entry in the publish-structure
 * @param length length of the file or directory
 * @param meta metadata for the file or directory (can be modified)
 * @param uri pointer to the keywords that will be used for this entry (can be
 * modified)
 * @param bo block options (can be modified)
 * @param do_index should we index (can be modified)
 * @param client_info pointer to client context set upon creation (can be
 * modified)
 * @return #GNUNET_OK to continue, #GNUNET_NO to remove
 *         this entry from the directory, #GNUNET_SYSERR
 *         to abort the iteration
 */
static int
update_treeview_after_edit (void *cls,
                            struct GNUNET_FS_FileInformation *fi,
                            uint64_t length,
                            struct GNUNET_FS_MetaData *meta,
                            struct GNUNET_FS_Uri **uri,
                            struct GNUNET_FS_BlockOptions *bo,
                            int *do_index,
                            void **client_info)
{
  struct EditPublishContext *epc = cls;
  char *name;

  name = GNUNET_FS_meta_data_get_by_type (
    meta,
    EXTRACTOR_METATYPE_GNUNET_ORIGINAL_FILENAME);
  gtk_tree_store_set (GTK_TREE_STORE (epc->tm),
                      &epc->iter,
                      PUBLISH_MC_DO_INDEX,
                      *do_index,
                      PUBLISH_MC_FILENAME,
                      name,
                      PUBLISH_MC_ANONYMITY_LEVEL,
                      (guint) bo->anonymity_level,
                      PUBLISH_MC_PRIORITY,
                      (guint) bo->content_priority,
                      PUBLISH_MC_EXPIRATION_TIME_ABSOLUTE,
                      (guint64) bo->expiration_time.abs_value_us,
                      PUBLISH_MC_REPLICATION_LEVEL,
                      (guint) bo->replication_level,
                      -1);
  GNUNET_free (name);
  return GNUNET_SYSERR;
}


/**
 * Function called when the edit publish dialog has been closed.
 *
 * @param cls closure
 * @param ret GTK_RESPONSE_OK if the dialog was closed with "OK"
 * @param root unused (namespace root name)
 */
static void
master_publish_edit_publish_dialog_cb (gpointer cls, gint ret, const char *root)
{
  struct EditPublishContext *epc = cls;

  if (ret == GTK_RESPONSE_OK)
    GNUNET_FS_file_information_inspect (epc->fip,
                                        &update_treeview_after_edit,
                                        epc);
  GNUNET_free (epc);
}


/**
 * The user clicked on the "edit" button in the master dialog.  Edit
 * the selected tree item.
 *
 * @param dummy the 'edit' button
 * @param user_data master publishing dialog context of our window
 */
void
GNUNET_GTK_master_publish_dialog_edit_button_clicked_cb (GtkWidget *dummy,
                                                         gpointer user_data)
{
  struct MainPublishingDialogContext *ctx = user_data;
  struct EditPublishContext *epc;

  epc = GNUNET_new (struct EditPublishContext);
  epc->tm = ctx->file_info_treemodel;
  if (! gtk_tree_selection_get_selected (ctx->file_info_selection,
                                         NULL,
                                         &epc->iter))
  {
    GNUNET_break (0);
    GNUNET_free (epc);
    return;
  }
  gtk_tree_model_get (ctx->file_info_treemodel,
                      &epc->iter,
                      PUBLISH_MC_FILE_INFORMATION_STRUCT,
                      &epc->fip,
                      -1);
  GNUNET_FS_GTK_edit_publish_dialog (ctx->master_pubdialog,
                                     epc->fip,
                                     GNUNET_NO,
                                     &master_publish_edit_publish_dialog_cb,
                                     epc);
}


/* ******************** master edit dialog shutdown *********************** */

/**
 * Get the file information struct corresponding to the
 * given iter in the publish dialog tree model.  Recursively
 * builds the file information struct from the subtree.
 *
 * @param tm model to grab fi from
 * @param iter position to grab fi from
 * @return file information from the given position (never NULL)
 */
static struct GNUNET_FS_FileInformation *
get_file_information (GtkTreeModel *tm, GtkTreeIter *iter)
{
  struct GNUNET_FS_FileInformation *fi;
  struct GNUNET_FS_FileInformation *fic;
  GtkTreeIter child;

  gtk_tree_model_get (tm, iter, PUBLISH_MC_FILE_INFORMATION_STRUCT, &fi, -1);
  gtk_tree_store_set (GTK_TREE_STORE (tm),
                      iter,
                      PUBLISH_MC_FILE_INFORMATION_STRUCT,
                      NULL,
                      -1);
  GNUNET_assert (fi != NULL);
  if (gtk_tree_model_iter_children (tm, &child, iter))
  {
    GNUNET_break (GNUNET_YES == GNUNET_FS_file_information_is_directory (fi));
    do
    {
      fic = get_file_information (tm, &child);
      GNUNET_break (GNUNET_OK == GNUNET_FS_file_information_add (fi, fic));
    } while (gtk_tree_model_iter_next (tm, &child));
  }
  return fi;
}


/**
 * Recursively clean up the tree store with the file information in it.
 *
 * @param tm tree model we are cleaning up
 * @param iter current position to clean up
 */
static void
free_file_information_tree_store (GtkTreeModel *tm, GtkTreeIter *iter)
{
  GtkTreeIter child;
  struct GNUNET_FS_FileInformation *fip;

  gtk_tree_model_get (tm, iter, PUBLISH_MC_FILE_INFORMATION_STRUCT, &fip, -1);
  if (NULL != fip)
    GNUNET_FS_file_information_destroy (fip, NULL, NULL);
  /* recursively clean up children */
  if (gtk_tree_model_iter_children (tm, &child, iter))
  {
    do
    {
      free_file_information_tree_store (tm, &child);
    } while (gtk_tree_model_iter_next (tm, &child));
  }
}


/**
 * Close the master publish dialog.  If the response code was OK, starts
 * the publishing operation.  Otherwise, this function just cleans up the
 * memory and the window itself.
 *
 * @param ctx master dialog context
 * @return #GNUNET_NO if we cannot clean up right now (sub-windows are still
 * open)
 */
static int
close_master_publish_dialog (struct MainPublishingDialogContext *ctx)
{
  GtkTreeIter iter;

  /* Refuse to close until all scanners are finished */
  if (NULL != ctx->adddir_head)
    return GNUNET_NO;

  /* free state from the identifiers treemodel */
  gtk_tree_store_clear (GTK_TREE_STORE (ctx->identifiers_treemodel));

  /* free state from the file info treemodel */
  if (gtk_tree_model_get_iter_first (ctx->file_info_treemodel, &iter))
    do
    {
      free_file_information_tree_store (ctx->file_info_treemodel, &iter);
    } while (gtk_tree_model_iter_next (ctx->file_info_treemodel, &iter));
  gtk_tree_store_clear (GTK_TREE_STORE (ctx->file_info_treemodel));
  GNUNET_IDENTITY_disconnect (ctx->identity);
  gtk_widget_destroy (GTK_WIDGET (ctx->master_pubdialog));
  g_object_unref (G_OBJECT (ctx->builder));
  GNUNET_free (ctx->ns_name);
  g_free (ctx->previous_id);
  GNUNET_free (ctx);
  return GNUNET_YES;
}


/**
 * Insert namespace advertisement into metadata when
 * publishing in both private namespace and global namespace.
 *
 * @param cls closure, a 'struct MainPublishingDialogContext *'
 * @param fi the entry in the publish-structure
 * @param length length of the file or directory
 * @param meta metadata for the file or directory (can be modified)
 * @param uri pointer to the keywords that will be used for this entry (can be
 * modified)
 * @param bo block options (can be modified)
 * @param do_index should we index (can be modified)
 * @param client_info pointer to client context set upon creation (can be
 * modified)
 * @return #GNUNET_OK to continue, #GNUNET_NO to remove
 *         this entry from the directory, #GNUNET_SYSERR
 *         to abort the iteration
 */
static int
insert_advertisement (void *cls,
                      struct GNUNET_FS_FileInformation *fi,
                      uint64_t length,
                      struct GNUNET_FS_MetaData *meta,
                      struct GNUNET_FS_Uri **uri,
                      struct GNUNET_FS_BlockOptions *bo,
                      int *do_index,
                      void **client_info)
{
  struct MainPublishingDialogContext *ctx = cls;
  struct GNUNET_IDENTITY_PublicKey pub;
  struct GNUNET_FS_Uri *sks_uri;
  char *sks_uri_string;

  if (NULL == ctx->ns)
    return GNUNET_SYSERR;
  GNUNET_IDENTITY_ego_get_public_key (ctx->ns, &pub);
  if (GNUNET_IDENTITY_TYPE_ECDSA != pub.type)
  {
    GNUNET_break (0); /* FIXME: EDDSA keys not yet
                         supported for file-sharing! */
    return GNUNET_SYSERR;
  }
  sks_uri = GNUNET_FS_uri_sks_create (&pub.ecdsa_key, "/");
  sks_uri_string = GNUNET_FS_uri_to_string (sks_uri);
  GNUNET_FS_uri_destroy (sks_uri);
  if (NULL == sks_uri_string)
    return GNUNET_SYSERR;

  GNUNET_FS_meta_data_insert (meta,
                              "<gnunet>",
                              EXTRACTOR_METATYPE_URI,
                              EXTRACTOR_METAFORMAT_UTF8,
                              "text/plain",
                              sks_uri_string,
                              strlen (sks_uri_string) + 1);
  GNUNET_free (sks_uri_string);
  return GNUNET_SYSERR;
}


/**
 * The user pushed the 'execute' button.  Start the publishing
 * operation and clean up the memory and the window itself.
 *
 * @param button the button that was clicked
 * @param user_data master publishing dialog context of our window
 */
void
GNUNET_GTK_master_publish_dialog_execute_button_clicked_cb (GtkButton *button,
                                                            gpointer user_data)
{
  struct MainPublishingDialogContext *ctx = user_data;
  gchar *namespace_id;
  struct GNUNET_IDENTITY_Ego *ns;
  const gchar *namespace_uid;
  struct GNUNET_FS_FileInformation *fi;
  GtkTreeIter iter;
  gboolean do_global;
  gboolean do_updateable;
  gboolean do_own;
  gboolean disable_ads_insertion;

  if (NULL != ctx->adddir_head)
  {
    GNUNET_break (0);
    return;
  }

  do_global =
    gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ctx->global_checkbox));
  do_updateable =
    gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ctx->updateable_checkbox));
  do_own = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ctx->own_checkbox));

  disable_ads_insertion = GNUNET_CONFIGURATION_get_value_yesno (
    GNUNET_FS_GTK_get_configuration (),
    "gnunet-fs-gtk",
    "DISABLE_AUTOMATIC_NAMESPACE_ADVERTISEMENT_INSERTION");
  if (disable_ads_insertion == GNUNET_SYSERR)
    disable_ads_insertion = GNUNET_NO;

  ns = NULL;
  namespace_id = NULL;
  namespace_uid = NULL;
  if (ctx->ns && do_own)
  {
    const gchar *id_entry_text;
    if (NULL != (id_entry_text =
                   gtk_entry_get_text (GTK_ENTRY (ctx->identifier_entry))))
      namespace_id = g_strdup (id_entry_text);
    else if (! do_global)
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Global publication is disabled, but "
                  "namespace_id is not available\n");
    if ((NULL != namespace_id) && ('\0' == namespace_id[0]))
    {
      g_free (namespace_id);
      namespace_id = NULL;
    }
    if (NULL != namespace_id)
    {
      namespace_uid = gtk_entry_get_text (GTK_ENTRY (ctx->update_id_entry));
      if ((NULL == namespace_uid) || ('\0' == namespace_uid[0]) ||
          (! do_updateable))
        namespace_uid = NULL;
      ns = ctx->ns;
    }
    else if (! do_global)
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Global publication is disabled, but namespace_id unavailable "
                  "or zero-length\n");
  }
  else if (! do_global)
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Global publication is disabled, ns is either NULL or disabled, "
                "yet 'Execute' was pressed\n");
  if ((! do_global) && (NULL == ns))
  {
    g_free (namespace_id);
    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ctx->own_checkbox), FALSE);
    return;
  }
  if (gtk_tree_model_get_iter_first (ctx->file_info_treemodel, &iter))
  {
    if (! do_global)
      clear_keywords_from_tm (ctx);
    do
    {
      const struct GNUNET_IDENTITY_PrivateKey *pk;

      if (NULL != ns)
      {
        pk = GNUNET_IDENTITY_ego_get_private_key (ns);
        if (GNUNET_IDENTITY_TYPE_ECDSA != pk->type)
        {
          GNUNET_break (0); /* FIXME: EDDSA keys not yet supported by FS */
          gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ctx->own_checkbox),
                                        FALSE);
          return;
        }
      }
      else
      {
        pk = NULL;
      }
      fi = get_file_information (ctx->file_info_treemodel, &iter);
      if (do_global && do_own && ! disable_ads_insertion)
        GNUNET_FS_file_information_inspect (fi, insert_advertisement, ctx);
      GNUNET_FS_publish_start (GNUNET_FS_GTK_get_fs_handle (),
                               fi,
                               (NULL == pk) ? NULL : &pk->ecdsa_key,
                               namespace_id,
                               namespace_uid,
                               GNUNET_FS_PUBLISH_OPTION_NONE);
    } while (gtk_tree_model_iter_next (ctx->file_info_treemodel, &iter));
  }
  else
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to get first item in files tree view. Is it empty?\n");
  }
  g_free (namespace_id);
  GNUNET_break (GNUNET_YES == close_master_publish_dialog (ctx));
}


/**
 * Function called on entries in a `struct GNUNET_FS_FileInformation` for
 * publishing.
 *
 * @param cls closure, unused
 * @param fi the entry in the publish-structure
 * @param length length of the file or directory
 * @param meta metadata for the file or directory (can be modified)
 * @param uri pointer to the keywords that will be used for this entry (will be
 * set to NULL)
 * @param bo block options (can be modified)
 * @param do_index should we index (can be modified)
 * @param client_info pointer to client context set upon creation (can be
 * modified)
 * @return #GNUNET_OK (to continue)
 */
static int
clear_keywords_in_file_information (void *cls,
                                    struct GNUNET_FS_FileInformation *fi,
                                    uint64_t length,
                                    struct GNUNET_FS_MetaData *meta,
                                    struct GNUNET_FS_Uri **uri,
                                    struct GNUNET_FS_BlockOptions *bo,
                                    int *do_index,
                                    void **client_info)
{
  if (NULL == *uri)
    return GNUNET_OK;
  GNUNET_FS_uri_destroy (*uri);
  *uri = NULL;
  return GNUNET_OK;
}


/**
 * Remove all of the keywords from the file information item in the tree store
 *
 * @param tm tree model / store
 * @param iter current position in the recursion
 */
static void
clear_keywords_from_file_information_in_tree_store (GtkTreeModel *tm,
                                                    GtkTreeIter *iter)
{
  GtkTreeIter child;
  struct GNUNET_FS_FileInformation *fip;

  gtk_tree_model_get (tm, iter, PUBLISH_MC_FILE_INFORMATION_STRUCT, &fip, -1);
  if (NULL != fip)
  {
    GNUNET_FS_file_information_inspect (fip,
                                        &clear_keywords_in_file_information,
                                        NULL);
  }
  /* recursively clean up children */
  if (gtk_tree_model_iter_children (tm, &child, iter))
  {
    do
    {
      clear_keywords_from_file_information_in_tree_store (tm, &child);
    } while (gtk_tree_model_iter_next (tm, &child));
  }
}


/**
 * Remove all of the keywords from all file information structs in the tree
 * store
 *
 * @param ctx context
 */
static void
clear_keywords_from_tm (struct MainPublishingDialogContext *ctx)
{
  GtkTreeIter iter;

  /* clear keywords from 'tm' */
  if (gtk_tree_model_get_iter_first (ctx->file_info_treemodel, &iter))
    do
    {
      clear_keywords_from_file_information_in_tree_store (ctx
                                                          ->file_info_treemodel,
                                                          &iter);
    } while (gtk_tree_model_iter_next (ctx->file_info_treemodel, &iter));
}


/**
 * The user pushed the 'clear' button.  Remove all keywords.
 *
 * @param button the button that was clicked
 * @param user_data master publishing dialog context of our window
 */
void
GNUNET_GTK_master_publish_dialog_clear_button_clicked_cb (GtkButton *button,
                                                          gpointer user_data)
{
  clear_keywords_from_tm (user_data);
}


/**
 * The user pushed the 'cancel' button.  Close the master publish dialog.
 *
 * @param button the button that was clicked
 * @param user_data master publishing dialog context of our window
 */
void
GNUNET_GTK_master_publish_dialog_cancel_button_clicked_cb (GtkButton *button,
                                                           gpointer user_data)
{
  struct MainPublishingDialogContext *ctx = user_data;

  GNUNET_break (GNUNET_YES == close_master_publish_dialog (ctx));
}


/**
 * The user attempted to close the publish window.  Check if this is
 * allowed and if so, close it.
 *
 * @param widget the widget that generated the close event
 * @param event the close event
 * @param user_data master publishing dialog context of our window
 * @return TRUE to refuse to close (stops other handlers from being invoked)
 *         FALSE to allow closing the window
 */
gboolean
GNUNET_GTK_master_publish_dialog_delete_event_cb (GtkWidget *widget,
                                                  GdkEvent *event,
                                                  gpointer user_data)
{
  struct MainPublishingDialogContext *ctx = user_data;

  if (GNUNET_NO == close_master_publish_dialog (ctx))
    return TRUE;
  return FALSE;
}


/**
 * Called when expander changes its state (expanded/hidden).
 * Hides the widget, adjusts the paned position to compensate,
 * changes resizability of the bottom frame (it will not get any
 * extra space while its only resizable widget is hidden).
 *
 * @param object expander
 * @param param_spec parameter that was changed
 * @param user_data context
 */
static void
expander_callback (GObject *object, GParamSpec *param_spec, gpointer user_data)
{
  GtkExpander *expander;
  struct MainPublishingDialogContext *ctx = user_data;
  GtkAllocation scrolled_allocation;
  gint paned_pos;
  GValue gv = G_VALUE_INIT;
  gboolean expanded;
  expander = GTK_EXPANDER (object);

  gtk_widget_get_allocation (GTK_WIDGET (ctx->identifiers_scrolled),
                             &scrolled_allocation);
  expanded = gtk_expander_get_expanded (expander);
  gtk_widget_set_visible (GTK_WIDGET (ctx->identifiers_scrolled), expanded);

  g_value_init (&gv, G_TYPE_BOOLEAN);
  g_value_set_boolean (&gv, expanded);
  gtk_container_child_set_property (GTK_CONTAINER (ctx->vpaned),
                                    ctx->bottom_frame,
                                    "resize",
                                    &gv);
  paned_pos = gtk_paned_get_position (GTK_PANED (ctx->vpaned));
  paned_pos = paned_pos + (scrolled_allocation.height * (expanded ? -1 : 1));
  gtk_paned_set_position (GTK_PANED (ctx->vpaned), paned_pos);
}


/* ******************** master edit dialog initialization ******************* */

/**
 * Closure for 'add_updateable_to_ts'.
 */
struct UpdateableContext
{
  /**
   * Parent of current insertion.
   */
  GtkTreeIter *parent;

  /**
   * Tree store we are modifying.
   */
  GtkTreeStore *ts;

  /**
   * Handle to the namespace.
   */
  struct GNUNET_IDENTITY_Ego *ns;

  /**
   * Hash codes of identifiers already added to tree store.
   */
  struct GNUNET_CONTAINER_MultiHashMap *seen;

  /**
   * Did the iterator get called?
   */
  int update_called;
};

/**
 * Add updateable entries to the tree view.
 *
 * @param cls closure
 * @param last_id ID to add
 * @param last_uri associated URI
 * @param last_meta associate meta data
 * @param next_id ID for future updates
 */
static void
add_updateable_to_ts (void *cls,
                      const char *last_id,
                      const struct GNUNET_FS_Uri *last_uri,
                      const struct GNUNET_FS_MetaData *last_meta,
                      const char *next_id)
{
  struct UpdateableContext *uc = cls;
  struct UpdateableContext sc;
  GtkTreeIter iter;
  GtkTreeIter titer;
  char *desc;
  int desc_is_a_dup;
  struct GNUNET_HashCode hc;
  gchar *displaytext;
  gchar *hashbuf;
  char *mdbuf;
  ssize_t hashbufsize;
  ssize_t mdsize;
  ssize_t next_id_len;
  char *uristring;
  ssize_t urilen;
  ssize_t last_id_len;
  gchar *spath;

  uc->update_called = GNUNET_YES;

  if (next_id)
    next_id_len = strlen (next_id);
  else
    next_id_len = 0;

  if (last_id)
    last_id_len = strlen (last_id);
  else
    last_id_len = 0;

  uristring = GNUNET_FS_uri_to_string (last_uri);
  if (uristring)
    urilen = strlen (uristring);
  else
    urilen = 0;

  mdbuf = NULL;
  mdsize = GNUNET_FS_meta_data_serialize (
    last_meta,
    &mdbuf,
    64 * 1024,
    GNUNET_FS_META_DATA_SERIALIZE_FULL);
  if (0 > mdsize)
    mdsize = 0;

  hashbufsize = last_id_len + next_id_len + urilen + mdsize;
  hashbuf = GNUNET_malloc (hashbufsize);
  if (last_id)
    strcpy (&hashbuf[0], last_id);
  if (next_id)
    strcpy (&hashbuf[last_id_len], next_id);
  if (uristring)
    strcpy (&hashbuf[last_id_len + next_id_len], uristring);
  if (mdbuf)
    memcpy (&hashbuf[last_id_len + next_id_len + urilen], mdbuf, mdsize);

  GNUNET_CRYPTO_hash (hashbuf, hashbufsize, &hc);

  GNUNET_free (uristring);
  GNUNET_free (mdbuf);
  GNUNET_free (hashbuf);

  spath = NULL;
  spath = GNUNET_CONTAINER_multihashmap_get (uc->seen, &hc);

  desc =
    GNUNET_FS_GTK_get_description_from_metadata (last_meta, &desc_is_a_dup);
  if (NULL != spath)
  {
    displaytext = g_strdup_printf ("<i>%s</i>", last_id);
    gtk_tree_store_insert_with_values (uc->ts,
                                       &iter,
                                       uc->parent,
                                       G_MAXINT,
                                       IDENTIFIERS_MC_ID,
                                       last_id,
                                       IDENTIFIERS_MC_UPDATE_ID,
                                       next_id,
                                       IDENTIFIERS_MC_DESCRIPTION,
                                       desc,
                                       IDENTIFIERS_MC_PATH_TO_ORIGINAL,
                                       spath,
                                       IDENTIFIERS_MC_ID_MARKED_UP,
                                       displaytext,
                                       -1);
  }
  else if (NULL == uc->parent)
  {
    displaytext = g_strdup_printf ("%s", last_id);
    gtk_tree_store_insert_with_values (uc->ts,
                                       &iter,
                                       uc->parent,
                                       G_MAXINT,
                                       IDENTIFIERS_MC_ID,
                                       last_id,
                                       IDENTIFIERS_MC_UPDATE_ID,
                                       next_id,
                                       IDENTIFIERS_MC_DESCRIPTION,
                                       desc,
                                       IDENTIFIERS_MC_PATH_TO_ORIGINAL,
                                       NULL,
                                       IDENTIFIERS_MC_ID_MARKED_UP,
                                       displaytext,
                                       -1);
  }
  else
  {
    displaytext = g_strdup_printf ("%s", last_id);
    gtk_tree_store_insert_with_values (uc->ts,
                                       &iter,
                                       uc->parent,
                                       G_MAXINT,
                                       IDENTIFIERS_MC_ID,
                                       last_id,
                                       IDENTIFIERS_MC_UPDATE_ID,
                                       next_id,
                                       IDENTIFIERS_MC_DESCRIPTION,
                                       desc,
                                       IDENTIFIERS_MC_PATH_TO_ORIGINAL,
                                       NULL,
                                       IDENTIFIERS_MC_ID_MARKED_UP,
                                       displaytext,
                                       -1);
  }
  g_free (displaytext);

  if (NULL != spath)
    return;
  {
    GtkTreePath *path;
    char *gspath;
    path = gtk_tree_model_get_path (GTK_TREE_MODEL (uc->ts), &iter);
    spath = gtk_tree_path_to_string (path);
    gtk_tree_path_free (path);
    /* for the sake of consistency, only put GNUNET_free'able things into the
     * map */
    gspath = GNUNET_strdup (spath);
    if (GNUNET_SYSERR == GNUNET_CONTAINER_multihashmap_put (
          uc->seen,
          &hc,
          gspath,
          GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST))
      GNUNET_free (gspath);
  }

  sc.parent = &iter;
  sc.ts = uc->ts;
  sc.ns = uc->ns;
  sc.seen = uc->seen;
  sc.update_called = GNUNET_NO;
  {
    const struct GNUNET_IDENTITY_PrivateKey *pk;

    pk = GNUNET_IDENTITY_ego_get_private_key (uc->ns);
    if (GNUNET_IDENTITY_TYPE_ECDSA != pk->type)
    {
      GNUNET_break (0);
      return;
    }
    GNUNET_FS_namespace_list_updateable (GNUNET_FS_GTK_get_fs_handle (),
                                         &pk->ecdsa_key,
                                         next_id,
                                         &add_updateable_to_ts,
                                         &sc);
  }
  if ((sc.update_called == GNUNET_NO) && (NULL != next_id) &&
      (strlen (next_id) > 0))
  {
    /* add leaf */
    displaytext = g_strdup_printf ("<b>%s</b>", next_id);
    gtk_tree_store_insert_with_values (uc->ts,
                                       &titer,
                                       &iter,
                                       G_MAXINT,
                                       IDENTIFIERS_MC_ID,
                                       next_id,
                                       IDENTIFIERS_MC_UPDATE_ID,
                                       NULL,
                                       IDENTIFIERS_MC_DESCRIPTION,
                                       NULL,
                                       IDENTIFIERS_MC_PATH_TO_ORIGINAL,
                                       NULL,
                                       IDENTIFIERS_MC_ID_MARKED_UP,
                                       displaytext,
                                       -1);
    g_free (displaytext);
  }
}


/**
 * Called by a hashmap iterator.
 * Frees its argument with GNUNET_free().
 *
 * @param cls closure
 * @param key key from the map
 * @param value value from the map
 * @return #GNUNET_YES, always.
 */
static int
free_seen_paths (void *cls, const struct GNUNET_HashCode *key, void *value)
{
  GNUNET_free (value);
  return GNUNET_YES;
}


/**
 * User changed the selected namespace.  Check if it is valid,
 * and if so, update the rest of the dialog accordingly.
 *
 * @param combo the namespace selection combo box
 * @param user_data the `struct MainPublishingDialogContext`
 */
void
GNUNET_GTK_master_publish_dialog_ego_combobox_changed_cb (GtkComboBox *combo,
                                                          gpointer user_data)
{
  struct MainPublishingDialogContext *ctx = user_data;
  GtkTreeIter iter;
  GtkTreeStore *ts;
  GtkTreeModel *tm;
  struct GNUNET_IDENTITY_Ego *ego;
  struct UpdateableContext uc;

  ts = GTK_TREE_STORE (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_update_identifiers_treestore"));
  gtk_tree_store_clear (ts);
  if (! gtk_combo_box_get_active_iter (combo, &iter))
  {
    /* entry unselected, etc. */
    ctx->ns = NULL;
    gtk_widget_set_sensitive (ctx->identifier_entry, FALSE);
    gtk_widget_set_sensitive (ctx->updateable_checkbox, FALSE);
    gtk_widget_set_sensitive (ctx->update_id_entry, FALSE);
    update_selectivity_execute_cancel (ctx);
    return;
  }
  tm = GTK_TREE_MODEL (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_master_publish_dialog_ego_liststore"));
  gtk_tree_model_get (tm, &iter, EGO_MC_EGO, &ego, -1);
  if (NULL == ego)
  {
    GNUNET_break (0);
    gtk_widget_set_sensitive (ctx->identifier_entry, FALSE);
    gtk_widget_set_sensitive (ctx->updateable_checkbox, FALSE);
    gtk_widget_set_sensitive (ctx->update_id_entry, FALSE);
    update_selectivity_execute_cancel (ctx);
    return;
  }
  ctx->ns = ego;
  uc.parent = NULL;
  uc.ts = ts;
  uc.ns = ego;
  uc.update_called = GNUNET_NO;
  uc.seen = GNUNET_CONTAINER_multihashmap_create (128, GNUNET_NO);
  {
    const struct GNUNET_IDENTITY_PrivateKey *pk;

    pk = GNUNET_IDENTITY_ego_get_private_key (ego);
    if (GNUNET_IDENTITY_TYPE_ECDSA != pk->type)
    {
      GNUNET_break (0);
      gtk_widget_set_sensitive (ctx->identifier_entry, FALSE);
      gtk_widget_set_sensitive (ctx->updateable_checkbox, FALSE);
      gtk_widget_set_sensitive (ctx->update_id_entry, FALSE);
      update_selectivity_execute_cancel (ctx);
      return;
    }
    GNUNET_FS_namespace_list_updateable (GNUNET_FS_GTK_get_fs_handle (),
                                         &pk->ecdsa_key,
                                         NULL,
                                         &add_updateable_to_ts,
                                         &uc);
  }
  GNUNET_CONTAINER_multihashmap_iterate (uc.seen, &free_seen_paths, NULL);
  GNUNET_CONTAINER_multihashmap_destroy (uc.seen);
  gtk_widget_set_sensitive (ctx->identifier_entry, TRUE);
  gtk_widget_set_sensitive (ctx->updateable_checkbox, TRUE);
  gtk_widget_set_sensitive (ctx->update_id_entry, TRUE);
  update_selectivity_execute_cancel (ctx);
}


/**
 * Add all updateable entries of the current namespace to the
 * tree store.
 *
 * @param cls our `struct MainPublishingDialogContext`
 * @param ego identity of the namespace to add
 * @param ego_ctx where to store context data
 * @param name name of the namespace to add
 */
static void
add_namespace_to_ts (void *cls,
                     struct GNUNET_IDENTITY_Ego *ego,
                     void **ego_ctx,
                     const char *name)
{
  struct MainPublishingDialogContext *ctx = cls;
  GtkListStore *ls;
  GtkTreePath *path;
  GtkTreeRowReference *rr;
  GtkTreeIter iter;
  gboolean have_ns;

  if (NULL == ego)
    return; /* nothing to be done */
  ls = GTK_LIST_STORE (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_master_publish_dialog_ego_liststore"));
  rr = *ego_ctx;
  if (NULL == rr)
  {
    /* insert operation */
    GNUNET_assert (NULL != name);
    gtk_list_store_insert_with_values (ls,
                                       &iter,
                                       G_MAXINT,
                                       EGO_MC_NAME,
                                       name,
                                       EGO_MC_EGO,
                                       ego,
                                       -1);
    path = gtk_tree_model_get_path (GTK_TREE_MODEL (ls), &iter);
    rr = gtk_tree_row_reference_new (GTK_TREE_MODEL (ls), path);
    gtk_tree_path_free (path);
    *ego_ctx = rr;
  }
  else if (NULL == name)
  {
    /* delete operation */
    path = gtk_tree_row_reference_get_path (rr);
    gtk_tree_row_reference_free (rr);
    GNUNET_assert (gtk_tree_model_get_iter (GTK_TREE_MODEL (ls), &iter, path));
    gtk_tree_path_free (path);
    gtk_list_store_remove (ls, &iter);
    *ego_ctx = NULL;
  }
  else
  {
    /* rename operation */
    path = gtk_tree_row_reference_get_path (rr);
    GNUNET_assert (gtk_tree_model_get_iter (GTK_TREE_MODEL (ls), &iter, path));
    gtk_list_store_set (ls, &iter, G_MAXINT, EGO_MC_NAME, name, -1);
    gtk_tree_path_free (path);
  }
  have_ns = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (ls), &iter);
  gtk_widget_set_sensitive (ctx->own_checkbox, have_ns);
  gtk_widget_set_sensitive (ctx->identifier_entry, have_ns);
  gtk_widget_set_sensitive (ctx->updateable_checkbox, have_ns);
  gtk_widget_set_sensitive (ctx->update_id_entry, have_ns);
}


/**
 * Run the file-publishing operation (by opening the master publishing dialog).
 *
 * @param dummy widget that triggered the action
 * @param user_data builder of the main window
 */
void
GNUNET_GTK_main_menu_file_publish_activate_cb (GtkWidget *dummy,
                                               gpointer user_data)
{
  struct MainPublishingDialogContext *ctx;
  const struct GNUNET_CONFIGURATION_Handle *cfg;
  GtkWidget *toplevel;
  int updateable_enabled;
  int own_enabled;
  int global_enabled;

  ctx = GNUNET_new (struct MainPublishingDialogContext);
  ctx->builder =
    GNUNET_GTK_get_new_builder ("gnunet_fs_gtk_publish_dialog.glade", ctx);
  if (NULL == ctx->builder)
  {
    GNUNET_break (0);
    GNUNET_free (ctx);
    return;
  }

  /* initialize widget references */
  ctx->up_button = GTK_WIDGET (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_master_publish_dialog_up_button"));
  ctx->down_button = GTK_WIDGET (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_master_publish_dialog_down_button"));
  ctx->left_button = GTK_WIDGET (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_master_publish_dialog_left_button"));
  ctx->right_button = GTK_WIDGET (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_master_publish_dialog_right_button"));
  ctx->delete_button = GTK_WIDGET (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_master_publish_dialog_delete_button"));
  ctx->edit_button = GTK_WIDGET (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_master_publish_dialog_edit_button"));
  ctx->execute_button = GTK_WIDGET (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_master_publish_dialog_execute_button"));
  ctx->cancel_button = GTK_WIDGET (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_master_publish_dialog_cancel_button"));
  ctx->file_info_treeview = GTK_TREE_VIEW (gtk_builder_get_object (
                                             ctx->builder,
                                             "GNUNET_GTK_master_publish_dialog_file_information_tree_view"));
  ctx->file_info_selection =
    gtk_tree_view_get_selection (ctx->file_info_treeview);
  ctx->file_info_treemodel = gtk_tree_view_get_model (ctx->file_info_treeview);
  ctx->master_pubdialog = GTK_WINDOW (
    gtk_builder_get_object (ctx->builder, "GNUNET_GTK_master_publish_dialog"));

  ctx->global_checkbox = GTK_WIDGET (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_master_publish_dialog_global_checkbox"));
  ctx->own_checkbox = GTK_WIDGET (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_master_publish_dialog_own_checkbox"));
  ctx->updateable_checkbox = GTK_WIDGET (gtk_builder_get_object (
                                           ctx->builder,
                                           "GNUNET_GTK_master_publish_dialog_updateable_checkbox"));
  ctx->update_id_entry = GTK_WIDGET (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_master_publish_dialog_update_id_entry"));

  ctx->own_vbox = GTK_WIDGET (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_master_publish_dialog_own_vbox"));
  ctx->update_id_hbox = GTK_WIDGET (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_master_publish_dialog_update_id_hbox"));

  ctx->identifiers_expander = GTK_WIDGET (gtk_builder_get_object (
                                            ctx->builder,
                                            "GNUNET_GTK_master_publish_dialog_previous_identifiers_expander"));
  ctx->vpaned = GTK_WIDGET (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_master_publish_dialog_vpaned"));
  ctx->bottom_frame = GTK_WIDGET (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_master_publish_dialog_bottom_frame"));

  ctx->identifier_entry = GTK_WIDGET (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_master_publish_dialog_identifier_entry"));
  ctx->identifiers_scrolled = GTK_WIDGET (gtk_builder_get_object (
                                            ctx->builder,
                                            "GNUNET_GTK_master_publish_dialog_previous_identifiers_scrolled"));
  ctx->identifiers_treeview = GTK_TREE_VIEW (gtk_builder_get_object (
                                               ctx->builder,
                                               "GNUNET_GTK_master_publish_dialog_previous_identifiers_treeview"));
  ctx->identifiers_treemodel =
    gtk_tree_view_get_model (ctx->identifiers_treeview);
  ctx->identifiers_selection =
    gtk_tree_view_get_selection (ctx->identifiers_treeview);

  g_signal_connect (ctx->identifiers_expander,
                    "notify::expanded",
                    G_CALLBACK (expander_callback),
                    ctx);

  cfg = GNUNET_FS_GTK_get_configuration ();
  ctx->identity = GNUNET_IDENTITY_connect (cfg, &add_namespace_to_ts, ctx);

  updateable_enabled = GNUNET_CONFIGURATION_get_value_yesno (
    cfg,
    "gnunet-fs-gtk",
    "MAKE_UPDATEABLE_PUBLICATIONS_BY_DEFAULT");
  if (GNUNET_SYSERR == updateable_enabled)
    updateable_enabled = GNUNET_YES;
  own_enabled =
    GNUNET_CONFIGURATION_get_value_yesno (cfg,
                                          "gnunet-fs-gtk",
                                          "MAKE_NAMESPACE_PUBLICATIONS_BY_DEFAULT");
  if (GNUNET_SYSERR == own_enabled)
    own_enabled = GNUNET_NO;
  global_enabled =
    GNUNET_CONFIGURATION_get_value_yesno (cfg,
                                          "gnunet-fs-gtk",
                                          "MAKE_GLOBAL_PUBLICATIONS_BY_DEFAULT");
  if (GNUNET_SYSERR == global_enabled)
    global_enabled = GNUNET_YES;

  /* This will emit appropriate signals, their handlers will hide
   * parts of the dialog as needed.
   */
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ctx->updateable_checkbox),
                                GNUNET_YES == updateable_enabled);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ctx->own_checkbox),
                                GNUNET_YES == own_enabled);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ctx->global_checkbox),
                                GNUNET_YES == global_enabled);

  /* show dialog */
  toplevel = gtk_widget_get_toplevel (dummy);
  if (GTK_IS_WINDOW (toplevel))
    gtk_window_set_transient_for (GTK_WINDOW (ctx->master_pubdialog),
                                  GTK_WINDOW (toplevel));
  gtk_window_present (GTK_WINDOW (ctx->master_pubdialog));
}


/* end of gnunet-fs-gtk_publish-dialog.c */
