/*
     This file is part of GNUnet
     Copyright (C) 2012, 2013, 2014, 2018 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/namestore/gnunet-namestore-gtk.c
 * @author Christian Grothoff
 * @brief edit GNS zones
 */
#include "gnunet_gtk.h"
#include "gnunet_gtk_namestore_plugin.h"
#include <gmodule.h>
#include <gnunet/gnunet_gnsrecord_lib.h>
#include <gnunet/gnunet_gns_service.h>
#include <gnunet/gnunet_identity_service.h>
#include <gnunet/gnunet_namestore_service.h>

/**
 * Text we use for the 'name' entry for the user to select
 * for creating a new name.
 */
#define NEW_ZONE_STR gettext_noop ("<new zone>")

/**
 * Text we use for the 'name' entry for the user to select
 * for creating a new name.
 */
#define NEW_NAME_STR gettext_noop ("<new name>")

/**
 * Text we use for the 'type' selection for the user to
 * select when adding a new record.
 */
#define NEW_RECORD_STR gettext_noop ("<new record>")

/**
 * Text we use for the expiration to mean 'never expires'.
 */
#define EXPIRE_NEVER_STRING gettext_noop ("never")

/**
 * Text we use for invalid values.
 */
#define EXPIRE_INVALID_STRING gettext_noop ("invalid")

/**
 * Height and width of the QR code we display
 */
#define QRCODE_IMAGE_SIZE 64

/**
 * How many notifications do we allow the namestore to send us
 * before we need to start to catch up?
 */
#define NAMESTORE_MONITOR_WINDOW_SIZE 50

/**
 * Columns in the gns model.
 */
enum GNSTreestoreColumn
{
  /**
   * A gchararray with the value for the 'name' column.
   */
  GNS_TREESTORE_COL_NAME = 0,

  /**
   * A gboolean, TRUE if the record is public, FALSE if it is private.
   */
  GNS_TREESTORE_COL_IS_PUBLIC,

  /**
   * A guint with the record type (numeric value)
   */
  GNS_TREESTORE_COL_RECORD_TYPE,

  /**
   * A gchararray with the record type (human-readable string)
   */
  GNS_TREESTORE_COL_RECORD_TYPE_AS_STR,

  /**
   * A guint64 with the expiration time (relative or absolute)
   */
  GNS_TREESTORE_COL_EXP_TIME,

  /**
   * A gboolean, TRUE if the expiration time is relative.
   */
  GNS_TREESTORE_COL_EXP_TIME_IS_REL,

  /**
   * A gchararray with the expiration time as a human-readable string.
   */
  GNS_TREESTORE_COL_EXP_TIME_AS_STR,

  /**
   * A gchararray with the value of the record as a human-readable string.
   */
  GNS_TREESTORE_COL_VAL_AS_STR,

  /**
   * A gchararray with the background color to use for the value.
   */
  GNS_TREESTORE_COL_VAL_COLOR,

  /**
   * A gboolean; TRUE if the 'name' column should be shown,
   * FALSE for the lines with the individual records under a name.
   */
  GNS_TREESTORE_COL_NAME_IS_VISIBLE,

  /**
   * A gboolean, TRUE if this row is for editing a record,
   * FALSE if the 'public', 'type', 'expiration' and 'value'
   * columns should be hidden.
   */
  GNS_TREESTORE_COL_IS_RECORD_ROW,

  /**
   * A gboolean, FALSE if this is one of our 'dummy' rows that
   * is used to create a new name or record, TRUE if this is
   * a normal row with either a name or a record.
   */
  GNS_TREESTORE_COL_NOT_DUMMY_ROW,

  /**
   * A gchararray with the name of the color to use for the
   * expiration column.
   */
  GNS_TREESTORE_COL_EXP_TIME_COLOR,

  /**
   * A gchararray with the name of the color to use for the
   * name column.
   */
  GNS_TREESTORE_COL_NAME_COLOR,

  /**
   * A gboolean; TRUE if the 'type' column can still be changed;
   * FALSE once we have edited the value.
   */
  GNS_TREESTORE_COL_TYPE_IS_EDITABLE,

  /**
   * A gboolean; TRUE if the value is a shadow record.
   */
  GNS_TREESTORE_COL_IS_SHADOW,

  /**
   * A gboolean; TRUE if the name is editable (dummy line).
   */
  GNS_TREESTORE_COL_NAME_IS_EDITABLE,

  /**
   * A guint; offset of this record in the array.
   */
  GNS_TREESTORE_COL_RECORD_OFFSET

};


/**
 * Columns in the gns type model.
 */
enum LIST_COLUMNS
{

  /**
   * A guint
   */
  GNS_TYPE_TO_NAME_LISTSTORE_COLUMN_TYPE = 0,


  /**
   * A gchararray
   */
  GNS_TYPE_TO_NAME_LISTSTORE_COLUMN_TYPENAME
};


/**
 * Columns in the zone list store.
 */
enum ZONE_COLUMNS
{

  /**
   * A gchararray
   */
  ZONE_LS_NAME = 0,


  /**
   * A `struct GNUNET_IDENTITY_Ego`
   */
  ZONE_LS_EGO = 1
};


/**
 * Closure for #operation_done_cont().
 */
struct OperationContext
{

  /**
   * Kept in a DLL.
   */
  struct OperationContext *next;

  /**
   * Kept in a DLL.
   */
  struct OperationContext *prev;

  /**
   * Associated namestore operation.
   */
  struct GNUNET_NAMESTORE_QueueEntry *qe;
};


/**
 * Closure for #merge_with_existing_records().
 */
struct MoveOperationContext
{

  /**
   * Kept in a DLL.
   */
  struct MoveOperationContext *next;

  /**
   * Kept in a DLL.
   */
  struct MoveOperationContext *prev;

  /**
   * Associated namestore operation.
   */
  struct GNUNET_NAMESTORE_ZoneIterator *it;

  /**
   * Info from editing dialog.
   */
  struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc;

  /**
   * Private key of target zone.
   */
  struct GNUNET_IDENTITY_PrivateKey pk;

  /**


   * Data to free.
   */
  void *data;

  /**
   * Record to merge.
   */
  struct GNUNET_GNSRECORD_Data rd;
};


/**
 * Information we keep per name.
 */
struct RecordInfo
{

  /**
   * Name for this record.
   */
  char *name;

  /**
   * Location of this record in the tree view.
   */
  GtkTreeRowReference *rr;

  /**
   * Serialized records under this name.
   */
  void *data;

  /**
   * Number of bytes in @e data.
   */
  size_t data_size;

  /**
   * Number of records serialized in @e data.
   */
  unsigned int rd_count;
};


/**
 * Representation of a TLD, mapping the respective TLD string
 * (i.e. ".gnu") to the respective public key of the zone.
 */
struct GNS_TopLevelDomain
{

  /**
   * Kept in a DLL, as there are unlikely enough of these to
   * warrant a hash map.
   */
  struct GNS_TopLevelDomain *next;

  /**
   * Kept in a DLL, as there are unlikely enough of these to
   * warrant a hash map.
   */
  struct GNS_TopLevelDomain *prev;

  /**
   * Public key associated with the @a tld.
   */
  struct GNUNET_CRYPTO_EddsaPublicKey pkey;

  /**
   * Top-level domain as a string, including leading ".".
   */
  char *tld;
};


/**
 * Head of DLL of TLDs we map to GNS zones.
 */
static struct GNS_TopLevelDomain *tld_head;

/**
 * Tail of DLL of TLDs we map to GNS zones.
 */
static struct GNS_TopLevelDomain *tld_tail;

/**
 * When do NICK records expire?
 */
static struct GNUNET_TIME_Relative nick_expiration_time;

/**
 * Hash map from the H(name) in the zone to the 'struct RecordInfo'
 * for the respective entry in the tree view.
 */
static struct GNUNET_CONTAINER_MultiHashMap *n2r;

/**
 * Our current zone monitor.
 */
static struct GNUNET_NAMESTORE_ZoneMonitor *zmon;

/**
 * Head of linked list of active operations.
 */
static struct OperationContext *oc_head;

/**
 * Tail of linked list of active operations.
 */
static struct OperationContext *oc_tail;

/**
 * Head of linked list of active operations.
 */
static struct MoveOperationContext *moc_head;

/**
 * Tail of linked list of active operations.
 */
static struct MoveOperationContext *moc_tail;

/**
 * Handle to the namestore.
 */
static struct GNUNET_NAMESTORE_Handle *namestore;

/**
 * Tree store with all of the values we're modifying for GNS.
 */
static GtkTreeStore *ts;

/**
 * Tree model (same object as 'ts', just different type).
 */
static GtkTreeModel *tm;

/**
 * The main tree view for 'gns' that shows the records.
 */
static GtkTreeView *tv;

/**
 * Private key of the zone we are currently editing.
 */
static const struct GNUNET_IDENTITY_PrivateKey *pkey;

/**
 * Public key of the zone we are currently editing.
 */
static struct GNUNET_IDENTITY_PublicKey pubkey;

/**
 * Pseudonym of the current zone we are editing.
 */
static char *current_pseudonym;

/**
 * Currently selected entry in #zone_liststore.
 */
static GtkTreeIter zone_iter;

/**
 * List of all known zones/egos.
 */
static GtkListStore *zone_liststore;

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

/**
 * Main loop handle.
 */
static struct GNUNET_GTK_MainLoop *ml;

/**
 * Main window.
 */
static GtkWidget *main_window;

/**
 * Status label in main window.
 */
static GtkLabel *status_label;

/**
 * Zone combo box in the main window.
 */
static GtkComboBox *zone_combo_box;

/**
 * Our configuration.
 */
static const struct GNUNET_CONFIGURATION_Handle *cfg;

/**
 * Ongoing identity operation.
 */
static struct GNUNET_IDENTITY_Operation *iop;

/**
 * Global return value (for success/failure of gnunet-setup).
 */
static int gret;


/**
 * Get an object from the main window.
 *
 * @param name name of the object
 * @return NULL on error, otherwise the object
 */
static GObject *
get_object (const char *name)
{
  if (NULL == ml)
    return NULL;
  return GNUNET_GTK_main_loop_get_object (ml, name);
}


#if HAVE_QRENCODE_H
#include <qrencode.h>
#include <gdk-pixbuf/gdk-pixbuf.h>

/**
 * Create the QR code image for our zone.
 *
 * @param scale factor for scaling up the size of the image to create
 * @return NULL on error
 */
static GdkPixbuf *
create_qrcode (unsigned int scale)
{
  QRinput *qri;
  QRcode *qrc;
  char *str;
  GdkPixbuf *pb;
  guchar *pixels;
  int n_channels;
  const char *dir;
  char *fn;
  unsigned int size;
  char *upper;

  qri = QRinput_new2 (0, QR_ECLEVEL_M);
  if (NULL == qri)
  {
    GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "QRinput_new2");
    return NULL;
  }
  GNUNET_asprintf (&str,
                   "gnunet://gns/%s/%s",
                   GNUNET_GNSRECORD_z2s (&pubkey),
                   current_pseudonym);
  upper = GNUNET_strdup (str);
  GNUNET_STRINGS_utf8_toupper (str, upper);
  /* first try encoding as uppercase-only alpha-numerical
     QR code (much smaller encoding); if that fails, also
     try using binary encoding (in case nick contains
     special characters). */
  if ((0 != QRinput_append (qri,
                            QR_MODE_AN,
                            strlen (upper),
                            (unsigned char *) upper)) &&
      (0 !=
       QRinput_append (qri, QR_MODE_8, strlen (str), (unsigned char *) str)))
  {
    GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "QRinput_append");
    GNUNET_free (upper);
    GNUNET_free (str);
    return NULL;
  }
  GNUNET_free (upper);
  GNUNET_free (str);
  qrc = QRcode_encodeInput (qri);
  if (NULL == qrc)
  {
    GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "QRcode_encodeInput");
    QRinput_free (qri);
    return NULL;
  }
  /* We use a trick to create a pixbuf in a way that works for both Gtk2 and
     Gtk3 by loading a dummy file from disk; all other methods are not portable
     to both Gtk2 and Gtk3. */
  dir = GNUNET_GTK_get_data_dir ();
  GNUNET_asprintf (&fn, "%s%s", dir, "qr_dummy.png");
  size = qrc->width * scale;
  size += 8 - (size % 8);
  pb = gdk_pixbuf_new_from_file_at_size (fn, size, size, NULL);
  GNUNET_free (fn);
  if (NULL == pb)
  {
    QRcode_free (qrc);
    QRinput_free (qri);
    return NULL;
  }
  pixels = gdk_pixbuf_get_pixels (pb);
  n_channels = gdk_pixbuf_get_n_channels (pb);
  for (unsigned int x = 0; x < size; x++)
    for (unsigned int y = 0; y < size; y++)
    {
      unsigned int off =
        (x * qrc->width / size) + (y * qrc->width / size) * qrc->width;
      for (int c = 0; c < n_channels; c++)
        pixels[(y * size + x) * n_channels + c] =
          (0 == (qrc->data[off] & 1)) ? 0xFF : 0;
    }
  QRcode_free (qrc);
  QRinput_free (qri);
  return pb;
}


/**
 * Create the QR code image for our zone.
 */
static void
setup_qrcode ()
{
  GdkPixbuf *pb;
  GtkImage *image;
  GdkScreen *screen;
  GtkSettings *settings;
  gint dpi;
  int scale;

  image = GTK_IMAGE (get_object ("gnunet_namestore_gtk_qr_image"));
  if (NULL == image)
  {
    GNUNET_break (0);
    return;
  }
  /* adjust scale to screen resolution */
  screen = gtk_widget_get_screen (GTK_WIDGET (image));
  settings = gtk_settings_get_for_screen (screen);
  g_object_get (G_OBJECT (settings), "gtk-xft-dpi", &dpi, NULL);
  if (-1 == dpi)
    scale = 2;
  else if (dpi >= 122800)
    scale = 4;
  else if (dpi >= 98304)
    scale = 3;
  else
    scale = 2;
  pb = create_qrcode (scale);
  if (NULL == pb)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                _ ("Failed to initialize QR-code pixbuf\n"));
    return;
  }
  gtk_image_set_from_pixbuf (image, pb);
  g_object_unref (pb);
}


#endif


/**
 * Function called upon completion of the qr-code 'save as' dialog.
 *
 * @param dialog the dialog
 * @param response_id reason for the dialog closing
 * @param user_data the `GtkBuilder` we used to create the dialog
 */
void
gnunet_namestore_gtk_qr_save_as_dialog_response_cb (GtkDialog *dialog,
                                                    gint response_id,
                                                    gpointer user_data)
{
#if HAVE_QRENCODE_H
  GtkBuilder *builder = user_data;
  GdkPixbuf *pb;
  char *filename;

  if (GTK_RESPONSE_OK != response_id)
  {
    gtk_widget_destroy (GTK_WIDGET (dialog));
    g_object_unref (G_OBJECT (builder));
    return;
  }
  filename =
    GNUNET_GTK_filechooser_get_filename_utf8 (GTK_FILE_CHOOSER (dialog));
  pb = create_qrcode (8);
  if (NULL == pb)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                _ ("Failed to initialize QR-code pixbuf"));
    return;
  }
  gdk_pixbuf_save (pb, filename, "png", NULL, NULL);
  g_free (filename);
  g_object_unref (pb);
  gtk_widget_destroy (GTK_WIDGET (dialog));
  g_object_unref (G_OBJECT (builder));
#else
  GNUNET_break (0);
#endif
}


/**
 * User clicked on 'save as' to extract the QR code.  Open 'save as'
 * dialog to get the desired filename and file type.
 *
 * @param button the 'save as' button
 * @param user_data unused
 */
void
gnunet_namestore_gtk_qr_saveas_button_clicked_cb (GtkButton *button,
                                                  gpointer user_data)
{
  GtkBuilder *builder;
  GtkWindow *dialog;
  char *suggestion;

  builder =
    GNUNET_GTK_get_new_builder ("gnunet_namestore_gtk_qr_save_as_dialog.glade",
                                NULL);
  if (NULL == builder)
  {
    GNUNET_break (0);
    return;
  }
  GNUNET_asprintf (&suggestion, "%s.png", current_pseudonym);
  dialog = GTK_WINDOW (
    gtk_builder_get_object (builder, "gnunet_namestore_gtk_qr_save_as_dialog"));
  gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), suggestion);
  GNUNET_free (suggestion);
  gtk_window_present (dialog);
}


/**
 * Check if adding a record of the given type is Ok given the other
 * records already present for the given name.
 *
 * @param rd_count size of the @a rd array
 * @param rd existing records
 * @param n_type new record to be added
 * @return #GNUNET_OK if adding this record is OK, #GNUNET_NO if not
 */
static int
check_record_permitted (unsigned int rd_count,
                        const struct GNUNET_GNSRECORD_Data *rd,
                        gint n_type)
{
  unsigned int nick;

  nick = 0;
  for (unsigned int i = 0; i < rd_count; i++)
  {
    switch (rd[i].record_type)
    {
    case GNUNET_DNSPARSER_TYPE_CNAME:
      return GNUNET_NO;
    case GNUNET_GNSRECORD_TYPE_PKEY:
      return GNUNET_NO;
    case GNUNET_GNSRECORD_TYPE_GNS2DNS:
      return GNUNET_NO;
    case GNUNET_GNSRECORD_TYPE_NICK:
      nick++;
      break;
    default:
      break;
    }
  }
  rd_count -= nick;
  if (0 == rd_count)
    return GNUNET_OK;
  switch (n_type)
  {
  case GNUNET_DNSPARSER_TYPE_CNAME:
    return GNUNET_NO;
  case GNUNET_GNSRECORD_TYPE_PKEY:
    return GNUNET_NO;
  case GNUNET_GNSRECORD_TYPE_GNS2DNS:
    return GNUNET_NO;
  default:
    break;
  }
  return GNUNET_OK;
}


/**
 * Function called upon completion of an operation.
 *
 * @param cls the `struct OperationContext` of the operation that completed
 * @param ec the error code
 */
static void
operation_done_cont (void *cls, enum GNUNET_ErrorCode ec)
{
  struct OperationContext *oc = cls;
  GtkWidget *dialog;

  oc->qe = NULL;
  GNUNET_CONTAINER_DLL_remove (oc_head, oc_tail, oc);
  if (GNUNET_EC_NONE != ec)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Operation failed: `%s'\n"),
                GNUNET_ErrorCode_get_hint (ec));
    dialog = gtk_message_dialog_new (GTK_WINDOW (main_window),
                                     GTK_DIALOG_DESTROY_WITH_PARENT,
                                     GTK_MESSAGE_ERROR,
                                     GTK_BUTTONS_CLOSE,
                                     _ ("Operation failed: `%s'\n"),
                                     GNUNET_ErrorCode_get_hint (ec));
    g_signal_connect_swapped (dialog,
                              "response",
                              G_CALLBACK (gtk_widget_destroy),
                              dialog);
    gtk_widget_show_all (dialog);
  }
  GNUNET_free (oc);
}


/**
 * Release the record info.
 *
 * @param cls NULL
 * @param key key for @a value in the #n2r map
 * @param value a RecordInfo to release
 * @return #GNUNET_OK (continue to iterate)
 */
static int
release_ri (void *cls, const struct GNUNET_HashCode *key, void *value)
{
  struct RecordInfo *ri = value;
  (void) cls;

  gtk_tree_row_reference_free (ri->rr);
  GNUNET_free (ri->data);
  GNUNET_free (ri->name);
  GNUNET_break (GNUNET_YES ==
                GNUNET_CONTAINER_multihashmap_remove (n2r, key, ri));
  GNUNET_free (ri);
  return GNUNET_OK;
}


/**
 * Clear all entries in the zone view and hide the tree view.
 */
static void
clear_zone_view ()
{
  GNUNET_free (current_pseudonym);
  current_pseudonym = NULL;
  GNUNET_CONTAINER_multihashmap_iterate (n2r, &release_ri, NULL);
  gtk_widget_hide (GTK_WIDGET (zone_combo_box));
  gtk_widget_show (GTK_WIDGET (status_label));
  gtk_widget_hide (
    GTK_WIDGET (get_object ("gnunet_namestore_gtk_scrolledwindow")));
  gtk_widget_hide (GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_image")));
  gtk_widget_hide (
    GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_saveas_button")));
  gtk_widget_hide (
    GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_vseparator")));
  gtk_tree_store_clear (ts);
}


/**
 * Display an error message for the user.
 *
 * @param title title of the error message
 * @param emsg error message to show
 */
static void
show_error_message (const char *title, const char *emsg)
{
  GtkDialog *dialog;

  /* TODO: consider replacing with widget in the main window */
  dialog = GTK_DIALOG (gtk_message_dialog_new (GTK_WINDOW (main_window),
                                               GTK_DIALOG_DESTROY_WITH_PARENT,
                                               GTK_MESSAGE_ERROR,
                                               GTK_BUTTONS_CLOSE,
                                               _ ("%s\n%s\n"),
                                               title,
                                               emsg));
  g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL);
  gtk_widget_show_all (GTK_WIDGET (dialog));
}


/**
 * Release resources of an edit dialog context.
 *
 * @param edc resources to free
 */
static void
free_edit_dialog_context (struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc)
{
  g_free (edc->name);
  g_free (edc->n_value);
  g_free (edc->new_zone_option);
  GNUNET_free (edc);
}


/**
 * Process a record that was stored in the namestore.
 *
 * @param cls closure
 * @param rd_count number of entries in @a rd array
 * @param rd array of records with data to store
 */
static void
merge_with_existing_records (void *cls,
                             unsigned int rd_count,
                             const struct GNUNET_GNSRECORD_Data *rd)
{
  struct MoveOperationContext *moc = cls;
  struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc = moc->edc;
  struct GNUNET_GNSRECORD_Data rd_new[rd_count + 1];
  struct OperationContext *oc;

  GNUNET_CONTAINER_DLL_remove (moc_head, moc_tail, moc);
  if (GNUNET_OK != check_record_permitted (rd_count, rd, edc->record_type))
  {
    show_error_message (
      _ ("Record combination not permitted"),
      _ (
        "Given the existing records, adding a new record of this type is not allowed."));
    GNUNET_free (moc->data);
    GNUNET_free (moc);
    free_edit_dialog_context (edc);
    return;
  }

  memcpy (rd_new, rd, rd_count * sizeof (struct GNUNET_GNSRECORD_Data));
  rd_new[rd_count] = moc->rd;
  /* FIXME: sanity-check merge... */
  oc = GNUNET_new (struct OperationContext);
  GNUNET_CONTAINER_DLL_insert (oc_head, oc_tail, oc);
  oc->qe = GNUNET_NAMESTORE_records_store (namestore,
                                           &moc->pk,
                                           edc->name,
                                           rd_count + 1,
                                           rd_new,
                                           &operation_done_cont,
                                           oc);
  GNUNET_free (moc->data);
  GNUNET_free (moc);
  free_edit_dialog_context (edc);
}


/**
 * Process an encrypted that was stored in the namestore for
 * merging.
 *
 * @param cls closure with the `struct MoveOperationContext`
 * @param zone private key of the zone; NULL on disconnect
 * @param label label of the records; NULL on disconnect
 * @param rd_count number of entries in @a rd array, 0 if label was deleted
 * @param rd array of records with data to store
 */
static void
handle_records_for_merge (void *cls,
                          const struct GNUNET_IDENTITY_PrivateKey *zone,
                          const char *label,
                          unsigned int rd_count,
                          const struct GNUNET_GNSRECORD_Data *rd)
{
  struct MoveOperationContext *moc = cls;

  if (0 != strcmp (label, moc->edc->name))
  {
    GNUNET_NAMESTORE_zone_iterator_next (moc->it, 1);
    return;
  }
  GNUNET_NAMESTORE_zone_iteration_stop (moc->it);
  moc->it = NULL;
  merge_with_existing_records (moc, rd_count, rd);
}


/**
 * Error communicating with database, complain to user.
 *
 * @param cls the `struct MoveOperationContext`
 */
static void
handle_records_for_merge_error (void *cls)
{
  struct MoveOperationContext *moc = cls;
  struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc = moc->edc;

  show_error_message (_ ("Failed to communicate with database"),
                      _ ("Check that your peer is configured correctly."));
  GNUNET_free (moc->data);
  GNUNET_free (moc);
  free_edit_dialog_context (edc);
}


/**
 * Finished zone iteration.  Proceed.
 *
 * @param cls the `struct MoveOperationContext`
 */
static void
handle_records_for_merge_end (void *cls)
{
  struct MoveOperationContext *moc = cls;

  moc->it = NULL;
  merge_with_existing_records (moc, 0, NULL);
}


/**
 * The edit dialog completed; update the namestore and the
 * view based on the new values in @a edc.
 *
 * @param edc editing context information
 * @param ret return code of the dialog
 */
static void
edit_dialog_continuation (struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc,
                          GtkResponseType ret)
{
  struct RecordInfo *ri = edc->ri;
  unsigned int rd_count = (NULL == ri) ? 0 : ri->rd_count;
  struct GNUNET_GNSRECORD_Data rd_old[rd_count];
  struct GNUNET_GNSRECORD_Data rd;
  void *data;
  size_t data_size;
  struct OperationContext *oc;

  if (NULL != edc->plugin)
  {
    GNUNET_PLUGIN_unload (edc->liblow, edc->plugin);
    edc->plugin = NULL;
  }
  if (NULL != edc->liblow)
  {
    GNUNET_free (edc->liblow);
    edc->liblow = NULL;
  }
  if ((NULL != ri) &&
      (GNUNET_OK != GNUNET_GNSRECORD_records_deserialize (ri->data_size,
                                                          ri->data,
                                                          ri->rd_count,
                                                          rd_old)))
  {
    GNUNET_break (0);
    free_edit_dialog_context (edc);
    return;
  }
  if ((GTK_RESPONSE_CANCEL == ret) || (GTK_RESPONSE_DELETE_EVENT == ret))
  {
    free_edit_dialog_context (edc);
    return;
  }
  if (GNUNET_OK != GNUNET_GNSRECORD_string_to_value (edc->record_type,
                                                     edc->n_value,
                                                     &data,
                                                     &data_size))
  {
    GNUNET_break (0);
    free_edit_dialog_context (edc);
    return;
  }
  if (edc->n_public)
    rd.flags = GNUNET_GNSRECORD_RF_NONE;
  else
    rd.flags = GNUNET_GNSRECORD_RF_PRIVATE;
  if (edc->n_is_shadow)
    rd.flags |= GNUNET_GNSRECORD_RF_SHADOW;
  rd.record_type = edc->record_type;
  rd.expiration_time = edc->n_exp_time;
  if (edc->n_is_relative)
    rd.flags |= GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION;
  rd.data_size = data_size;
  rd.data = data;
  switch (ret)
  {
  case GTK_RESPONSE_REJECT: /* code for 'delete' */
    if (GNUNET_YES == edc->old_record_in_namestore)
    {
      /* remove item from tree view and namestore */
      struct GNUNET_GNSRECORD_Data rd_new[rd_count - 1];

      GNUNET_assert (NULL != ri);
      memcpy (rd_new,
              rd_old,
              (rd_count - 1) * sizeof (struct GNUNET_GNSRECORD_Data));
      rd_new[edc->off] = rd_old[rd_count - 1];
      oc = GNUNET_new (struct OperationContext);
      GNUNET_CONTAINER_DLL_insert (oc_head, oc_tail, oc);
      oc->qe = GNUNET_NAMESTORE_records_store (namestore,
                                               pkey,
                                               edc->name,
                                               rd_count - 1,
                                               rd_new,
                                               &operation_done_cont,
                                               oc);
    }
    else
    {
      /* invalid choice, 'delete' should have been hidden... */
      GNUNET_break (0);
    }
    break;
  case GTK_RESPONSE_OK:
    /* update model */
    if (0 == strcmp (edc->new_zone_option, current_pseudonym))
    {
      if (GNUNET_YES == edc->old_record_in_namestore)
      {
        struct GNUNET_GNSRECORD_Data rd_new[rd_count];

        GNUNET_assert (NULL != ri);
        memcpy (rd_new,
                rd_old,
                rd_count * sizeof (struct GNUNET_GNSRECORD_Data));
        rd_new[edc->off] = rd;
        oc = GNUNET_new (struct OperationContext);
        GNUNET_CONTAINER_DLL_insert (oc_head, oc_tail, oc);
        oc->qe = GNUNET_NAMESTORE_records_store (namestore,
                                                 pkey,
                                                 edc->name,
                                                 rd_count,
                                                 rd_new,
                                                 &operation_done_cont,
                                                 oc);
      }
      else
      {
        struct GNUNET_GNSRECORD_Data rd_new[rd_count + 1];

        memcpy (rd_new,
                rd_old,
                rd_count * sizeof (struct GNUNET_GNSRECORD_Data));
        rd_new[rd_count] = rd;
        oc = GNUNET_new (struct OperationContext);
        GNUNET_CONTAINER_DLL_insert (oc_head, oc_tail, oc);
        oc->qe = GNUNET_NAMESTORE_records_store (namestore,
                                                 pkey,
                                                 edc->name,
                                                 rd_count + 1,
                                                 rd_new,
                                                 &operation_done_cont,
                                                 oc);
      }
    }
    else
    {
      const struct GNUNET_IDENTITY_PrivateKey *pk;
      struct MoveOperationContext *moc;
      struct GNUNET_HashCode query;

      /* determine target zone */
      GNUNET_assert (NULL != edc->ego);
      pk = GNUNET_IDENTITY_ego_get_private_key (edc->ego);
      GNUNET_assert (NULL != pk);
      GNUNET_GNSRECORD_query_from_private_key (pk, edc->name, &query);
      moc = GNUNET_new (struct MoveOperationContext);
      moc->data = data;
      moc->rd = rd;
      moc->edc = edc;
      moc->pk = *pk;
      GNUNET_CONTAINER_DLL_insert (moc_head, moc_tail, moc);
      moc->it =
        GNUNET_NAMESTORE_zone_iteration_start (namestore,
                                               pk,
                                               &handle_records_for_merge_error,
                                               moc,
                                               &handle_records_for_merge,
                                               moc,
                                               &handle_records_for_merge_end,
                                               moc);
      /* zone changed, remove record from old zone, add to new zone! */
      if (GNUNET_YES == edc->old_record_in_namestore)
      {
        /* remove item from tree view and namestore */
        struct GNUNET_GNSRECORD_Data rd_new[rd_count - 1];

        GNUNET_assert (NULL != ri);
        memcpy (rd_new,
                rd_old,
                (rd_count - 1) * sizeof (struct GNUNET_GNSRECORD_Data));
        rd_new[edc->off] = rd_old[rd_count - 1];
        oc = GNUNET_new (struct OperationContext);
        GNUNET_CONTAINER_DLL_insert (oc_head, oc_tail, oc);
        oc->qe = GNUNET_NAMESTORE_records_store (namestore,
                                                 pkey,
                                                 edc->name,
                                                 rd_count - 1,
                                                 rd_new,
                                                 &operation_done_cont,
                                                 oc);
      }
      return;
    }
    break;
  default:
    GNUNET_break (0);
    break;
  }
  GNUNET_free (data);
  free_edit_dialog_context (edc);
}


/**
 * Disable 'save' button, dialog state is not acceptable.
 *
 * @param edc dialog to modify
 */
static void
edit_dialog_disable_save (struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc)
{
  gtk_widget_set_sensitive (GTK_WIDGET (
                              gtk_builder_get_object (edc->builder,
                                                      "edit_dialog_save_button")),
                            FALSE);
}


/**
 * Enable 'save' button, dialog state is acceptable.
 *
 * @param edc dialog to modify
 */
static void
edit_dialog_enable_save (struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc)
{
  gtk_widget_set_sensitive (GTK_WIDGET (
                              gtk_builder_get_object (edc->builder,
                                                      "edit_dialog_save_button")),
                            TRUE);
}


/**
 * Function that should be called by the plugin whenever values in
 * the dialog were edited.  It will check the validity of the dialog
 * and update the "save" button accordingly.
 *
 * @param edc the plugin environment
 */
static void
check_validity (struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc)
{

  GtkEditable *entry;
  const gchar *name;

  /* check name */
  entry = GTK_EDITABLE (
    gtk_builder_get_object (edc->builder, "edit_dialog_name_entry"));
  name = gtk_editable_get_chars (entry, 0, -1);
  if ((GNUNET_OK != edc->plugin->validate (edc->plugin->cls, edc->builder)) ||
      ((GNUNET_SYSERR == GNUNET_DNSPARSER_check_label (name)) &&
       (0 != strcmp (name, GNUNET_GNS_EMPTY_LABEL_AT))))
  {
    edit_dialog_disable_save (edc);
    return;
  }
  edit_dialog_enable_save (edc);
}


/**
 * Setup the zone combobox.
 *
 * @param edc dialog to setup the combo box for
 */
static void
setup_zone (struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc)
{
  GtkTreeIter iter;
  GtkComboBox *cb;
  gchar *name;

  cb = GTK_COMBO_BOX (
    gtk_builder_get_object (edc->builder, "edit_dialog_zone_combobox"));

  gtk_combo_box_set_model (cb, GTK_TREE_MODEL (edc->zone_liststore));
  GNUNET_assert (
    gtk_tree_model_get_iter_first (GTK_TREE_MODEL (edc->zone_liststore),
                                   &iter));
  do
  {
    gtk_tree_model_get (GTK_TREE_MODEL (edc->zone_liststore),
                        &iter,
                        ZONE_LS_NAME,
                        &name,
                        -1);
    if (0 == strcmp (name, edc->new_zone_option))
    {
      g_free (name);
      break;
    }
    g_free (name);
  } while (
    gtk_tree_model_iter_next (GTK_TREE_MODEL (edc->zone_liststore), &iter));
  gtk_combo_box_set_active_iter (cb, &iter);
}


/**
 * Initialize widgets of the edit dialog that are the same regardless of
 * the type of the record.
 *
 * @param edc dialog context
 */
static void
edit_dialog_setup_common_elements (
  struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc)
{
  GtkComboBox *cb;
  GtkListStore *ls;
  GtkTreeIter iter;
  struct GNUNET_TIME_Absolute at;
  struct GNUNET_TIME_Relative rt;
  time_t tp;
  struct tm *ymd;
  GtkCalendar *cal;

  if (GNUNET_YES != edc->old_record_in_namestore)
  {
    gtk_widget_hide (GTK_WIDGET (
                       gtk_builder_get_object (edc->builder,
                                               "edit_dialog_delete_button")));
    edit_dialog_disable_save (edc);
  }
  gtk_entry_set_text (GTK_ENTRY (
                        gtk_builder_get_object (edc->builder,
                                                "edit_dialog_name_entry")),
                      edc->name);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gtk_builder_get_object (
                                                     edc->builder,
                                                     "edit_dialog_options_public_checkbutton")),
                                edc->n_public);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gtk_builder_get_object (
                                                     edc->builder,
                                                     "edit_dialog_options_shadow_checkbutton")),
                                edc->n_is_shadow);

  if (GNUNET_TIME_UNIT_FOREVER_REL.rel_value_us == edc->n_exp_time)
  {
    gtk_toggle_button_set_active (
      GTK_TOGGLE_BUTTON (
        gtk_builder_get_object (edc->builder,
                                "edit_dialog_expiration_never_radiobutton")),
      TRUE);
    gtk_widget_hide (GTK_WIDGET (
                       gtk_builder_get_object (edc->builder,
                                               "edit_dialog_expiration_absolute_calendar")));
    gtk_widget_hide (GTK_WIDGET (
                       gtk_builder_get_object (edc->builder,
                                               "edit_dialog_expiration_absolute_hbox")));
    gtk_widget_hide (GTK_WIDGET (
                       gtk_builder_get_object (edc->builder,
                                               "edit_dialog_expiration_relative_combobox")));
  }
  if ((edc->n_is_relative) &&
      (GNUNET_TIME_UNIT_FOREVER_REL.rel_value_us != edc->n_exp_time))
  {
    gtk_toggle_button_set_active (
      GTK_TOGGLE_BUTTON (
        gtk_builder_get_object (edc->builder,
                                "edit_dialog_expiration_relative_radiobutton")),
      TRUE);
    gtk_widget_hide (GTK_WIDGET (
                       gtk_builder_get_object (edc->builder,
                                               "edit_dialog_expiration_absolute_calendar")));
    gtk_widget_hide (GTK_WIDGET (
                       gtk_builder_get_object (edc->builder,
                                               "edit_dialog_expiration_absolute_hbox")));
    rt.rel_value_us = edc->n_exp_time;
  }
  else
  {
    /* select a sane default */
    rt = GNUNET_TIME_UNIT_DAYS;
  }
  cb = GTK_COMBO_BOX (
    gtk_builder_get_object (edc->builder,
                            "edit_dialog_expiration_relative_combobox"));
  ls = GTK_LIST_STORE (gtk_combo_box_get_model (cb));
  gtk_list_store_insert_with_values (
    ls,
    &iter,
    -1 /* position: append */,
    0,
    GNUNET_STRINGS_relative_time_to_string (rt, GNUNET_NO),
    -1);
  gtk_combo_box_set_active_iter (cb, &iter);
  if ((! edc->n_is_relative) &&
      (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us != edc->n_exp_time))
  {
    gtk_toggle_button_set_active (
      GTK_TOGGLE_BUTTON (
        gtk_builder_get_object (edc->builder,
                                "edit_dialog_expiration_absolute_radiobutton")),
      TRUE);

    gtk_widget_hide (GTK_WIDGET (
                       gtk_builder_get_object (edc->builder,
                                               "edit_dialog_expiration_relative_combobox")));
    at.abs_value_us = edc->n_exp_time;
  }
  else
  {
    /* select a sane default: right now */
    at = GNUNET_TIME_absolute_get ();
  }
  tp = (time_t) (at.abs_value_us / 1000000LL); /* convert to seconds */
  ymd = gmtime (&tp);
  cal = GTK_CALENDAR (
    gtk_builder_get_object (edc->builder,
                            "edit_dialog_expiration_absolute_calendar"));
  gtk_calendar_select_month (cal, ymd->tm_mon, ymd->tm_year + 1900);
  gtk_calendar_mark_day (cal, ymd->tm_mday);
  gtk_spin_button_set_value (
    GTK_SPIN_BUTTON (
      gtk_builder_get_object (edc->builder,
                              "edit_dialog_expiration_absolute_hours_spinbutton")),
    (double) ymd->tm_hour);
  gtk_spin_button_set_value (
    GTK_SPIN_BUTTON (gtk_builder_get_object (
                       edc->builder,
                       "edit_dialog_expiration_absolute_minutes_spinbutton")),
    (double) ymd->tm_min);
  gtk_spin_button_set_value (
    GTK_SPIN_BUTTON (gtk_builder_get_object (
                       edc->builder,
                       "edit_dialog_expiration_absolute_seconds_spinbutton")),
    (double) ymd->tm_sec);
}


/**
 * Perform the reverse of the #edit_dialog_setup_common_elements() function,
 * that is, extract the values from the (common) widgets and store the
 * values in @a edc.
 *
 * @param edc edit dialog to extract data from
 */
static void
edit_dialog_putes_common_elements (
  struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc)
{
  const char *rt_s;
  struct GNUNET_TIME_Relative rt;
  GtkComboBox *cb;
  GtkTreeModel *tm;
  GtkTreeIter iter;
  gchar *opt;
  struct GNUNET_IDENTITY_Ego *ego;

  /* is public flag */
  edc->n_public = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
                                                  gtk_builder_get_object (
                                                    edc->builder,
                                                    "edit_dialog_options_public_checkbutton")));
  /* is shadow flag */
  edc->n_is_shadow = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
                                                     gtk_builder_get_object (
                                                       edc->builder,
                                                       "edit_dialog_options_shadow_checkbutton")));

  /* 'forever' expiration time */
  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
                                      gtk_builder_get_object (edc->builder,
                                                              "edit_dialog_expiration_never_radiobutton"))))
  {
    edc->n_exp_time = GNUNET_TIME_UNIT_FOREVER_REL.rel_value_us;
    edc->n_is_relative =
      TRUE; /* doesn't matter, but make sure it is well-defined anyway */
  }

  /* 'relative' expiration time */
  if (
    gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
                                    gtk_builder_get_object (edc->builder,
                                                            "edit_dialog_expiration_relative_radiobutton"))))
  {
    cb = GTK_COMBO_BOX (
      gtk_builder_get_object (edc->builder,
                              "edit_dialog_expiration_relative_combobox"));
    tm = gtk_combo_box_get_model (cb);
    if (! gtk_combo_box_get_active_iter (cb, &iter))
    {
      GNUNET_break (0);
      return;
    }
    gtk_tree_model_get (tm, &iter, 0, &rt_s, -1);
    GNUNET_break (GNUNET_YES ==
                  GNUNET_STRINGS_fancy_time_to_relative (rt_s, &rt));
    edc->n_exp_time = rt.rel_value_us;
    edc->n_is_relative = TRUE;
  }

  /* 'absolute' expiration time */
  if (
    gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
                                    gtk_builder_get_object (edc->builder,
                                                            "edit_dialog_expiration_absolute_radiobutton"))))
  {
    guint year;
    guint month;
    guint day;
    guint hour;
    guint minute;
    guint second;
    char fancydate[128];
    struct GNUNET_TIME_Absolute atime;

    gtk_calendar_get_date (GTK_CALENDAR (gtk_builder_get_object (
                                           edc->builder,
                                           "edit_dialog_expiration_absolute_calendar")),
                           &year,
                           &month,
                           &day);
    hour = gtk_spin_button_get_value (GTK_SPIN_BUTTON (
                                        gtk_builder_get_object (edc->builder,
                                                                "edit_dialog_expiration_absolute_hours_spinbutton")));
    minute =
      gtk_spin_button_get_value (GTK_SPIN_BUTTON (gtk_builder_get_object (
                                                    edc->builder,
                                                    "edit_dialog_expiration_absolute_minutes_spinbutton")));
    second =
      gtk_spin_button_get_value (GTK_SPIN_BUTTON (gtk_builder_get_object (
                                                    edc->builder,
                                                    "edit_dialog_expiration_absolute_seconds_spinbutton")));
    GNUNET_snprintf (fancydate,
                     sizeof (fancydate),
                     "%u-%u-%u %u:%u:%u",
                     (unsigned int) year,
                     (unsigned int) month + 1,
                     (unsigned int) day,
                     (unsigned int) hour,
                     (unsigned int) minute,
                     (unsigned int) second);
    GNUNET_break (GNUNET_OK ==
                  GNUNET_STRINGS_fancy_time_to_absolute (fancydate, &atime));
    edc->n_exp_time = atime.abs_value_us;
    edc->n_is_relative = FALSE;
  }

  /* extract target zone! */
  cb = GTK_COMBO_BOX (
    gtk_builder_get_object (edc->builder, "edit_dialog_zone_combobox"));
  tm = gtk_combo_box_get_model (cb);
  if (! gtk_combo_box_get_active_iter (cb, &iter))
  {
    GNUNET_break (0);
  }
  else
  {
    gtk_tree_model_get (tm, &iter, ZONE_LS_NAME, &opt, ZONE_LS_EGO, &ego, -1);
    if (NULL == opt)
    {
      GNUNET_break (0);
    }
    else
    {
      g_free (edc->new_zone_option);
      edc->new_zone_option = g_strdup (opt);
      g_free (opt);
      edc->ego = ego;
    }
  }
}


/**
 * Editing dialog was closed, get the data and call the
 * continuation.
 *
 * @param dialog editing dialog
 * @param response_id action that caused the dialog to be closed
 * @param user_data the `struct GNUNET_GTK_NAMESTORE_PluginEnvironment`
 */
static void
edit_dialog_response_cb (GtkDialog *dialog,
                         gint response_id,
                         gpointer user_data)
{
  struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc = user_data;

  if (GTK_RESPONSE_OK == response_id)
  {
    edit_dialog_putes_common_elements (edc);
    g_free (edc->n_value);
    edc->n_value = edc->plugin->store (edc->plugin->cls, edc->builder);
  }
  gtk_widget_destroy (GTK_WIDGET (edc->dialog));
  g_object_unref (edc->builder);
  edc->builder = NULL;
  edit_dialog_continuation (edc, response_id);
}


/**
 * Callback invoked from #GNUNET_GTK_get_new_builder2 to give
 * us a chance to add symbols from the plugin.
 *
 * @param builder the builder under construction
 * @param object object to bind signal to
 * @param signal_name name of the signal
 * @param handler_name name of the handler
 * @param connect_object a GObject, if non-NULL, use g_signal_connect_object()
 * @param flags GConnectFlags to use
 * @param user_data the `struct GNUNET_GTK_NAMESTORE_PluginEnvironment *`
 */
static void
add_symbols (GtkBuilder *builder,
             GObject *object,
             const gchar *signal_name,
             const gchar *handler_name,
             GObject *connect_object,
             GConnectFlags flags,
             gpointer user_data)

{
  struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc = user_data;
  GCallback cb;

  cb = NULL;
  if (NULL != edc->plugin->symbols)
  {
    for (unsigned int i = 0; NULL != edc->plugin->symbols[i].name; i++)
    {
      if (0 == strcmp (handler_name, edc->plugin->symbols[i].name))
      {
        cb = edc->plugin->symbols[i].cb;
        break;
      }
    }
  }
  if (NULL == cb)
  {
    GModule *m;

    m = g_module_open (NULL, 0);
    if (! g_module_symbol (m, handler_name, (void **) &cb))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  _ ("Failed to find handler `%s'\n"),
                  handler_name);
      g_module_close (m);
      return;
    }
    g_module_close (m);
  }
  if (NULL != connect_object)
    g_signal_connect_object (object, signal_name, cb, connect_object, flags);
  else if (flags & G_CONNECT_SWAPPED)
    g_signal_connect_swapped (object, signal_name, cb, user_data);
  else if (flags & G_CONNECT_AFTER)
    g_signal_connect_after (object, signal_name, cb, user_data);
  else
    g_signal_connect (object, signal_name, cb, user_data);
}


/**
 * Launch a record editing dialog.
 *
 * @param n_type type of the record to edit
 * @param name name of the record to edit
 * @param ri record information for this name (can be NULL
 *        if this is the first record for the name)
 * @param off offset of the record being edited in the
 *        @a ri's list; UINT_MAX if this is a new record
 */
static void
launch_edit_dialog (gint n_type,
                    const char *name,
                    struct RecordInfo *ri,
                    unsigned int off)
{
  struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc;
  const char *typename;

  typename = GNUNET_GNSRECORD_number_to_typename (n_type);
  if (NULL == typename)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Record type `%u' not supported by this installation\n",
                (unsigned int) n_type);
    return;
  }
  if (NULL == current_pseudonym)
  {
    GNUNET_break (0);
    return;
  }
  edc = GNUNET_new (struct GNUNET_GTK_NAMESTORE_PluginEnvironment);
  if ((NULL != ri) && (off < ri->rd_count))
  {
    struct GNUNET_GNSRECORD_Data rd_old[ri->rd_count];

    GNUNET_break (GNUNET_OK ==
                  GNUNET_GNSRECORD_records_deserialize (ri->data_size,
                                                        ri->data,
                                                        ri->rd_count,
                                                        rd_old));
    edc->n_value = GNUNET_GNSRECORD_value_to_string (n_type,
                                                     rd_old[off].data,
                                                     rd_old[off].data_size);
    edc->n_public = (0 == (rd_old[off].flags & GNUNET_GNSRECORD_RF_PRIVATE));
    edc->n_is_relative =
      (0 != (rd_old[off].flags & GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION));
    edc->n_is_shadow =
      (0 != (rd_old[off].flags & GNUNET_GNSRECORD_RF_SHADOW));
    edc->n_exp_time = rd_old[off].expiration_time;
    edc->old_record_in_namestore = GNUNET_YES;
  }
  else
  {
    edc->n_exp_time = 1000000LL * (time (NULL) + 365 * 24 * 60 * 60); /* + 1y */
  }
  edc->ri = ri;
  edc->off = off;
  edc->name = GNUNET_strdup (name);
  edc->new_zone_option = g_strdup (current_pseudonym);
  edc->zone_liststore = zone_liststore;
  edc->check_validity = &check_validity;
  GNUNET_asprintf (&edc->liblow, "libgnunet_plugin_gtk_namestore_%s", typename);
  GNUNET_STRINGS_utf8_tolower (edc->liblow, edc->liblow);
  edc->plugin = GNUNET_PLUGIN_load (edc->liblow, edc);
  if (NULL == edc->plugin)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                _ ("Failed to load plugin for record type %d\n"),
                (int) n_type);
    edit_dialog_continuation (edc, GTK_RESPONSE_CANCEL);
    return;
  }
  if (0 != edc->plugin->record_type)
    edc->record_type = edc->plugin->record_type;
  else
    edc->record_type = n_type;
  edc->builder =
    GNUNET_GTK_get_new_builder2 (edc->plugin->dialog_glade_filename,
                                 edc,
                                 &add_symbols);
  if (NULL == edc->builder)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                _ ("Failed to load dialog resource `%s'\n"),
                edc->plugin->dialog_glade_filename);
    edit_dialog_continuation (edc, GTK_RESPONSE_CANCEL);
    return;
  }
  if (GNUNET_YES == edc->old_record_in_namestore)
  {
    edc->plugin->load (edc->plugin->cls, edc->n_value, edc->builder);
  }
  edc->dialog = GTK_DIALOG (
    gtk_builder_get_object (edc->builder, edc->plugin->dialog_widget_name));
  g_signal_connect (edc->dialog,
                    "response",
                    G_CALLBACK (&edit_dialog_response_cb),
                    edc);
  edit_dialog_setup_common_elements (edc);
  setup_zone (edc);
  gtk_dialog_set_default_response (edc->dialog, GTK_RESPONSE_OK);
  gtk_window_present (GTK_WINDOW (edc->dialog));
}


/**
 * Check if, with the given record info, we are allowed to add
 * another record of the given type.
 *
 * @param ri existing records
 * @param n_type type of the new record
 * @return #GNUNET_OK if this is allowed
 */
static int
check_permissions (struct RecordInfo *ri, guint n_type)
{
  struct GNUNET_GNSRECORD_Data rd[ri->rd_count];

  GNUNET_break (GNUNET_OK ==
                GNUNET_GNSRECORD_records_deserialize (ri->data_size,
                                                      ri->data,
                                                      ri->rd_count,
                                                      rd));
  if (GNUNET_OK != check_record_permitted (ri->rd_count, rd, n_type))
  {
    show_error_message (
      _ ("Record combination not permitted"),
      _ (
        "Given the existing records, adding a new record of this type is not allowed.\n"
        "CNAME and PKEY records cannot co-exist with other records.\n"
        "NS records in GNS can only co-exist with A and AAAA records.\n"));
    return GNUNET_NO;
  }
  return GNUNET_OK;
}


/**
 * User selected 'edit' in the popup menu.  Edit the selected row.
 *
 * @param widget the GtkTreeView
 * @param user_data main window builder
 */
void
gnunet_namestore_gtk_popup_edit_button_activate_cb (GtkWidget *widget,
                                                    gpointer user_data)
{
  GtkTreeSelection *sel;
  gint n_type;
  gchar *n_name;
  guint off;
  struct RecordInfo *ri;
  GtkTreeIter iter;
  struct GNUNET_HashCode name_hash;

  sel = gtk_tree_view_get_selection (tv);
  if (! gtk_tree_selection_get_selected (sel, NULL, &iter))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "No row selected\n");
    return;
  }
  /* clang-format off */
  gtk_tree_model_get (tm,
                      &iter,
                      GNS_TREESTORE_COL_NAME, &n_name,
                      GNS_TREESTORE_COL_RECORD_TYPE, &n_type,
                      GNS_TREESTORE_COL_RECORD_OFFSET, &off,
                      -1);
  /* clang-format off */
  if (NULL == n_name)
  {
    GNUNET_break (0);
    return;
  }
  GNUNET_CRYPTO_hash (n_name,
                      strlen (n_name),
                      &name_hash);
  ri = GNUNET_CONTAINER_multihashmap_get (n2r,
                                          &name_hash);
  if ( (NULL == ri) ||
       (off >= ri->rd_count) )
  {
    GNUNET_break (0);
    g_free (n_name);
    return;
  }
  launch_edit_dialog (n_type, n_name,
                      ri, off);
  g_free (n_name);
}


/**
 * The user has selected a new record type.  Update the
 * model and then start the 'edit' dialog.
 *
 * @param text renderer updated renderer
 * @param path_string the path identifying the edited cell
 * @param iter selected position
 * @param user_data unused
 */
void
gnunet_namestore_gtk_type_cellrenderercombo_changed_cb (
  GtkCellRendererText *text,
  gchar *path_string,
  GtkTreeIter *iter,
  gpointer user_data)
{
  GtkTreeIter it;
  guint type;
  char *name_str;
  struct GNUNET_HashCode name_hash;
  struct RecordInfo *ri;
  GtkTreeModel *types;

  types = GTK_TREE_MODEL (get_object ("gnunet_namestore_gtk_type_liststore"));
  gtk_tree_model_get (types,
                      iter,
                      GNS_TYPE_TO_NAME_LISTSTORE_COLUMN_TYPE, &type,
                      -1);
  /* check if this is a new record */
  if (! gtk_tree_model_get_iter_from_string (tm,
                                             &it,
                                             path_string))
  {
    GNUNET_break (0);
    return;
  }
  gtk_tree_model_get (tm, &it,
                      GNS_TREESTORE_COL_NAME, &name_str,
                      -1);
  if (NULL == name_str)
  {
    GNUNET_break (0);
    return;
  }
  GNUNET_CRYPTO_hash (name_str,
                      strlen (name_str),
                      &name_hash);
  ri = GNUNET_CONTAINER_multihashmap_get (n2r,
                                          &name_hash);
  if ( (NULL == ri) ||
       (GNUNET_OK == check_permissions (ri,
                                        type)) )
    launch_edit_dialog (type,
                        name_str,
                        ri,
                        UINT_MAX);
  g_free (name_str);
}


/**
 * The user has edited a 'name' cell.  Update the model (and if needed
 * create another fresh line for additional records).
 *
 * @param renderer updated renderer
 * @param path the path identifying the edited cell
 * @param new_text the new name (not modified)
 * @param user_data unused
 */
void
gnunet_namestore_gtk_name_cellrenderertext_edited_cb (
  GtkCellRendererText *renderer,
  gchar *path,
  gchar *new_text,
  gpointer user_data)
{
  GtkTreeIter it;
  gboolean not_dummy;
  char *name;
  struct RecordInfo *ri;
  struct GNUNET_HashCode name_hash;
  GtkTreePath *tpath;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "New text for `%s' is `%s'\n",
              path,
              new_text);
  if ((0 == strcmp (new_text,
                    NEW_NAME_STR)) ||
      (0 == strcmp (new_text,
                    "")))
    return;
  if (GNUNET_OK !=
      GNUNET_DNSPARSER_check_label (new_text))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                _ (
                  "Name `%s' invalid for GNS/DNS (too long for a DNS label?)\n"),
                new_text);
    gtk_widget_error_bell (GTK_WIDGET (main_window));
    return;
  }
  if (! gtk_tree_model_get_iter_from_string (tm,
                                             &it,
                                             path))
  {
    GNUNET_break (0);
    return;
  }
  gtk_tree_model_get (tm, &it,
                      GNS_TREESTORE_COL_NOT_DUMMY_ROW, &not_dummy,
                      GNS_TREESTORE_COL_NAME, &name,
                      -1);
  GNUNET_break (! not_dummy);
  GNUNET_break (0 == strcmp (name, _ (NEW_NAME_STR)));
  g_free (name);

  GNUNET_CRYPTO_hash (new_text,
                      strlen (new_text),
                      &name_hash);
  ri = GNUNET_CONTAINER_multihashmap_get (n2r,
                                          &name_hash);
  if (NULL != ri)
  {
    GtkTreeSelection *sel;
    GtkTreePath *sel_path;

    sel_path = gtk_tree_row_reference_get_path (ri->rr);
    sel = gtk_tree_view_get_selection (tv);
    gtk_tree_selection_select_path (sel, sel_path);
    gtk_tree_path_free (sel_path);
    return;
  }

  /* change dummy line to new name, then add new dummy */
  ri = GNUNET_new (struct RecordInfo);
  gtk_tree_store_set (ts,
                      &it,
                      GNS_TREESTORE_COL_NAME, new_text,
                      GNS_TREESTORE_COL_NAME_IS_VISIBLE, TRUE,
                      GNS_TREESTORE_COL_RECORD_TYPE, 0,
                      GNS_TREESTORE_COL_RECORD_TYPE_AS_STR, _ (NEW_RECORD_STR),
                      GNS_TREESTORE_COL_NOT_DUMMY_ROW, FALSE,
                      GNS_TREESTORE_COL_IS_RECORD_ROW, TRUE,
                      GNS_TREESTORE_COL_TYPE_IS_EDITABLE, TRUE,
                      GNS_TREESTORE_COL_NAME_IS_EDITABLE, FALSE,
                      -1);
  tpath = gtk_tree_model_get_path (tm,
                                   &it);
  ri->rr = gtk_tree_row_reference_new (tm,
                                       tpath);
  ri->name = GNUNET_strdup (name);
  gtk_tree_path_free (tpath);
  GNUNET_assert (GNUNET_OK ==
                 GNUNET_CONTAINER_multihashmap_put (n2r,
                                                    &name_hash,
                                                    ri,
                                                    GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));

  /* add a new dummy line */
  gtk_tree_store_insert_with_values (ts,
                                     &it,
                                     NULL,
                                     0,
                                     GNS_TREESTORE_COL_NAME, _ (NEW_NAME_STR),
                                     GNS_TREESTORE_COL_NAME_IS_VISIBLE, TRUE,
                                     GNS_TREESTORE_COL_RECORD_TYPE,
                                     GNUNET_DNSPARSER_TYPE_A,
                                     GNS_TREESTORE_COL_NOT_DUMMY_ROW, FALSE,
                                     GNS_TREESTORE_COL_IS_RECORD_ROW, FALSE,
                                     GNS_TREESTORE_COL_TYPE_IS_EDITABLE, FALSE,
                                     GNS_TREESTORE_COL_NAME_IS_EDITABLE, TRUE,
                                     GNS_TREESTORE_COL_RECORD_OFFSET, UINT_MAX,
                                     -1);
}


/**
 * Create a context (popup) menu for the zone iteration treeview
 * (if applicable).
 *
 * @return NULL if no menu could be created
 */
static GtkMenu *
create_popup_menu ()
{
  GtkTreeIter it;
  GtkMenu *popup;
  GtkTreeSelection *sel;
  gboolean name_vis;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Considering creating popup menu...\n");
  sel = gtk_tree_view_get_selection (tv);
  if (! gtk_tree_selection_get_selected (sel,
                                         NULL,
                                         &it))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "No row selected\n");
    return NULL;
  }
  gtk_tree_model_get (tm, &it,
                      GNS_TREESTORE_COL_NAME_IS_VISIBLE, &name_vis,
                      -1);
  if (name_vis)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Selected row is not a record row\n");
    return NULL;
  }
  popup  = GTK_MENU (get_object ("gnunet_namestore_gtk_edit_popup_menu"));
  gtk_widget_show_all (GTK_WIDGET (popup));
  return popup;
}


/**
 * The zone treeview pop up menu is supposed to be created.
 * (Note: this is not the only method that might need to be
 * written to handle events to create pop up menus; right-clicks
 * need to be managed separately).
 *
 * @param widget the widget
 * @param user_data unused
 * @return TRUE if a menu was activated
 */
gboolean
gnunet_namestore_gtk_treeview_popup_menu_cb (GtkWidget *widget,
                                             gpointer user_data)
{
  GtkMenu *menu;

  menu = create_popup_menu ();
  if (NULL == menu)
    return FALSE;
  gtk_menu_popup_at_widget (menu,
                            widget,
                            GDK_GRAVITY_CENTER,
                            GDK_GRAVITY_CENTER,
                            NULL);
  return TRUE;
}


/**
 * A button was pressed in the GtkTreeView, check for right button and
 * if applicable create the popup menu.
 *
 * @param widget the GtkTreeView
 * @param event the event
 * @param user_data unused
 * @return TRUE if a menu was activated (event was handled)
 */
gboolean
gnunet_namestore_gtk_treeview_button_press_event_cb (GtkWidget *widget,
                                                     GdkEvent *event,
                                                     gpointer user_data)
{
  GdkEventButton *event_button = (GdkEventButton *) event;
  GtkMenu *menu;

  /* Check for right click*/
  if (NULL == widget)
    return FALSE;
  if ( (GDK_BUTTON_PRESS != event_button->type) ||
       (3 != event_button->button) )
    return FALSE;
  menu = create_popup_menu ();
  if (NULL == menu)
    return FALSE;
  gtk_menu_popup_at_pointer (menu,
                             event);
  return TRUE;
}


/**
 * User pushed a key in the GtkTreeView.  Check for 'del' and if so, delete
 * the currently selected row.
 *
 * @param widget originating widget
 * @param event event information
 * @param user_data unused
 * @return TRUE if the key was processed ('del')
 */
gboolean
gnunet_namestore_gtk_treeview_key_press_event_cb (GtkWidget *widget,
                                                  GdkEvent *event,
                                                  gpointer user_data)
{
  GdkEventKey *event_key = (GdkEventKey *) event;
  GtkTreeIter iter;
  GtkTreeSelection *sel;
  int not_dummy;
  GtkTreeIter parent;
  char *name;
  struct GNUNET_HashCode name_hash;
  struct RecordInfo *ri;
  struct OperationContext *oc;

  /* Check for delete key */
  if ( (GDK_KEY_PRESS != event_key->type) ||
       (GDK_KEY_Delete != event_key->keyval) )
    return FALSE;
  sel = gtk_tree_view_get_selection (tv);
  if (! gtk_tree_selection_get_selected (sel,
                                         NULL,
                                         &iter))
    return TRUE; /* nothing selected */
  /* clang-format off */
  gtk_tree_model_get (tm,
                      &iter,
                      GNS_TREESTORE_COL_NOT_DUMMY_ROW, &not_dummy,
                      GNS_TREESTORE_COL_NAME, &name,
                      -1);
  /* clang-format on */
  if (GNUNET_NO == not_dummy)
  {
    g_free (name);
    return TRUE; /* do not delete the dummy line */
  }
  GNUNET_CRYPTO_hash (name, strlen (name), &name_hash);
  ri = GNUNET_CONTAINER_multihashmap_get (n2r, &name_hash);
  GNUNET_assert (NULL != ri);

  if ((gtk_tree_model_iter_parent (tm, &parent, &iter)) && (ri->rd_count > 0))
  {
    struct GNUNET_GNSRECORD_Data rd_old[ri->rd_count];
    struct GNUNET_GNSRECORD_Data rd_new[ri->rd_count - 1];
    struct GNUNET_GNSRECORD_Data rd;
    unsigned int off;
    int n_type;
    gboolean n_public;
    guint64 n_exp_time;
    gboolean n_is_relative;
    gboolean n_is_shadow;
    char *n_value;

    /* Removing a single record */
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Removing single record for name `%s'\n",
                name);
    GNUNET_break (GNUNET_OK ==
                  GNUNET_GNSRECORD_records_deserialize (ri->data_size,
                                                        ri->data,
                                                        ri->rd_count,
                                                        rd_old));
    /* clang-format off */
    gtk_tree_model_get (tm, &iter,
                        GNS_TREESTORE_COL_RECORD_TYPE, &n_type,
                        GNS_TREESTORE_COL_IS_PUBLIC, &n_public,
                        GNS_TREESTORE_COL_EXP_TIME, &n_exp_time,
                        GNS_TREESTORE_COL_EXP_TIME_IS_REL, &n_is_relative,
                        GNS_TREESTORE_COL_IS_SHADOW, &n_is_shadow,
                        GNS_TREESTORE_COL_VAL_AS_STR, &n_value,
                        -1);
    /* clang-format on */
    /* valid name */
    if (n_public)
      rd.flags = GNUNET_GNSRECORD_RF_NONE;
    else
      rd.flags = GNUNET_GNSRECORD_RF_PRIVATE;
    if (n_is_relative)
      rd.flags |= GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION;
    if (n_is_shadow)
      rd.flags |= GNUNET_GNSRECORD_RF_SHADOW;
    rd.record_type = n_type;
    rd.expiration_time = n_exp_time;
    if (GNUNET_OK != GNUNET_GNSRECORD_string_to_value (n_type,
                                                       n_value,
                                                       (void **) &rd.data,
                                                       &rd.data_size))
    {
      /* can't remove, value invalid */
      GNUNET_assert (0);
      g_free (n_value);
      g_free (name);
      return TRUE;
    }
    for (off = 0; off < ri->rd_count; off++)
      if (GNUNET_YES == GNUNET_GNSRECORD_records_cmp (&rd, &rd_old[off]))
        break;
    GNUNET_assert (off != ri->rd_count);
    memcpy (rd_new,
            rd_old,
            (ri->rd_count - 1) * sizeof (struct GNUNET_GNSRECORD_Data));
    rd_new[off] = rd_old[ri->rd_count - 1];
    oc = GNUNET_new (struct OperationContext);
    GNUNET_CONTAINER_DLL_insert (oc_head, oc_tail, oc);
    oc->qe = GNUNET_NAMESTORE_records_store (namestore,
                                             pkey,
                                             name,
                                             ri->rd_count - 1,
                                             rd_new,
                                             &operation_done_cont,
                                             oc);
    g_free (n_value);
  }
  else
  {
    oc = GNUNET_new (struct OperationContext);
    GNUNET_CONTAINER_DLL_insert (oc_head, oc_tail, oc);
    oc->qe = GNUNET_NAMESTORE_records_store (namestore,
                                             pkey,
                                             name,
                                             0,
                                             NULL,
                                             &operation_done_cont,
                                             oc);
  }
  g_free (name);

  return TRUE;
}


/**
 * The user clicked on the 'copy' button.  Copy the full string
 * with the hash of our public key to the clipboard.
 *
 * @param button the button that was clicked
 * @param user_data unused
 */
void
gnunet_namestore_gtk_public_key_copy_button_clicked_cb (GtkButton *button,
                                                        gpointer user_data)
{
  GtkClipboard *cb;

  if (NULL == pkey)
    return;
  cb = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
  gtk_clipboard_set_text (cb, GNUNET_GNSRECORD_z2s (&pubkey), -1);
}


/**
 * Function called once our model should be consistent with
 * the current zone.  Makes the view visible.
 *
 * @param cls NULL
 */
static void
zone_sync_proc (void *cls)
{
  (void) cls;
  gtk_tree_view_set_model (tv, tm);
#if HAVE_QRENCODE_H
  setup_qrcode ();
  gtk_widget_show (GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_image")));
  gtk_widget_show (
    GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_saveas_button")));
  gtk_widget_show (
    GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_vseparator")));
#else
  gtk_widget_hide (GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_image")));
  gtk_widget_hide (
    GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_saveas_button")));
  gtk_widget_hide (
    GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_vseparator")));
#endif
  gtk_widget_hide (GTK_WIDGET (status_label));
  gtk_widget_show (
    GTK_WIDGET (get_object ("gnunet_namestore_gtk_scrolledwindow")));
  gtk_widget_show (GTK_WIDGET (zone_combo_box));
}


/**
 * Function called once our model is again inconsistent with
 * the current zone.  Hides the view.
 *
 * @param cls NULL
 */
static void
zone_iteration_error (void *cls)
{
  (void) cls;
  clear_zone_view ();
#if HAVE_QRENCODE_H
  setup_qrcode ();
  gtk_widget_hide (GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_image")));
  gtk_widget_hide (
    GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_saveas_button")));
  gtk_widget_hide (
    GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_vseparator")));
#else
  gtk_widget_show (GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_image")));
  gtk_widget_show (
    GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_saveas_button")));
  gtk_widget_show (
    GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_vseparator")));
#endif
}


/**
 * A set of records is completely empty, remove it
 * from the hashmap and free associated memory.
 *
 * @param ri record info to release
 */
static void
free_ri (struct RecordInfo *ri)
{
  GtkTreePath *path;
  GtkTreeIter iter_name;
  struct GNUNET_HashCode name_hash;

  path = gtk_tree_row_reference_get_path (ri->rr);
  GNUNET_assert (gtk_tree_model_get_iter (tm, &iter_name, path));
  gtk_tree_path_free (path);
  gtk_tree_store_remove (ts, &iter_name);
  GNUNET_CRYPTO_hash (ri->name, strlen (ri->name), &name_hash);
  release_ri (NULL, &name_hash, ri);
}


/**
 * Function called for each record in the current zone.  Update the
 * widgets accordingly.
 *
 * @param cls NULL
 * @param zone_key private key of the zone
 * @param name name that is being mapped (at most 255 characters long)
 * @param rd_count number of entries in @a rd array
 * @param rd array of records with data to store
 */
static void
zone_iteration_proc (void *cls,
                     const struct GNUNET_IDENTITY_PrivateKey *zone_key,
                     const char *name,
                     unsigned int rd_count,
                     const struct GNUNET_GNSRECORD_Data *rd)
{
  GtkTreeRowReference *rr;
  GtkTreePath *path;
  GtkTreeIter iter_name;
  GtkTreeIter iter_record;
  const char *exp;
  char *val;
  char *type_str;
  gboolean time_is_relative;
  gboolean is_public;
  gboolean is_shadow;
  guint64 exp_t;
  struct GNUNET_HashCode name_hash;
  struct RecordInfo *ri;
  GtkTreeSelection *sel;
  GtkTreeIter sel_iter;
  GtkTreePath *sel_path;

  (void) cls;
  if (0 != memcmp (zone_key, pkey, sizeof (struct GNUNET_IDENTITY_PrivateKey)))
  {
    GNUNET_break (0);
    return;
  }
  GNUNET_NAMESTORE_zone_monitor_next (zmon, 1);
  if ((GNUNET_SYSERR == GNUNET_DNSPARSER_check_name (name)) &&
      (0 != strcmp (name, GNUNET_GNS_EMPTY_LABEL_AT)))
  {
    GNUNET_break (0);
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                _ ("Got invalid record name `%s' from namestore\n"),
                name);
    return;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Zone iteration result `%s', %u records\n",
              name,
              rd_count);
  GNUNET_CRYPTO_hash (name, strlen (name), &name_hash);
  sel = gtk_tree_view_get_selection (tv);
  if (gtk_tree_selection_get_selected (sel, NULL, &sel_iter))
    sel_path = gtk_tree_model_get_path (tm, &sel_iter);
  else
    sel_path = NULL;
  ri = GNUNET_CONTAINER_multihashmap_get (n2r, &name_hash);
  if (NULL != ri)
  {
    rr = ri->rr;
    path = gtk_tree_row_reference_get_path (rr);
    GNUNET_assert (gtk_tree_model_get_iter (tm, &iter_name, path));
    gtk_tree_path_free (path);
    /* remove all records, we'll re-add those that are left next */
    if (gtk_tree_model_iter_children (tm, &iter_record, &iter_name))
      while (gtk_tree_store_remove (ts, &iter_record))
        ;
  }
  else
  {
    ri = GNUNET_new (struct RecordInfo);
    gtk_tree_store_insert_with_values (ts,
                                       &iter_name,
                                       NULL,
                                       -1,
                                       GNS_TREESTORE_COL_NAME,
                                       name,
                                       GNS_TREESTORE_COL_NAME_IS_VISIBLE,
                                       TRUE,
                                       GNS_TREESTORE_COL_RECORD_TYPE,
                                       GNUNET_GNSRECORD_TYPE_ANY,
                                       GNS_TREESTORE_COL_RECORD_TYPE_AS_STR,
                                       _ (NEW_RECORD_STR),
                                       GNS_TREESTORE_COL_IS_RECORD_ROW,
                                       TRUE,
                                       GNS_TREESTORE_COL_NOT_DUMMY_ROW,
                                       FALSE,
                                       GNS_TREESTORE_COL_TYPE_IS_EDITABLE,
                                       TRUE,
                                       GNS_TREESTORE_COL_RECORD_OFFSET,
                                       UINT_MAX,
                                       -1);

    path = gtk_tree_model_get_path (tm, &iter_name);
    rr = gtk_tree_row_reference_new (tm, path);
    ri->rr = rr;
    ri->name = GNUNET_strdup (name);
    gtk_tree_path_free (path);
    GNUNET_assert (GNUNET_OK ==
                   GNUNET_CONTAINER_multihashmap_put (
                     n2r,
                     &name_hash,
                     ri,
                     GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
  }
  if (0 == rd_count)
  {
    /* all records removed, remove name as well */
    free_ri (ri);
    return;
  }
  /* update record info serialized version of the records */
  GNUNET_free (ri->data);
  ri->rd_count = rd_count;
  ri->data_size = GNUNET_GNSRECORD_records_get_size (rd_count, rd);
  if (0 != ri->data_size)
    ri->data = GNUNET_malloc (ri->data_size);
  GNUNET_break (
    ri->data_size ==
    GNUNET_GNSRECORD_records_serialize (rd_count, rd, ri->data_size, ri->data));

  /* Append elements for records in tree view */
  for (unsigned int c = 0; c < rd_count; c++)
  {
    if (GNUNET_GNSRECORD_TYPE_NICK == rd[c].record_type)
      continue;
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Record %u: type %u flags %u expiration %llu data_size %u\n",
                c,
                rd[c].record_type,
                rd[c].flags,
                (unsigned long long) rd[c].expiration_time,
                (unsigned int) rd[c].data_size);

    /* Set public toggle */
    is_public = ((rd[c].flags & GNUNET_GNSRECORD_RF_PRIVATE) !=
                 GNUNET_GNSRECORD_RF_PRIVATE);
    /* Set shadow toggle */
    is_shadow = ((rd[c].flags & GNUNET_GNSRECORD_RF_SHADOW) ==
                 GNUNET_GNSRECORD_RF_SHADOW);
    /* Expiration time */
    time_is_relative =
      (0 != (rd[c].flags & GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION));

    if (time_is_relative)
    {
      struct GNUNET_TIME_Relative rel_time;

      rel_time.rel_value_us = rd[c].expiration_time;
      exp_t = rel_time.rel_value_us;
      exp = GNUNET_STRINGS_relative_time_to_string (rel_time, GNUNET_NO);
    }
    else
    {
      struct GNUNET_TIME_Absolute exp_abs;

      exp_abs.abs_value_us = rd[c].expiration_time;
      exp_t = exp_abs.abs_value_us;
      exp = GNUNET_STRINGS_absolute_time_to_string (exp_abs);
    }
    /* value */
    val = GNUNET_GNSRECORD_value_to_string (rd[c].record_type,
                                            rd[c].data,
                                            rd[c].data_size);
    if (NULL == val)
      GNUNET_asprintf (&val, "%s", EXPIRE_INVALID_STRING);

    if (NULL != GNUNET_GNSRECORD_number_to_typename (rd[c].record_type))
      type_str =
        strdup (GNUNET_GNSRECORD_number_to_typename (rd[c].record_type));
    else
      GNUNET_asprintf (&type_str, "%s", EXPIRE_INVALID_STRING);
    if ((0 == strcmp (name, GNUNET_GNS_EMPTY_LABEL_AT)) &&
        (GNUNET_GNSRECORD_TYPE_NICK == rd[c].record_type) &&
        (NULL == current_pseudonym))
    {
#if HAVE_QRENCODE_H
      setup_qrcode ();
#endif
    }
    gtk_tree_store_insert_with_values (ts,
                                       &iter_record,
                                       &iter_name,
                                       -1,
                                       GNS_TREESTORE_COL_NAME,
                                       name,
                                       GNS_TREESTORE_COL_NAME_IS_VISIBLE,
                                       FALSE,
                                       GNS_TREESTORE_COL_RECORD_TYPE,
                                       rd[c].record_type,
                                       GNS_TREESTORE_COL_RECORD_TYPE_AS_STR,
                                       type_str,
                                       GNS_TREESTORE_COL_IS_PUBLIC,
                                       is_public,
                                       GNS_TREESTORE_COL_IS_SHADOW,
                                       is_shadow,
                                       GNS_TREESTORE_COL_EXP_TIME,
                                       exp_t,
                                       GNS_TREESTORE_COL_EXP_TIME_AS_STR,
                                       exp,
                                       GNS_TREESTORE_COL_EXP_TIME_IS_REL,
                                       time_is_relative,
                                       GNS_TREESTORE_COL_VAL_AS_STR,
                                       val,
                                       GNS_TREESTORE_COL_IS_RECORD_ROW,
                                       TRUE,
                                       GNS_TREESTORE_COL_NOT_DUMMY_ROW,
                                       TRUE,
                                       GNS_TREESTORE_COL_RECORD_OFFSET,
                                       (guint) c,
                                       -1);
    GNUNET_free (type_str);
    GNUNET_free (val);
  }
  if (NULL != sel_path)
  {
    gtk_tree_selection_select_path (sel, sel_path);
    gtk_tree_view_expand_to_path (tv, sel_path);
    gtk_tree_path_free (sel_path);
  }
}

/**
 * Method called to switch the model to a new zone.
 *
 * @param name name of the zone
 * @param ego ego handle
 */
static void
load_zone (const char *name, struct GNUNET_IDENTITY_Ego *ego)
{
  char *label;
  GtkTreeIter toplevel;

  /* setup crypto keys */
  clear_zone_view ();
  if (NULL != zmon)
  {
    GNUNET_NAMESTORE_zone_monitor_stop (zmon);
    zmon = NULL;
  }
  GNUNET_free (current_pseudonym);
  current_pseudonym = NULL;
  gtk_tree_view_set_model (tv, NULL);
  if (NULL == name)
    return; /* empty zone */
  current_pseudonym = GNUNET_strdup (name);
  pkey = GNUNET_IDENTITY_ego_get_private_key (ego);
  GNUNET_IDENTITY_ego_get_public_key (ego, &pubkey);
  label = g_markup_printf_escaped ("<b>%s</b>", GNUNET_GNSRECORD_z2s (&pubkey));
  gtk_label_set_markup (GTK_LABEL (
                          get_object ("gnunet_namestore_gtk_zone_label")),
                        label);
  g_free (label);
  /* Append a top level row and leave it empty */
  gtk_tree_store_insert_with_values (ts,
                                     &toplevel,
                                     NULL,
                                     -1,
                                     GNS_TREESTORE_COL_NAME,
                                     _ (NEW_NAME_STR),
                                     GNS_TREESTORE_COL_NAME_IS_VISIBLE,
                                     TRUE,
                                     GNS_TREESTORE_COL_RECORD_TYPE,
                                     GNUNET_DNSPARSER_TYPE_A,
                                     GNS_TREESTORE_COL_IS_RECORD_ROW,
                                     FALSE,
                                     GNS_TREESTORE_COL_NOT_DUMMY_ROW,
                                     FALSE,
                                     GNS_TREESTORE_COL_TYPE_IS_EDITABLE,
                                     FALSE,
                                     GNS_TREESTORE_COL_NAME_IS_EDITABLE,
                                     TRUE,
                                     GNS_TREESTORE_COL_RECORD_OFFSET,
                                     UINT_MAX,
                                     -1);
  /* Load zone from namestore! */
#if HAVE_QRENCODE_H
  setup_qrcode ();
#endif
  GNUNET_assert (NULL != n2r);
  zmon = GNUNET_NAMESTORE_zone_monitor_start (cfg,
                                              pkey,
                                              GNUNET_YES,
                                              &zone_iteration_error,
                                              NULL,
                                              &zone_iteration_proc,
                                              NULL,
                                              &zone_sync_proc,
                                              NULL);
  GNUNET_NAMESTORE_zone_monitor_next (zmon, NAMESTORE_MONITOR_WINDOW_SIZE - 1);
}


/**
 * Check if @a label is a valid label or is already used for the name
 * of any of our zones OR configured as a TLD in the configuration.
 *
 * @param label label or domain name to check
 * @return NULL if label is fresh and valid
 *         error message explaining why @a label is not valid
 */
static const char *
fresh_label (const char *label)
{
  GtkTreeIter iter;
  struct GNS_TopLevelDomain *tld;

  if ((NULL == label) || (0 == strlen (label)))
    return _ ("Name must not be empty\n");
  if (GNUNET_OK != GNUNET_DNSPARSER_check_name (label))
    return _ ("Name is not a syntactically valid DNS label\n");
  if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (zone_liststore), &iter))
  {
    do
    {
      char *name;

      gtk_tree_model_get (GTK_TREE_MODEL (zone_liststore),
                          &iter,
                          ZONE_LS_NAME,
                          &name,
                          -1);
      if (0 == strcasecmp (name, label))
        return _ ("Name already exists in the list\n");
    } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (zone_liststore), &iter));
  }
  for (tld = tld_head; NULL != tld; tld = tld->next)
    if (0 == strcasecmp (tld->tld, label))
      return _ (
        "Name is assigned in the configuration file (see [GNS] section)\n");
  return NULL;
}


/**
 * Continuation after identity operation.  Makes the
 * main window sensitive again.
 *
 * @param cls NULL
 * @param priv private key of the new identity
 * @param emsg error message, used in status bar
 */
static void
iop_create_cont (void *cls,
                 const struct GNUNET_IDENTITY_PrivateKey *priv,
                 enum GNUNET_ErrorCode ec)
{
  GtkEntry *entry;

  (void) cls;
  (void) priv;
  iop = NULL;
  entry = GTK_ENTRY (get_object ("gnunet_namestore_gtk_zone_entry"));
  gtk_widget_set_sensitive (main_window, TRUE);
  gtk_label_set_markup (status_label, (GNUNET_EC_NONE == ec) ? NULL :
                        GNUNET_ErrorCode_get_hint (ec));
  gtk_entry_grab_focus_without_selecting (entry);
}


/**
 * Continuation after identity operation.  Makes the
 * main window sensitive again.
 *
 * @param cls NULL
 * @param emsg error message, used in status bar
 */
static void
iop_cont (void *cls, enum GNUNET_ErrorCode ec)
{
  GtkEntry *entry;

  iop = NULL;
  entry = GTK_ENTRY (get_object ("gnunet_namestore_gtk_zone_entry"));
  gtk_widget_set_sensitive (main_window, TRUE);
  gtk_label_set_markup (status_label, (GNUNET_EC_NONE == ec) ? NULL :
                        GNUNET_ErrorCode_get_hint (ec));
  gtk_entry_grab_focus_without_selecting (entry);
}


/**
 * Actual main to tear down the system.
 *
 * @param cls the main loop handle
 */
static void
cleanup_task (void *cls)
{
  struct OperationContext *oc;
  struct MoveOperationContext *moc;
  struct GNS_TopLevelDomain *tld;

  if (NULL == ml)
  {
    GNUNET_break (0);
    return;
  }
  GNUNET_GTK_main_loop_quit (ml);
  gtk_widget_show (GTK_WIDGET (status_label));
  gtk_widget_hide (
    GTK_WIDGET (get_object ("gnunet_namestore_gtk_scrolledwindow")));
  while (NULL != (oc = oc_head))
  {
    GNUNET_log (
      GNUNET_ERROR_TYPE_ERROR,
      _ (
        "A pending namestore operation was not transmitted to the namestore.\n"));
    GNUNET_CONTAINER_DLL_remove (oc_head, oc_tail, oc);
    GNUNET_NAMESTORE_cancel (oc->qe);
    GNUNET_free (oc);
  }
  while (NULL != (moc = moc_head))
  {
    GNUNET_log (
      GNUNET_ERROR_TYPE_ERROR,
      _ (
        "A pending namestore operation was not transmitted to the namestore.\n"));
    GNUNET_CONTAINER_DLL_remove (moc_head, moc_tail, moc);
    if (NULL != moc->it)
      GNUNET_NAMESTORE_zone_iteration_stop (moc->it);
    free_edit_dialog_context (moc->edc);
    GNUNET_free (moc->data);
    GNUNET_free (moc);
  }
  gtk_tree_store_clear (ts);
  if (NULL != current_pseudonym)
  {
    GNUNET_free (current_pseudonym);
    current_pseudonym = NULL;
  }
  if (NULL != iop)
  {
    GNUNET_IDENTITY_cancel (iop);
    iop = NULL;
  }
  if (NULL != identity)
  {
    GNUNET_IDENTITY_disconnect (identity);
    identity = NULL;
  }
  if (NULL != zmon)
  {
    GNUNET_NAMESTORE_zone_monitor_stop (zmon);
    zmon = NULL;
  }
  if (NULL != namestore)
  {
    GNUNET_NAMESTORE_disconnect (namestore);
    namestore = NULL;
  }
  if (NULL != n2r)
  {
    GNUNET_CONTAINER_multihashmap_iterate (n2r, &release_ri, NULL);
    GNUNET_CONTAINER_multihashmap_destroy (n2r);
    n2r = NULL;
  }
  GNUNET_assert (NULL == zmon);
  while (NULL != (tld = tld_head))
  {
    GNUNET_CONTAINER_DLL_remove (tld_head, tld_tail, tld);
    GNUNET_free (tld->tld);
    GNUNET_free (tld);
  }
}


/**
 * Callback invoked if the application is supposed to exit.
 */
void
gnunet_namestore_gtk_quit_cb (GObject *object, gpointer user_data)
{
  GNUNET_SCHEDULER_shutdown ();
}


/**
 * Change the NICK record of the current zone to @a new_name.
 * Gets the old records, append new one or updates the existing one.
 *
 * @param new_name the new name (and hence nick) of the zone
 */
static void
update_nick_record (const char *new_name)
{
  struct RecordInfo *ri;
  struct GNUNET_HashCode hc;
  unsigned int rd_count;
  struct OperationContext *oc;

  GNUNET_CRYPTO_hash ("+", 1, &hc);
  ri = GNUNET_CONTAINER_multihashmap_get (n2r, &hc);
  if (NULL == ri)
    rd_count = 0;
  else
    rd_count = ri->rd_count;

  {
    struct GNUNET_GNSRECORD_Data rd_old[rd_count];
    struct GNUNET_GNSRECORD_Data rd_new[rd_count + 1];
    unsigned int off;
    unsigned int total;

    total = rd_count;
    if (NULL != ri)
      GNUNET_break (GNUNET_OK ==
                    GNUNET_GNSRECORD_records_deserialize (ri->data_size,
                                                          ri->data,
                                                          rd_count,
                                                          rd_old));
    memcpy (rd_new, rd_old, sizeof (struct GNUNET_GNSRECORD_Data) * rd_count);
    for (off = 0; off < rd_count; off++)
      if (GNUNET_GNSRECORD_TYPE_NICK == rd_new[off].record_type)
        break;
    if (off == rd_count)
      total++;
    if ((NULL == new_name) || (0 == strlen (new_name)))
    {
      rd_new[off] = rd_new[rd_count - 1];
      total--;
    }
    else
    {
      rd_new[off].record_type = GNUNET_GNSRECORD_TYPE_NICK;
      rd_new[off].expiration_time = nick_expiration_time.rel_value_us;
      rd_new[off].flags =
        GNUNET_GNSRECORD_RF_PRIVATE | GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION;
      rd_new[off].data_size = strlen (new_name) + 1;
      rd_new[off].data = new_name;
    }
    oc = GNUNET_new (struct OperationContext);
    GNUNET_CONTAINER_DLL_insert (oc_head, oc_tail, oc);
    oc->qe = GNUNET_NAMESTORE_records_store (namestore,
                                             pkey,
                                             GNUNET_GNS_EMPTY_LABEL_AT,
                                             total,
                                             rd_new,
                                             &operation_done_cont,
                                             oc);
  }
}


/**
 * The user has edited the zone's name.  Check if the new name is
 * valid (if not, warn) and update the NICK and rename the zone.
 */
static void
handle_zone_entry_edit ()
{
  /* user is editing label, or we removed the last entry in the list */
  const char *new_name;
  GtkEntry *entry;
  const char *emsg;

  entry = GTK_ENTRY (get_object ("gnunet_namestore_gtk_zone_entry"));
  if (NULL == current_pseudonym)
  {
    gtk_entry_set_text (entry, "");
    return;
  }
  new_name = gtk_entry_get_text (entry);
  if (NULL != (emsg = fresh_label (new_name)))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "%s", emsg);
    gtk_widget_error_bell (GTK_WIDGET (entry));
    gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_PRIMARY, "error");
    gtk_entry_set_icon_tooltip_text (entry, GTK_ENTRY_ICON_PRIMARY, emsg);
    return;
  }
  gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_PRIMARY, NULL);

  update_nick_record (new_name);
#if HAVE_QRENCODE_H
  setup_qrcode ();
#endif
  gtk_list_store_set (GTK_LIST_STORE (zone_liststore),
                      &zone_iter,
                      ZONE_LS_NAME,
                      new_name,
                      -1);
  gtk_label_set_markup (status_label, _ ("Renaming zone"));
  gtk_widget_set_sensitive (main_window, FALSE);
  GNUNET_assert (NULL == iop);
  iop = GNUNET_IDENTITY_rename (identity,
                                current_pseudonym,
                                new_name,
                                &iop_cont,
                                NULL);
  GNUNET_free (current_pseudonym);
  current_pseudonym = GNUNET_strdup (new_name);
}


/**
 * The user selected another zone in the combobox.  Load it.
 *
 * @param widget the combo box where the selection was changed
 * @param user_data the builder, unused
 */
void
gnunet_namestore_gtk_zone_combobox_changed_cb (GtkComboBox *widget,
                                               gpointer user_data)
{
  GtkTreeIter iter;
  struct GNUNET_IDENTITY_Ego *ego;
  char *name;
  GtkEntry *entry;

  (void) user_data;
  if (! gtk_combo_box_get_active_iter (zone_combo_box, &iter))
  {
    handle_zone_entry_edit ();
    return;
  }
  zone_iter = iter;
  entry = GTK_ENTRY (get_object ("gnunet_namestore_gtk_zone_entry"));
  gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_PRIMARY, NULL);
  /* clang-format off */
  gtk_tree_model_get (GTK_TREE_MODEL (zone_liststore),
                      &iter,
                      ZONE_LS_NAME, &name,
                      ZONE_LS_EGO, &ego,
                      -1);
  /* clang-format on */
  gtk_widget_set_sensitive (GTK_WIDGET (get_object (
                                          "gnunet_namestore_gtk_zone_del_button")),
                            TRUE);
  load_zone (name, ego);
}


/**
 * Method called to inform about the egos of this peer.  Updates the
 * `zone_liststore`.
 *
 * When used with #GNUNET_IDENTITY_connect, this function is
 * initially called for all egos and then again whenever a
 * ego's name changes or if it is deleted.  At the end of
 * the initial pass over all egos, the function is once called
 * with 'NULL' for @a ego. That does NOT mean that the callback won't
 * be invoked in the future or that there was an error.
 *
 * If @a ego is non-NULL and if '*ctx' is set in those callbacks, the
 * value WILL be passed to a subsequent call to the identity callback
 * of #GNUNET_IDENTITY_connect (if that one was not NULL).
 *
 * When an identity is renamed, this function is called with the
 * (known) @a ego but the NEW @a name.
 *
 * When an identity is deleted, this function is called with the
 * (known) ego and "NULL" for the @a name.  In this case,
 * the @a ego is henceforth invalid (and the @a ctx should also be
 * cleaned up).
 *
 * @param cls closure
 * @param ego ego handle
 * @param ctx context for application to store data for this ego
 *                 (during the lifetime of this process, initially NULL)
 * @param name name assigned by the user for this ego,
 *                   NULL if the user just deleted the ego and it
 *                   must thus no longer be used
 */
static void
identity_cb (void *cls,
             struct GNUNET_IDENTITY_Ego *ego,
             void **ctx,
             const char *name)
{
  GtkTreeRowReference *rr;
  GtkTreeIter iter;
  GtkTreePath *path;

  if ((NULL == ego) && (NULL == name) && (NULL == ctx))
  {
    /* end of initial iteration, trigger loading selected zone */
    gnunet_namestore_gtk_zone_combobox_changed_cb (zone_combo_box, ml);
    return;
  }
  rr = *ctx;
  if (NULL == rr)
  {
    /* new identity, add to list */
    GNUNET_assert (NULL != name);
    /* clang-format off */
    gtk_list_store_insert_with_values (zone_liststore,
                                       &iter, 0,
                                       ZONE_LS_NAME, name,
                                       ZONE_LS_EGO, ego,
                                       -1);
    /* clang-format on */
    gtk_combo_box_set_active_iter (zone_combo_box, &iter);
    gtk_widget_set_sensitive (GTK_WIDGET (zone_combo_box), TRUE);
    gtk_widget_set_sensitive (GTK_WIDGET (
                                get_object ("gnunet_namestore_gtk_zone_entry")),
                              TRUE);
    path = gtk_tree_model_get_path (GTK_TREE_MODEL (zone_liststore), &iter);
    rr = gtk_tree_row_reference_new (GTK_TREE_MODEL (zone_liststore), path);
    *ctx = rr;
    gtk_tree_path_free (path);
    return;
  }
  path = gtk_tree_row_reference_get_path (rr);
  GNUNET_break (
    gtk_tree_model_get_iter (GTK_TREE_MODEL (zone_liststore), &iter, path));
  gtk_tree_path_free (path);
  if (NULL == name)
  {
    GtkTreeIter act_iter;

    /* identity was removed, remove from list */
    GNUNET_free (current_pseudonym);
    current_pseudonym = NULL;
    gtk_list_store_remove (zone_liststore, &iter);
    if (! gtk_combo_box_get_active_iter (zone_combo_box, &act_iter))
    {
      if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (zone_liststore),
                                         &act_iter))
      {
        /* make sure combo box remains selected if possible */
        gtk_combo_box_set_active (zone_combo_box, 0);
      }
      else
      {
        /* make combo box insensitive if nothing can be selected */
        gtk_widget_set_sensitive (GTK_WIDGET (zone_combo_box), FALSE);
        gtk_widget_set_sensitive (GTK_WIDGET (get_object (
                                                "gnunet_namestore_gtk_zone_entry")),
                                  FALSE);
        clear_zone_view ();
      }
    }
    return;
  }
  /* identity was renamed, rename in model */
  gtk_list_store_set (zone_liststore, &iter, ZONE_LS_NAME, name, -1);
}


/**
 * The user clicked the "add" button for a new zone.
 * Obtain the label, create the new zone and reset
 * the entry to empty.
 *
 * @param button the "add" button
 * @param user_data the builder, unused
 */
void
gnunet_namestore_gtk_zone_add_button_clicked_cb (GtkButton *button,
                                                 gpointer user_data)
{
  const char *label;
  GtkEntry *entry;

  (void) user_data;
  entry = GTK_ENTRY (get_object ("gnunet_namestore_gtk_zone_add_entry"));
  label = gtk_entry_get_text (entry);
  gtk_label_set_markup (status_label, _ ("Creating zone"));
  gtk_widget_set_sensitive (main_window, FALSE);
  iop = GNUNET_IDENTITY_create (identity,
                                label,
                                NULL,
                                GNUNET_IDENTITY_TYPE_ECDSA,
                                &iop_create_cont,
                                NULL);
  gtk_entry_set_text (entry, "");
}


/**
 * Delete all records of the current zone.
 *
 * @param cls NULL
 * @param key key of a record in the map to delete
 * @param value a `struct RecordInfo` to delete
 * @return #GNUNET_OK (continue to iterate)
 */
static int
delete_records (void *cls, const struct GNUNET_HashCode *key, void *value)
{
  struct RecordInfo *ri = value;
  struct OperationContext *oc;

  if (0 != ri->rd_count)
  {
    oc = GNUNET_new (struct OperationContext);
    GNUNET_CONTAINER_DLL_insert (oc_head, oc_tail, oc);
    oc->qe = GNUNET_NAMESTORE_records_store (namestore,
                                             pkey,
                                             ri->name,
                                             0,
                                             NULL,
                                             &operation_done_cont,
                                             oc);
  }
  free_ri (ri);
  return GNUNET_OK;
}


/**
 * The user clicked the "del" button for an existing zone.
 * Obtain the label/ego and delete the zone.
 *
 * @param button the "del" button
 * @param user_data the builder, unused
 */
void
gnunet_namestore_gtk_zone_del_button_clicked_cb (GtkButton *button,
                                                 gpointer user_data)
{
  char *pseu;
  (void) user_data;

  pseu = current_pseudonym;
  current_pseudonym = NULL;
  GNUNET_CONTAINER_multihashmap_iterate (n2r, &delete_records, NULL);
  gtk_tree_store_clear (ts);
  gtk_widget_set_sensitive (GTK_WIDGET (get_object (
                                          "gnunet_namestore_gtk_zone_del_button")),
                            FALSE);
  gtk_widget_set_sensitive (main_window, FALSE);
  iop = GNUNET_IDENTITY_delete (identity, pseu, &iop_cont, NULL);
  GNUNET_free (pseu);
}


/**
 * The user edited the zone label for 'adding' a new zone.
 * Check if the label is unique and well-formed and update
 * the sensitivity of the "add" button accordingly.
 *
 * @param editable the zone label that was changed
 * @param user_data the builder, unused
 */
void
gnunet_namestore_gtk_zone_add_entry_changed_cb (GtkEditable *editable,
                                                gpointer user_data)
{
  const char *label;

  (void) user_data;
  label = gtk_entry_get_text (GTK_ENTRY (editable));
  gtk_widget_set_sensitive (GTK_WIDGET (get_object (
                                          "gnunet_namestore_gtk_zone_add_button")),
                            NULL == fresh_label (label));
}


/**
 * Reads the configuration and populates TLDs
 *
 * @param cls unused
 * @param section name of section in config, always "gns"
 * @param option name of the option, TLDs start with "."
 * @param value value for the option, public key for TLDs
 */
static void
read_service_conf (void *cls,
                   const char *section,
                   const char *option,
                   const char *value)
{
  struct GNUNET_CRYPTO_EddsaPublicKey pk;
  struct GNS_TopLevelDomain *tld;

  if (option[0] != '.')
    return;
  if (GNUNET_OK !=
      GNUNET_STRINGS_string_to_data (value, strlen (value), &pk, sizeof (pk)))
  {
    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
                               section,
                               option,
                               _ (
                                 "Properly base32-encoded public key required"));
    return;
  }
  tld = GNUNET_new (struct GNS_TopLevelDomain);
  tld->tld = GNUNET_strdup (&option[1]);
  tld->pkey = pk;
  GNUNET_CONTAINER_DLL_insert (tld_head, tld_tail, tld);
}


/**
 * Actual main method that sets up the configuration window.
 *
 * @param cls the main loop handle
 */
static void
run (void *cls)
{
  gchar *label;

  ml = cls;
  cfg = GNUNET_GTK_main_loop_get_configuration (ml);
  GNUNET_CONFIGURATION_iterate_section_values (cfg,
                                               "gns",
                                               &read_service_conf,
                                               NULL);
  if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (cfg,
                                                        "gnunet-namestore-gtk",
                                                        "NICK_EXPIRATION",
                                                        &nick_expiration_time))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               "gnunet-namestore-gtk",
                               "NICK_EXPIRATION");
    GNUNET_GTK_main_loop_quit (ml);
    return;
  }
  if (GNUNET_OK != GNUNET_GTK_main_loop_build_window (ml, NULL))
  {
    GNUNET_GTK_main_loop_quit (ml);
    return;
  }
  GNUNET_GTK_set_icon_search_path ();
  GNUNET_GTK_setup_nls ();

  main_window = GTK_WIDGET (get_object ("gnunet_namestore_gtk_dialog"));
  status_label = GTK_LABEL (get_object ("gnunet_namestore_gtk_status_label"));
  zone_combo_box =
    GTK_COMBO_BOX (get_object ("gnunet_namestore_gtk_zone_combobox"));
  namestore = GNUNET_NAMESTORE_connect (cfg);
  if (NULL == namestore)
  {
    label = g_markup_printf_escaped (
      _ ("<b><big>Failed to connect to namestore</b></big>"));
    gtk_label_set_markup (status_label, label);
    g_free (label);
    GNUNET_SCHEDULER_add_shutdown (&cleanup_task, NULL);
    return;
  }
  ts = GTK_TREE_STORE (get_object ("gnunet_namestore_gtk_treestore"));
  tv = GTK_TREE_VIEW (get_object ("gnunet_namestore_gtk_treeview"));
  zone_liststore = GTK_LIST_STORE (get_object ("zone_liststore"));
  tm = GTK_TREE_MODEL (ts);
  n2r = GNUNET_CONTAINER_multihashmap_create (128, GNUNET_NO);
  identity = GNUNET_IDENTITY_connect (cfg, &identity_cb, NULL);
  gtk_widget_show (main_window);
  gtk_window_present (GTK_WINDOW (main_window));
  GNUNET_SCHEDULER_add_shutdown (&cleanup_task, NULL);
}


/**
 * Main function for gnunet-namestore-gtk.
 *
 * @param argc number of arguments
 * @param argv arguments
 * @return 0 on success
 */
int
main (int argc, char *const *argv)
{
  struct GNUNET_GETOPT_CommandLineOption options[] = {GNUNET_GETOPT_OPTION_END};
  int ret;

  if (GNUNET_OK ==
      GNUNET_GTK_main_loop_start ("gnunet-namestore-gtk",
                                  "gnunet-namestore-gtk",
                                  argc,
                                  argv,
                                  options,
                                  "gnunet_namestore_gtk_main_window.glade",
                                  &run))
    ret = gret;
  else
    ret = 1;
  return ret;
}


/* end of gnunet-namestore-gtk.c */
