/*
     This file is part of GNUnet.
     Copyright (C) 2009, 2010, 2012, 2013 GNUnet e.V.

     GNUnet is free software: you can redistribute it and/or modify it
     under the terms of the GNU Affero General Public License as published
     by the Free Software Foundation, either version 3 of the License,
     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
     Affero General Public License for more details.

     You should have received a copy of the GNU Affero General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.

     SPDX-License-Identifier: AGPL3.0-or-later
 */

/**
 * @file fs/fs_publish_ksk.c
 * @brief publish a URI under a keyword in GNUnet
 * @see https://gnunet.org/encoding and #2564
 * @author Krista Bennett
 * @author Christian Grothoff
 */

#include "platform.h"
#include "gnunet_constants.h"
#include "gnunet_signatures.h"
#include "gnunet_util_lib.h"

#include "gnunet_fs_service.h"
#include "fs_api.h"
#include "fs_tree.h"
#include "fs_publish_ublock.h"

/**
 * Context for the KSK publication.
 */
struct GNUNET_FS_PublishKskContext
{
  /**
   * Keywords to use.
   */
  struct GNUNET_FS_Uri *ksk_uri;

  /**
   * URI to publish.
   */
  struct GNUNET_FS_Uri *uri;

  /**
   * Metadata to use.
   */
  struct GNUNET_FS_MetaData *meta;

  /**
   * Global FS context.
   */
  struct GNUNET_FS_Handle *h;

  /**
   * UBlock publishing operation that is active.
   */
  struct GNUNET_FS_PublishUblockContext *uc;

  /**
   * Handle to the datastore, NULL if we are just simulating.
   */
  struct GNUNET_DATASTORE_Handle *dsh;

  /**
   * Current task.
   */
  struct GNUNET_SCHEDULER_Task *ksk_task;

  /**
   * Function to call once we're done.
   */
  GNUNET_FS_PublishContinuation cont;

  /**
   * Closure for cont.
   */
  void *cont_cls;

  /**
   * When should the KBlocks expire?
   */
  struct GNUNET_FS_BlockOptions bo;

  /**
   * Options to use.
   */
  enum GNUNET_FS_PublishOptions options;

  /**
   * Keyword that we are currently processing.
   */
  unsigned int i;
};


/**
 * Continuation of #GNUNET_FS_publish_ksk() that performs
 * the actual publishing operation (iterating over all
 * of the keywords).
 *
 * @param cls closure of type `struct PublishKskContext *`
 */
static void
publish_ksk_cont (void *cls);


/**
 * Function called by the datastore API with
 * the result from the PUT request.
 *
 * @param cls closure of type `struct GNUNET_FS_PublishKskContext *`
 * @param msg error message (or NULL)
 */
static void
kb_put_cont (void *cls,
             const char *msg)
{
  struct GNUNET_FS_PublishKskContext *pkc = cls;

  pkc->uc = NULL;
  if (NULL != msg)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "KBlock PUT operation failed: %s\n", msg);
    pkc->cont (pkc->cont_cls, NULL, msg);
    GNUNET_FS_publish_ksk_cancel (pkc);
    return;
  }
  pkc->ksk_task = GNUNET_SCHEDULER_add_now (&publish_ksk_cont, pkc);
}


static void
publish_ksk_cont (void *cls)
{
  struct GNUNET_FS_PublishKskContext *pkc = cls;
  const char *keyword;

  pkc->ksk_task = NULL;
  if ((pkc->i == pkc->ksk_uri->data.ksk.keywordCount) ||
      (NULL == pkc->dsh))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "KSK PUT operation complete\n");
    pkc->cont (pkc->cont_cls, pkc->ksk_uri,
               NULL);
    GNUNET_FS_publish_ksk_cancel (pkc);
    return;
  }
  keyword = pkc->ksk_uri->data.ksk.keywords[pkc->i++];
  pkc->uc = GNUNET_FS_publish_ublock_ (pkc->h,
                                       pkc->dsh,
                                       keyword + 1 /* skip '+' */,
                                       NULL,
                                       GNUNET_CRYPTO_ecdsa_key_get_anonymous (),
                                       pkc->meta,
                                       pkc->uri,
                                       &pkc->bo,
                                       pkc->options,
                                       &kb_put_cont, pkc);
}


/**
 * Publish a CHK under various keywords on GNUnet.
 *
 * @param h handle to the file sharing subsystem
 * @param ksk_uri keywords to use
 * @param meta metadata to use
 * @param uri URI to refer to in the KBlock
 * @param bo per-block options
 * @param options publication options
 * @param cont continuation
 * @param cont_cls closure for cont
 * @return NULL on error ('cont' will still be called)
 */
struct GNUNET_FS_PublishKskContext *
GNUNET_FS_publish_ksk (struct GNUNET_FS_Handle *h,
                       const struct GNUNET_FS_Uri *ksk_uri,
                       const struct GNUNET_FS_MetaData *meta,
                       const struct GNUNET_FS_Uri *uri,
                       const struct GNUNET_FS_BlockOptions *bo,
                       enum GNUNET_FS_PublishOptions options,
                       GNUNET_FS_PublishContinuation cont, void *cont_cls)
{
  struct GNUNET_FS_PublishKskContext *pkc;

  GNUNET_assert (NULL != uri);
  pkc = GNUNET_new (struct GNUNET_FS_PublishKskContext);
  pkc->h = h;
  pkc->bo = *bo;
  pkc->options = options;
  pkc->cont = cont;
  pkc->cont_cls = cont_cls;
  pkc->meta = GNUNET_FS_meta_data_duplicate (meta);
  if (0 == (options & GNUNET_FS_PUBLISH_OPTION_SIMULATE_ONLY))
  {
    pkc->dsh = GNUNET_DATASTORE_connect (h->cfg);
    if (NULL == pkc->dsh)
    {
      cont (cont_cls,
            NULL,
            _ ("Could not connect to datastore."));
      GNUNET_free (pkc);
      return NULL;
    }
  }
  pkc->uri = GNUNET_FS_uri_dup (uri);
  pkc->ksk_uri = GNUNET_FS_uri_dup (ksk_uri);
  pkc->ksk_task = GNUNET_SCHEDULER_add_now (&publish_ksk_cont, pkc);
  return pkc;
}


/**
 * Abort the KSK publishing operation.
 *
 * @param pkc context of the operation to abort.
 */
void
GNUNET_FS_publish_ksk_cancel (struct GNUNET_FS_PublishKskContext *pkc)
{
  if (NULL != pkc->ksk_task)
  {
    GNUNET_SCHEDULER_cancel (pkc->ksk_task);
    pkc->ksk_task = NULL;
  }
  if (NULL != pkc->uc)
  {
    GNUNET_FS_publish_ublock_cancel_ (pkc->uc);
    pkc->uc = NULL;
  }
  if (NULL != pkc->dsh)
  {
    GNUNET_DATASTORE_disconnect (pkc->dsh, GNUNET_NO);
    pkc->dsh = NULL;
  }
  GNUNET_FS_meta_data_destroy (pkc->meta);
  GNUNET_FS_uri_destroy (pkc->ksk_uri);
  GNUNET_FS_uri_destroy (pkc->uri);
  GNUNET_free (pkc);
}


/* end of fs_publish_ksk.c */
