/*
     This file is part of GNUnet
     Copyright (C) 2005-2013 GNUnet e.V.

     GNUnet is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published
     by the Free Software Foundation; either version 3, or (at your
     option) any later version.

     GNUnet is distributed in the hope that it will be useful, but
     WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     General Public License for more details.

     You should have received a copy of the GNU General Public License
     along with GNUnet; see the file COPYING.  If not, write to the
     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     Boston, MA 02110-1301, USA.
*/

/**
 * @file src/fs/gnunet-fs-gtk_publish-edit-dialog.c
 * @author Christian Grothoff
 */
#include "gnunet-fs-gtk_publish-edit-dialog.h"
#include "gnunet-fs-gtk.h"
#include <gnunet/gnunet_util_lib.h>

#include "metatypes.c"


/**
 * Columns in the publication metadata model.
 */
enum PUBLISH_MetadataModelColumns
{
  /**
     * A guint.
     */
  PUBLISH_METADATA_MC_TYPE = 0,

  /**
     * A guint.
     */
  PUBLISH_METADATA_MC_FORMAT = 1,

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

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

  /**
     * A gchararray.
     */
  PUBLISH_METADATA_MC_DESCRIPTION = 4,
};


/**
 * Columns in the publication metadata types model.
 */
enum PUBLISH_MetadataTypesModelColumns
{
  /**
     * A gint.
     */
  PUBLISH_METADATA_TYPES_MC_TYPE = 0,

  /**
     * A gint.
     */
  PUBLISH_METADATA_TYPES_MC_FORMAT = 1,

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

  /**
     * A gchararray.
     */
  PUBLISH_METADATA_TYPES_MC_DESCRIPTION = 3
};


/**
 * Columns in the publication types model.
 */
enum PUBLISH_TypesModelColumns
{
  /**
     * A gint.
     */
  PUBLISH_TYPES_MC_TYPE = 0,

  /**
     * A gchararray.
     */
  PUBLISH_TYPES_MC_TYPE_NAME = 1
};


/**
 * Columns in the publication keywords model.
 */
enum PUBLISH_KeywordsModelColumns
{
  /**
     * A gchararray.
     */
  PUBLISH_TYPES_MC_KEYWORD = 0,

  /**
     * A gboolean.
     */
  PUBLISH_TYPES_MC_ADDED = 1
};


/**
 * Internal state kept for each "edit" dialog where the user can edit
 * publishing information for a file.
 */
struct EditPublicationDialogContext
{
  /**
   * Builder for the dialog.
   */
  GtkBuilder *builder;

  /**
   * The 'window' object for the dialog.
   */
  GtkWindow *edit_publication_window;

  /**
   * The confirmation button which closes the dialog (only sensitive
   * if the values entered are valid).
   */
  GtkWidget *confirm_button;

  /**
   * Tree view showing the meta data for the file.
   */
  GtkTreeView *meta_treeview;

  /**
   * Tree view showing the keywords for the file.
   */
  GtkTreeView *keywords_treeview;

  /**
   * Image showing the preview image for the file.
   */
  GtkImage *preview_image;

  /**
   * Combo box where the user can select the anonymity level.
   */
  GtkComboBox *anonymity_combo;

  /**
   * Liststore of possible publication types.
   */
  GtkListStore *pubtypes_liststore;

  /**
   * Liststore of all possible meta types the user can choose from.
   * (updated to based on the selected publication type).
   */
  GtkListStore *metatypes_liststore;

  /**
   * Liststore showing the meta data of the file (associated with
   * the 'meta_treeview'.
   */
  GtkListStore *meta_liststore;

  /**
   * Liststore with the keywords of the file (associated with the
   * 'keywords_treeview'.
   */
  GtkListStore *keywords_liststore;

  /**
   * Spin button to select content priority level for the file.
   */
  GtkSpinButton *priority_spin;

  /**
   * Spin button to select the expiration year.
   */
  GtkSpinButton *expiration_year_spin;

  /**
   * Spin button to select the replication level.
   */
  GtkSpinButton *replication_spin;

  /**
   * Entry line for adding additional keywords.
   */
  GtkEntry *keyword_entry;

  /**
   * Entry line for setting a namespace root (possibly invisible).
   */
  GtkEntry *root_entry;

  /**
   * Entry line to check indexing vs. inserting (possibly invisible)
   */
  GtkToggleButton *index_checkbutton;

  /**
   * Type ID of the last selected item in the GtkCellRendererCombo
   * of the meta data tree view.
   */
  gint meta_combo_selected_type_id;

  /**
   * Continuation to call once the dialog has been closed
   */
  GNUNET_FS_GTK_EditPublishDialogCallback cb;

  /**
   * Closure for 'cb'.
   */
  void *cb_cls;

  /**
   * Briefly used temporary meta data set.
   */
  struct GNUNET_FS_MetaData *md;

  /**
   * Information about the file being published as seen by the FS-API.
   * This is what we are primarily editing.
   */
  struct GNUNET_FS_FileInformation *fip;

  /**
   * Flag to track if we changed the preview and thus should keep/discard
   * binary metadata.
   */
  int preview_changed;

  /**
   * Is this operation for a directory?
   */
  int is_directory;

  /**
   * Is it allowed for the user to supply keywords in this dialog?
   */
  int allow_no_keywords;
};


/**
 * Free resources associated with the edit publication dialog.
 *
 * @param ctx the context of the dialog to release resources of
 */
static void
free_edit_dialog_context (struct EditPublicationDialogContext *ctx)
{
  gtk_widget_destroy (GTK_WIDGET (ctx->edit_publication_window));
  g_object_unref (G_OBJECT (ctx->builder));
  GNUNET_free (ctx);
}


/* ****************** metadata editing ******************** */


/**
 * Update the set of metatypes listed in the dialog based on the
 * given code.
 *
 * @param ctx main dialog context
 * @param code which set of metatypes is desired?
 */
static void
change_metatypes (struct EditPublicationDialogContext *ctx, gint code)
{
  gint pubtype_count;
#if HAVE_EXTRACTOR
  gint max_type;
  gint i;
  GtkTreeIter iter;
#endif

  /* double-check that 'code' is valid */
  for (pubtype_count = 0; NULL != types[pubtype_count]; pubtype_count++)
    ;
  GNUNET_assert (code < pubtype_count);

  /* clear existing selection of metatypes */
  gtk_list_store_clear (ctx->metatypes_liststore);
#if HAVE_EXTRACTOR
  max_type = EXTRACTOR_metatype_get_max ();
  /* add new types based on selection */
  for (i = 0; types[code][i] != EXTRACTOR_METATYPE_RESERVED; i++)
    if ((types[code][i] < max_type) && (types[code][i] > 0))
      gtk_list_store_insert_with_values (ctx->metatypes_liststore,
                                         &iter,
                                         G_MAXINT,
                                         PUBLISH_METADATA_TYPES_MC_TYPE,
                                         types[code][i],
                                         PUBLISH_METADATA_TYPES_MC_FORMAT,
                                         EXTRACTOR_METAFORMAT_UTF8,
                                         PUBLISH_METADATA_TYPES_MC_TYPE_NAME,
                                         EXTRACTOR_metatype_to_string (
                                           types[code][i]),
                                         PUBLISH_METADATA_TYPES_MC_DESCRIPTION,
                                         EXTRACTOR_metatype_to_description (
                                           types[code][i]),
                                         -1);
#endif
}


/**
 * The user has selected a different publication type.
 * Update the meta type selection.
 *
 * @param widget the publication type combo box widget
 * @param user_data the 'struct EditPublicationDialogContext'
 */
void
GNUNET_GTK_edit_publication_type_combo_changed_cb (GtkComboBox *widget,
                                                   gpointer user_data)
{
  struct EditPublicationDialogContext *ctx = user_data;
  GtkTreeIter iter;
  gint code;

  if (! gtk_combo_box_get_active_iter (widget, &iter))
    return;
  gtk_tree_model_get (GTK_TREE_MODEL (ctx->pubtypes_liststore),
                      &iter,
                      PUBLISH_TYPES_MC_TYPE,
                      &code,
                      -1);
  change_metatypes (ctx, code);
}


/**
 * The user has changed the selection in the meta data tree view.
 * Update the sensitivity of the 'delete' button.
 *
 * @param ts the tree selection object
 * @param user_data the 'struct EditPublicationDialogContext'
 */
void
GNUNET_GTK_edit_publication_metadata_treeview_selection_changed_cb (
  GtkTreeSelection *ts,
  gpointer user_data)
{
  struct EditPublicationDialogContext *ctx = user_data;

  gtk_widget_set_sensitive (GTK_WIDGET (gtk_builder_get_object (
                              ctx->builder,
                              "GNUNET_GTK_edit_publication_delete_button")),
                            gtk_tree_selection_get_selected (ts, NULL, NULL));
}


/**
 * The user changed (and confirmed the change) the type of a
 * meta-data item in the meta data tree view.  Update the type and
 * text in the list store accordingly.
 *
 * @param renderer widget where the change happened
 * @param path which item was changed in the tree view
 * @param new_text new value for the item
 * @param user_data the 'struct EditPublicationDialogContext'
 */
void
GNUNET_GTK_edit_publication_metadata_tree_view_type_renderer_edited_cb (
  GtkCellRendererText *renderer,
  gchar *path,
  gchar *new_text,
  gpointer user_data)
{
  struct EditPublicationDialogContext *ctx = user_data;
  GtkTreeIter iter;
  gint type_id;

  if (! gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (
                                               ctx->meta_liststore),
                                             &iter,
                                             path))
  {
    GNUNET_break (0);
    return;
  }
  if (-1 == ctx->meta_combo_selected_type_id)
  {
    GNUNET_break (0);
    return;
  }
  type_id = ctx->meta_combo_selected_type_id;
  ctx->meta_combo_selected_type_id = -1;
  gtk_list_store_set (ctx->meta_liststore,
                      &iter,
                      PUBLISH_METADATA_MC_TYPE,
                      type_id,
                      PUBLISH_METADATA_MC_FORMAT,
                      EXTRACTOR_METAFORMAT_UTF8,
#if HAVE_EXTRACTOR
                      PUBLISH_METADATA_MC_TYPE_NAME,
                      EXTRACTOR_metatype_to_string (type_id),
                      PUBLISH_METADATA_MC_DESCRIPTION,
                      EXTRACTOR_metatype_to_description (type_id),
#endif
                      -1);
}


/**
 * The user changed the type of a meta-data item in the meta data
 * tree view.  Obtain the selected type_id and store it in
 * the 'meta_combo_selected_type_id' field for use by
 * 'GNUNET_GTK_edit_publication_metadata_tree_view_type_renderer_edited_cb'.
 *
 * @param combo combo box that was dropped down
 * @param path_string which item was changed in the tree view
 * @param new_iter item that is now selected in the drop-down combo box
 * @param user_data the 'struct EditPublicationDialogContext'
 */
void
GNUNET_GTK_edit_publication_metadata_tree_view_type_renderer_changed_cb (
  GtkCellRendererCombo *combo,
  gchar *path_string,
  GtkTreeIter *new_iter,
  gpointer user_data)
{
  struct EditPublicationDialogContext *ctx = user_data;
  GtkTreeModel *combo_model;
  gint type_id;

  g_object_get (combo, "model", &combo_model, NULL);
  gtk_tree_model_get (combo_model,
                      new_iter,
                      PUBLISH_METADATA_MC_TYPE,
                      &type_id,
                      -1);
  ctx->meta_combo_selected_type_id = type_id;
}


/**
 * The user changed (and confirmed the change) the value of a
 * meta-data item in the meta data tree view.  Update the value
 * in the list store accordingly.
 *
 * @param renderer widget where the change happened
 * @param path which item was changed in the tree view
 * @param new_text new value for the item
 * @param user_data the 'struct EditPublicationDialogContext'
 */
void
GNUNET_GTK_edit_publication_metadata_tree_view_value_renderer_edited_cb (
  GtkCellRendererText *renderer,
  gchar *path,
  gchar *new_text,
  gpointer user_data)
{
  struct EditPublicationDialogContext *ctx = user_data;
  GtkTreeIter iter;
  gint metatype;
  char *avalue;
  const char *ivalue;
  size_t slen;
  char *pos;

  if (! gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (
                                               ctx->meta_liststore),
                                             &iter,
                                             path))
  {
    GNUNET_break (0);
    return;
  }

  gtk_tree_model_get (GTK_TREE_MODEL (ctx->meta_liststore),
                      &iter,
                      PUBLISH_METADATA_MC_TYPE,
                      &metatype,
                      -1);
  if (metatype == EXTRACTOR_METATYPE_GNUNET_ORIGINAL_FILENAME)
  {
    /* apply filename rules */
    /* First, use UNIX-style separators */
    avalue = GNUNET_strdup (new_text);
    while (NULL != (pos = strstr (avalue, "\\")))
      *pos = '/';

    /* if user put '/' at the end, remove it' */
    slen = strlen (avalue);
    while ((slen > 1) && (avalue[slen - 1] == '\\'))
    {
      avalue[slen - 1] = '\0';
      slen--;
    }

    /* However, directories must end with '/', so add it */
    if ((new_text[strlen (new_text) - 1] != '/') &&
        ctx->is_directory == GNUNET_YES)
    {
      char *tmp;

      GNUNET_asprintf (&tmp, "%s/", avalue);
      GNUNET_free (avalue);
      avalue = tmp;
    }

    /* Also, replace '../' everywhere with "___" */
    while (NULL != (pos = strstr (avalue, "../")))
      memset (pos, '_', 3);

    ivalue = avalue;
  }
  else
  {
    ivalue = new_text;
    avalue = NULL;
  }
  gtk_list_store_set (ctx->meta_liststore,
                      &iter,
                      PUBLISH_METADATA_MC_VALUE,
                      ivalue,
                      -1);
  GNUNET_free (avalue);
}


/**
 * The user has pushed the 'add' button for metadata.  Add a 'dummy' value
 * to our meta data store (to be edited by the user).
 *
 * @param button the 'add' button
 * @param user_data the 'struct EditPublicationDialogContext'
 */
void
GNUNET_GTK_edit_publication_add_button_clicked_cb (GtkButton *button,
                                                   gpointer user_data)
{
  struct EditPublicationDialogContext *ctx = user_data;
  GtkTreeIter iter;

  gtk_list_store_insert_with_values (ctx->meta_liststore,
                                     &iter,
                                     0,
                                     PUBLISH_METADATA_MC_TYPE,
                                     0,
                                     PUBLISH_METADATA_MC_FORMAT,
                                     EXTRACTOR_METAFORMAT_UTF8,
                                     PUBLISH_METADATA_MC_TYPE_NAME,
                                     _ ("Select a type"),
                                     PUBLISH_METADATA_MC_VALUE,
                                     _ ("Specify a value"),
                                     PUBLISH_METADATA_MC_DESCRIPTION,
                                     NULL,
                                     -1);
}


/**
 * The user has pushed the 'del' button for metadata.
 * If there is a metadata selected, remove it from the list store.
 *
 * @param button the delete button
 * @param user_data the 'struct EditPublicationDialogContext'
 */
void
GNUNET_GTK_edit_publication_delete_button_clicked_cb (GtkButton *button,
                                                      gpointer user_data)
{
  struct EditPublicationDialogContext *ctx = user_data;
  GtkTreeIter iter;
  GtkTreeSelection *meta_selection;

  meta_selection = gtk_tree_view_get_selection (ctx->meta_treeview);
  if (! gtk_tree_selection_get_selected (meta_selection, NULL, &iter))
  {
    GNUNET_break (0);
    return;
  }
  if (gtk_list_store_remove (ctx->meta_liststore, &iter))
    gtk_tree_selection_select_iter (meta_selection, &iter);
}


/**
 * The user has selected another preview image file.  Update the
 * preview image.
 *
 * @param widget the file chooser dialog that completed
 * @param user_data the 'struct EditPublicationDialogContext'
 */
void
GNUNET_GTK_edit_publication_metadata_preview_file_chooser_button_file_set_cb (
  GtkFileChooserButton *widget,
  gpointer user_data)
{
  struct EditPublicationDialogContext *ctx = user_data;
  gchar *fn;

  fn = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (widget));
  gtk_image_set_from_file (ctx->preview_image, fn);
  g_free (fn);
  ctx->preview_changed = GNUNET_YES;
}


/* ****************** keyword list editing ******************** */


/**
 * The user has changed the selection in the keyword tree view.
 * Update the sensitivity of the 'delete' button.
 *
 * @param ts the tree selection object
 * @param user_data the 'struct EditPublicationDialogContext'
 */
void
GNUNET_GTK_edit_publication_keyword_list_treeview_selection_changed_cb (
  GtkTreeSelection *ts,
  gpointer user_data)
{
  struct EditPublicationDialogContext *ctx = user_data;

  gtk_widget_set_sensitive (
    GTK_WIDGET (gtk_builder_get_object (
      ctx->builder,
      "GNUNET_GTK_edit_publication_keyword_list_del_button")),
    gtk_tree_selection_get_selected (ts, NULL, NULL));
}


/**
 * The user has edited the keyword entry line.  Update the
 * sensitivity of the 'add' button.
 *
 * @param editable the entry line for keywords
 * @param user_data the 'struct EditPublicationDialogContext'
 */
void
GNUNET_GTK_edit_publication_keyword_entry_changed_cb (GtkEditable *editable,
                                                      gpointer user_data)
{
  struct EditPublicationDialogContext *ctx = user_data;
  const char *keyword;

  keyword = gtk_entry_get_text (ctx->keyword_entry);
  gtk_widget_set_sensitive (
    GTK_WIDGET (gtk_builder_get_object (
      ctx->builder,
      "GNUNET_GTK_edit_publication_keyword_list_add_button")),
    (strlen (keyword) > 0) ? TRUE : FALSE);
}


/**
 * Update the sensitivity of the 'confirm' button based on
 * the availability of keywords and whether they are required or
 * not.
 *
 * @param ctx the 'struct EditPublicationDialogContext'
 */
static void
update_confirm_sensitivity (struct EditPublicationDialogContext *ctx)
{
  GtkTreeIter iter;

  if ((! ctx->allow_no_keywords) &&
      (! gtk_tree_model_get_iter_first (GTK_TREE_MODEL (
                                          ctx->keywords_liststore),
                                        &iter)))
    gtk_widget_set_sensitive (ctx->confirm_button, FALSE);
  else
    gtk_widget_set_sensitive (ctx->confirm_button, TRUE);
}


/**
 * The user has pushed the 'del' button for the keyword.
 * If there is a keyword selected, remove it from the list store.
 *
 * @param button the button
 * @param user_data the 'struct EditPublicationDialogContext'
 */
void
GNUNET_GTK_edit_publication_keyword_list_del_button_clicked_cb (
  GtkButton *button,
  gpointer user_data)
{
  struct EditPublicationDialogContext *ctx = user_data;
  GtkTreeIter iter;
  GtkTreeSelection *keywords_selection;

  keywords_selection = gtk_tree_view_get_selection (ctx->keywords_treeview);
  if (! gtk_tree_selection_get_selected (keywords_selection, NULL, &iter))
  {
    GNUNET_break (0);
    return;
  }
  if (gtk_list_store_remove (GTK_LIST_STORE (ctx->keywords_liststore), &iter))
    gtk_tree_selection_select_iter (keywords_selection, &iter);

  /* disable confirm button if keywords are required and we have no more keywords */
  update_confirm_sensitivity (ctx);
}


/**
 * The user has pushed the 'add' button for the keyword (or pressed RETURN).
 * If there is a keyword in the line, add it to the list store.
 *
 * @param button the add button, or NULL (if we are called from 'RETURN')
 * @param user_data the 'struct EditPublicationDialogContext'
 */
void
GNUNET_GTK_edit_publication_keyword_list_add_button_clicked_cb (
  GtkButton *button,
  gpointer user_data)
{
  struct EditPublicationDialogContext *ctx = user_data;
  const char *keyword;
  GtkTreeIter iter;

  keyword = gtk_entry_get_text (ctx->keyword_entry);
  if (strlen (keyword) == 0)
    return;
  gtk_list_store_insert_with_values (ctx->keywords_liststore,
                                     &iter,
                                     G_MAXINT,
                                     PUBLISH_TYPES_MC_KEYWORD,
                                     keyword,
                                     PUBLISH_TYPES_MC_ADDED,
                                     TRUE,
                                     -1);
  gtk_entry_set_text (ctx->keyword_entry, "");
  update_confirm_sensitivity (ctx);
}


/**
 * The user has pushed a button in the entry line.  Check if it was 'RETURN'
 * and if so consider executing the 'add' action.
 *
 * @param widget the entry line
 * @param event the event information
 * @param user_data the 'struct EditPublicationDialogContext'
 * @return TRUE if we handled the event, FALSE if not
 */
gboolean
GNUNET_GTK_edit_publication_keyword_entry_key_press_event_cb (
  GtkWidget *widget,
  GdkEventKey *event,
  gpointer user_data)
{
  struct EditPublicationDialogContext *ctx = user_data;

  if (event->keyval != GDK_KEY_Return)
    return FALSE;
  GNUNET_GTK_edit_publication_keyword_list_add_button_clicked_cb (NULL, ctx);
  return TRUE;
}


/* ****************** handlers for closing the dialog ******************** */


/**
 * The user clicked the 'cancel' button.  Abort the operation.
 *
 * @param button the cancel button
 * @param user_data the 'struct EditPublicationDialogContext'
 */
void
GNUNET_GTK_edit_publication_cancel_button_clicked_cb (GtkButton *button,
                                                      gpointer user_data)
{
  struct EditPublicationDialogContext *ctx = user_data;

  ctx->cb (ctx->cb_cls, GTK_RESPONSE_CANCEL, NULL);
  free_edit_dialog_context (ctx);
}


/**
 * The user closed the window.  Abort the operation.
 *
 * @param widget the window
 * @param event the event that caused the window to close
 * @param user_data the 'struct EditPublicationDialogContext'
 * @return TRUE (always)
 */
gboolean
GNUNET_GTK_edit_publication_window_delete_event_cb (GtkWidget *widget,
                                                    GdkEvent *event,
                                                    gpointer user_data)
{
  struct EditPublicationDialogContext *ctx = user_data;

  ctx->cb (ctx->cb_cls, GTK_RESPONSE_CANCEL, NULL);
  free_edit_dialog_context (ctx);
  return TRUE;
}


/**
 * Copy binary meta data from to the new container and also preserve
 * all entries that were not changed.  In particular, all binary meta
 * data is removed if the preview was changed, otherwise it is all
 * kept.  Similarly, if values are still in the liststore, they are
 * fully kept (including plugin name and original format).  "Keeping"
 * a value means that it is added to the 'md' meta data in the dialog
 * context.
 *
 * @param cls closure, a 'struct FileInformationUpdateContext';
 *        contains the 'new' meta data to construct in the 'md'
 *        field and the liststore to check the current value
 *        against in 'meta_liststore'.
 * @param plugin_name name of the plugin that produced this value;
 *        special values can be used (i.e. '\<zlib\>' for zlib being
 *        used in the main libextractor library and yielding
 *        meta data).
 * @param type libextractor-type describing the meta data
 * @param format basic format information about data
 * @param data_mime_type mime-type of data (not of the original file);
 *        can be NULL (if mime-type is not known)
 * @param data actual meta-data found
 * @param data_len number of bytes in data
 * @return 0 to continue extracting
 */
static int
preserve_meta_items (void *cls,
                     const char *plugin_name,
                     enum EXTRACTOR_MetaType type,
                     enum EXTRACTOR_MetaFormat format,
                     const char *data_mime_type,
                     const char *data,
                     size_t data_len)
{
  struct EditPublicationDialogContext *ctx = cls;
  GtkTreeIter iter;
  gchar *value;
  guint ntype;
  guint nformat;
  int keep;

  keep = GNUNET_NO;
  switch (format)
  {
  case EXTRACTOR_METAFORMAT_UTF8:
  case EXTRACTOR_METAFORMAT_C_STRING:
    if (TRUE ==
        gtk_tree_model_get_iter_first (GTK_TREE_MODEL (ctx->meta_liststore),
                                       &iter))
    {
      do
      {
        gtk_tree_model_get (GTK_TREE_MODEL (ctx->meta_liststore),
                            &iter,
                            PUBLISH_METADATA_MC_TYPE,
                            &ntype,
                            PUBLISH_METADATA_MC_FORMAT,
                            &nformat,
                            PUBLISH_METADATA_MC_VALUE,
                            &value,
                            -1);
        if ((ntype == type) && (nformat == format) &&
            (0 == strcmp (value, data)))
        {
          gtk_list_store_remove (ctx->meta_liststore, &iter);
          keep = GNUNET_YES;
          g_free (value);
          break;
        }
        g_free (value);
      } while (
        TRUE ==
        gtk_tree_model_iter_next (GTK_TREE_MODEL (ctx->meta_liststore), &iter));
    }
    break;
  case EXTRACTOR_METAFORMAT_UNKNOWN:
    break;
  case EXTRACTOR_METAFORMAT_BINARY:
    if (ctx->preview_changed == GNUNET_NO)
      keep = GNUNET_YES;
    break;
  default:
    GNUNET_break (0);
    break;
  }
  if (GNUNET_YES == keep)
    GNUNET_break (GNUNET_OK ==
                  GNUNET_FS_meta_data_insert (ctx->md,
                                                     plugin_name,
                                                     type,
                                                     format,
                                                     data_mime_type,
                                                     data,
                                                     data_len));
  return 0;
}


#if HAVE_EXTRACTOR
/**
 * Type of a function that libextractor calls for each
 * meta data item found.  Used to get the mime type.
 *
 * @param cls pointer to where to store the mime type.
 * @param plugin_name name of the plugin that produced this value;
 *        special values can be used (i.e. '&lt;zlib&gt;' for zlib being
 *        used in the main libextractor library and yielding
 *        meta data).
 * @param type libextractor-type describing the meta data
 * @param format basic format information about data
 * @param data_mime_type mime-type of data (not of the original file);
 *        can be NULL (if mime-type is not known)
 * @param data actual meta-data found
 * @param data_len number of bytes in data
 * @return 0 to continue extracting, 1 to abort
 */
static int
le_callback (void *cls,
             const char *plugin_name,
             enum EXTRACTOR_MetaType type,
             enum EXTRACTOR_MetaFormat format,
             const char *data_mime_type,
             const char *data,
             size_t data_len)
{
  char **ret = cls;

  if ((EXTRACTOR_METATYPE_MIMETYPE == type) &&
      (EXTRACTOR_METAFORMAT_BINARY != format))
  {
    *ret = GNUNET_strdup (data);
    return 1;
  }
  return 0;
}
#endif


/**
 * Use GNU libextractor to get the mime type for the given data.
 *
 * @param data binary data of the file
 * @param data_size number of bytes in 'data'
 */
static char *
get_mime_type (const void *data, gsize data_size)
{
#if HAVE_EXTRACTOR
  char *ret;
  struct EXTRACTOR_PluginList *pl;

  pl =
    EXTRACTOR_plugin_add (NULL, "mime", NULL, EXTRACTOR_OPTION_DEFAULT_POLICY);
  if (NULL == pl)
    return NULL;
  ret = NULL;
  EXTRACTOR_extract (pl, NULL, data, data_size, &le_callback, &ret);
  EXTRACTOR_plugin_remove_all (pl);
  return ret;
#else
  return NULL;
#endif
}


/**
 * Function called to update the information in FI based on the changes made in
 * the edit dialog.
 *
 * @param cls closure with a 'struct FileInformationUpdateContext *'
 * @param fi the entry in the publish-structure
 * @param length length of the file or directory
 * @param meta metadata for the file or directory (can be modified)
 * @param uri pointer to the keywords that will be used for this entry (can be modified)
 * @param bo block options (can be modified)
 * @param do_index should we index (can be modified)
 * @param client_info pointer to client context set upon creation (can be modified)
 * @return #GNUNET_SYSERR (aborts after first call)
 */
static int
file_information_update (void *cls,
                         struct GNUNET_FS_FileInformation *fi,
                         uint64_t length,
                         struct GNUNET_FS_MetaData *meta,
                         struct GNUNET_FS_Uri **uri,
                         struct GNUNET_FS_BlockOptions *bo,
                         int *do_index,
                         void **client_info)
{
  struct EditPublicationDialogContext *ctx = cls;
  GtkTreeIter iter;
  gint year;

  /* gather publishing options  */
  *do_index = gtk_toggle_button_get_active (ctx->index_checkbutton);
  GNUNET_break (
    GNUNET_GTK_get_selected_anonymity_combo_level (ctx->anonymity_combo,
                                                   &bo->anonymity_level));
  bo->content_priority = gtk_spin_button_get_value (ctx->priority_spin);
  bo->replication_level = gtk_spin_button_get_value (ctx->replication_spin);
  year = gtk_spin_button_get_value (ctx->expiration_year_spin);
  bo->expiration_time = GNUNET_TIME_year_to_time (year);

  /* update keyword-URI */
  if (NULL != (*uri))
    GNUNET_FS_uri_destroy (*uri);
  *uri = NULL;
  if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (ctx->keywords_liststore),
                                     &iter))
  {
    do
    {
      gchar *value;

      gtk_tree_model_get (GTK_TREE_MODEL (ctx->keywords_liststore),
                          &iter,
                          PUBLISH_TYPES_MC_KEYWORD,
                          &value,
                          -1);
      if (NULL == *uri)
        *uri = GNUNET_FS_uri_ksk_create_from_args (1, (const char **) &value);
      else
        GNUNET_FS_uri_ksk_add_keyword (*uri, value, GNUNET_NO);
      g_free (value);
    } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (ctx->keywords_liststore),
                                       &iter));
  }

  /* update meta data; first, we copy the unchanged values from the original meta data */
  ctx->md = GNUNET_FS_meta_data_create ();
  GNUNET_FS_meta_data_iterate (meta, &preserve_meta_items, ctx);
  /* clear original meta data */
  GNUNET_FS_meta_data_clear (meta);
  /* add all of the 'preserved' values */
  GNUNET_FS_meta_data_merge (meta, ctx->md);
  GNUNET_FS_meta_data_destroy (ctx->md);
  ctx->md = NULL;
  /* now add all of the values from the model; adding will simply do
     nothing for values that are already in the set because they were preserved */
  if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (ctx->meta_liststore),
                                     &iter))
  {
    do
    {
      guint ntype;
      guint nformat;
      gchar *value;

      gtk_tree_model_get (GTK_TREE_MODEL (ctx->meta_liststore),
                          &iter,
                          PUBLISH_METADATA_MC_TYPE,
                          &ntype,
                          PUBLISH_METADATA_MC_FORMAT,
                          &nformat,
                          PUBLISH_METADATA_MC_VALUE,
                          &value,
                          -1);
      if (ntype > 0)
      {
        GNUNET_FS_meta_data_insert (meta,
                                           "<user>",
                                           ntype,
                                           nformat,
                                           "text/plain",
                                           value,
                                           strlen (value) + 1);
      }
      g_free (value);
    } while (
      gtk_tree_model_iter_next (GTK_TREE_MODEL (ctx->meta_liststore), &iter));
  }

  /* finally, if we got a new preview, add it as well */
  if (ctx->preview_changed == GNUNET_YES)
  {
    gchar *fn;
    char *data;
    gsize data_size;
    char *mime;
    GFile *f;

    fn =
      gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (gtk_builder_get_object (
        ctx->builder,
        "GNUNET_GTK_edit_publication_metadata_preview_file_chooser_button")));
    f = g_file_new_for_path (fn);
    if (! g_file_load_contents (f, NULL, &data, &data_size, NULL, NULL))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  _ ("Could not load preview `%s' into memory\n"),
                  fn);
    }
    else
    {
      mime = get_mime_type (data, data_size);
#if LINUX
      if (NULL == mime)
      {
        /* fall back to Gtk mime-detection; this only works on GNU */
        GFileInfo *finfo;
        const char *gmime;

        finfo = g_file_query_info (f,
                                   G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
                                   0,
                                   NULL,
                                   NULL);
        gmime =
          g_file_info_get_attribute_string (finfo,
                                            G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
        if (NULL != gmime)
          mime = GNUNET_strdup (gmime);
        g_object_unref (finfo);
      }
#endif
      GNUNET_FS_meta_data_insert (meta,
                                         "<user>",
                                         EXTRACTOR_METATYPE_THUMBNAIL,
                                         EXTRACTOR_METAFORMAT_BINARY,
                                         mime,
                                         data,
                                         data_size);
      GNUNET_free (mime);
    }
    g_object_unref (f);
    g_free (fn);
  }
  return GNUNET_SYSERR; /* only visit top-level item */
}


/**
 * The user clicked the 'confirm' button.  Push the edits back into the
 * FileInformation structure and given it and the options back to the
 * callback.  Then clean up the dialog.
 *
 * @param button the cancel button
 * @param user_data the 'struct EditPublicationDialogContext'
 */
void
GNUNET_GTK_edit_publication_confirm_button_clicked_cb (GtkButton *button,
                                                       gpointer user_data)
{
  struct EditPublicationDialogContext *ctx = user_data;

  /* push back changes to file-information */
  GNUNET_FS_file_information_inspect (ctx->fip, &file_information_update, ctx);
  /* call our continuation */
  ctx->cb (ctx->cb_cls, GTK_RESPONSE_OK, gtk_entry_get_text (ctx->root_entry));
  /* free resources from the edit dialog */
  free_edit_dialog_context (ctx);
}


/* ****************** code for initialization of the dialog ******************** */


/**
 * Add each of the keywords to the keyword list store.
 *
 * @param cls closure
 * @param keyword the keyword
 * @param is_mandatory is the keyword mandatory (in a search)
 * @return GNUNET_OK to continue to iterate
 */
static int
add_keyword (void *cls, const char *keyword, int is_mandatory)
{
  GtkListStore *ls;
  GtkTreeIter iter;

  ls = GTK_LIST_STORE (cls);
  gtk_list_store_insert_with_values (ls,
                                     &iter,
                                     G_MAXINT,
                                     PUBLISH_TYPES_MC_KEYWORD,
                                     keyword,
                                     PUBLISH_TYPES_MC_ADDED,
                                     FALSE,
                                     -1);
  return GNUNET_OK;
}


/**
 * Set the title of the window based on the filename in the meta data.
 *
 * @param cls the window
 * @param plugin_name name of the plugin that produced this value;
 *        special values can be used (i.e. '&lt;zlib&gt;' for zlib being
 *        used in the main libextractor library and yielding
 *        meta data).
 * @param type libextractor-type describing the meta data
 * @param format basic format information about data
 * @param data_mime_type mime-type of data (not of the original file);
 *        can be NULL (if mime-type is not known)
 * @param data actual meta-data found
 * @param data_len number of bytes in data
 * @return 0 to continue extracting, 1 to abort
 */
static int
set_window_title_to_filename (void *cls,
                              const char *plugin_name,
                              enum EXTRACTOR_MetaType type,
                              enum EXTRACTOR_MetaFormat format,
                              const char *data_mime_type,
                              const char *data,
                              size_t data_len)
{
  char *utf8;

  if (EXTRACTOR_METATYPE_GNUNET_ORIGINAL_FILENAME != type)
    return 0;
  utf8 = GNUNET_FS_GTK_dubious_meta_to_utf8 (format, data, data_len);
  if (NULL == utf8)
    return 0;
  gtk_window_set_title (GTK_WINDOW (cls), utf8);
  GNUNET_free (utf8);
  return 1;
}


/**
 * Function called to extract the information from FI to populate the edit dialog.
 *
 * @param cls the 'struct EditPublicationDialogContext'
 * @param fi the entry in the publish-structure (unused)
 * @param length length of the file or directory (unused)
 * @param meta metadata for the file or directory (can be modified)
 * @param uri pointer to the keywords that will be used for this entry (can be modified)
 * @param bo block options
 * @param do_index should we index (can be modified)
 * @param client_info pointer to client context set upon creation (can be modified)
 * @return GNUNET_SYSERR (aborts after first call)
 */
static int
file_information_import (void *cls,
                         struct GNUNET_FS_FileInformation *fi,
                         uint64_t length,
                         struct GNUNET_FS_MetaData *meta,
                         struct GNUNET_FS_Uri **uri,
                         struct GNUNET_FS_BlockOptions *bo,
                         int *do_index,
                         void **client_info)
{
  struct EditPublicationDialogContext *ctx = cls;
  GdkPixbuf *pixbuf;
  int year;

  /* import options */
  year = (int) GNUNET_TIME_time_to_year (bo->expiration_time);
  gtk_spin_button_set_value (ctx->expiration_year_spin, year);
  GNUNET_GTK_select_anonymity_combo_level (ctx->anonymity_combo,
                                           bo->anonymity_level);
  gtk_spin_button_set_value (ctx->priority_spin, bo->content_priority);
  gtk_spin_button_set_value (ctx->replication_spin, bo->replication_level);
  gtk_toggle_button_set_active (ctx->index_checkbutton, *do_index);


  /* import keywords */
  if (NULL != *uri)
    GNUNET_FS_uri_ksk_get_keywords (*uri,
                                    &add_keyword,
                                    ctx->keywords_liststore);

  /* import meta data */
  if (NULL != meta)
  {
    GNUNET_FS_meta_data_iterate (meta,
                                        &GNUNET_FS_GTK_add_meta_data_to_list_store,
                                        ctx->meta_liststore);
    pixbuf = GNUNET_FS_GTK_get_thumbnail_from_meta_data (meta);
    if (pixbuf != NULL)
    {
      gtk_image_set_from_pixbuf (ctx->preview_image, pixbuf);
    }
  }

  /* Also update window title based on filename */
  gtk_window_set_title (ctx->edit_publication_window, _ ("<unnamed>"));
  GNUNET_FS_meta_data_iterate (meta,
                                      &set_window_title_to_filename,
                                      ctx->edit_publication_window);
  return GNUNET_SYSERR; /* only visit top-level item */
}


/**
 * Open the dialog to edit file information data.
 *
 * @param parent parent window of the dialog
 * @param fip information about the file information that is to be edited
 * @param is_namespace_edit #GNUNET_YES if we are editing a namespace advertisement;
 *        this means that keywords are required and that a "root" can be
 *        entered
 * @param cb function to call when the dialog is closed
 * @param cb_cls closure for @a cb
 */
void
GNUNET_FS_GTK_edit_publish_dialog (GtkWindow *parent,
                                   struct GNUNET_FS_FileInformation *fip,
                                   int is_namespace_edit,
                                   GNUNET_FS_GTK_EditPublishDialogCallback cb,
                                   gpointer cb_cls)
{
  GtkTreeIter iter;
  gint code;
  GtkComboBox *pubtypes_combo;
  GtkLabel *index_label;
  GtkLabel *root_label;
  struct EditPublicationDialogContext *ctx;

  ctx = GNUNET_new (struct EditPublicationDialogContext);
  ctx->fip = fip;
  ctx->preview_changed = GNUNET_NO;
  ctx->allow_no_keywords = is_namespace_edit ? GNUNET_NO : GNUNET_YES;
  ctx->is_directory = GNUNET_FS_file_information_is_directory (fip);
  ctx->cb = cb;
  ctx->cb_cls = cb_cls;
  ctx->meta_combo_selected_type_id = -1;
  ctx->builder =
    GNUNET_GTK_get_new_builder ("gnunet_fs_gtk_edit_publication.glade", ctx);

  if (NULL == ctx->builder)
  {
    GNUNET_free (ctx);
    return;
  }

  /* obtain various widgets for use later */
  ctx->pubtypes_liststore = GTK_LIST_STORE (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_publication_types_liststore"));
  ctx->metatypes_liststore = GTK_LIST_STORE (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_publication_metadata_types_liststore"));
  ctx->meta_treeview = GTK_TREE_VIEW (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_edit_publication_metadata_tree_view"));
  ctx->keywords_treeview = GTK_TREE_VIEW (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_edit_publication_keyword_list_tree_view"));
  ctx->edit_publication_window =
    GTK_WINDOW (gtk_builder_get_object (ctx->builder,
                                        "GNUNET_GTK_edit_publication_window"));
  ctx->keywords_liststore = GTK_LIST_STORE (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_publication_keywords_liststore"));
  ctx->keyword_entry = GTK_ENTRY (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_edit_publication_keyword_entry"));
  ctx->confirm_button = GTK_WIDGET (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_edit_publication_confirm_button"));
  ctx->preview_image = GTK_IMAGE (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_edit_publication_metadata_preview_image"));
  ctx->meta_liststore = GTK_LIST_STORE (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_publication_metadata_liststore"));
  ctx->root_entry = GTK_ENTRY (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_edit_publication_root_entry"));
  ctx->expiration_year_spin = GTK_SPIN_BUTTON (gtk_builder_get_object (
    ctx->builder,
    "GNUNET_GTK_edit_publication_expiration_year_spin_button"));
  ctx->priority_spin = GTK_SPIN_BUTTON (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_edit_publication_priority_spin_button"));
  ctx->replication_spin = GTK_SPIN_BUTTON (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_edit_publication_replication_spin_button"));
  ctx->index_checkbutton = GTK_TOGGLE_BUTTON (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_edit_publication_index_checkbutton"));
  ctx->anonymity_combo = GTK_COMBO_BOX (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_edit_publication_anonymity_combobox"));

  /* Basic initialization of widgets models and visibility */
  gtk_combo_box_set_model (ctx->anonymity_combo,
                           GNUNET_FS_GTK_get_anonymity_level_list_store ());
  GNUNET_GTK_setup_expiration_year_adjustment (ctx->builder);

  pubtypes_combo = GTK_COMBO_BOX (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_edit_publication_type_combo"));
  if (gtk_combo_box_get_active_iter (pubtypes_combo, &iter))
  {
    gtk_tree_model_get (GTK_TREE_MODEL (ctx->pubtypes_liststore),
                        &iter,
                        PUBLISH_TYPES_MC_TYPE,
                        &code,
                        -1);
    change_metatypes (ctx, 0);
  }
  else
    gtk_combo_box_set_active (pubtypes_combo, 0);

  /* indexing does not apply to directories */
  gtk_widget_set_visible (GTK_WIDGET (ctx->index_checkbutton),
                          ctx->is_directory != GNUNET_YES);
  index_label = GTK_LABEL (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_edit_publication_index_label"));
  gtk_widget_set_visible (GTK_WIDGET (index_label),
                          ctx->is_directory != GNUNET_YES);

  /* show root label only for namespaces */
  gtk_widget_set_visible (GTK_WIDGET (ctx->root_entry),
                          is_namespace_edit ? TRUE : FALSE);
  root_label = GTK_LABEL (
    gtk_builder_get_object (ctx->builder,
                            "GNUNET_GTK_edit_publication_root_label"));
  gtk_widget_set_visible (GTK_WIDGET (root_label),
                          is_namespace_edit ? TRUE : FALSE);

  /* import meta data and options */
  GNUNET_FS_file_information_inspect (fip, &file_information_import, ctx);


  /* Finally, display window */
  update_confirm_sensitivity (ctx);
  gtk_window_set_transient_for (ctx->edit_publication_window, parent);
  gtk_window_present (ctx->edit_publication_window);
}


/* end of gnunet-fs-gtk-edit_publish_dialog.c */
