/* change-archive.c
 *
 * vim:smartindent ts=8:sts=2:sta:et:ai:shiftwidth=2
 ****************************************************************
 * Copyright (C) 2005 Canonical Limited
 *	Authors: Robert Collins <robert.collins@canonical.com>
 *
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */


#include "config-options.h"
#include "po/gettext.h"
#include "hackerlab/bugs/exception.h"
#include "hackerlab/cmd/main.h"
#include "hackerlab/fs/file-names.h"
#include "libarch/my.h"
#include "libarch/archive.h"
#include "libarch/archives.h"
#include "libarch/archive-mirror.h"
#include "libarch/cached-archive.h"
#include "libarch/pfs.h"
#include "libfsutils/tmp-files.h"
#include "commands/version.h"
#include "commands/change-archive.h"



static t_uchar * usage = N_("[options] archivename|URL");

#define OPTS(OP) \
  OP (opt_help_msg, "h", "help", 0, \
      N_("Display a help message and exit.")) \
  OP (opt_long_help, "H", 0, 0, \
      N_("Display a verbose help message and exit.")) \
  OP (opt_version, "V", "version", 0, \
      N_("Display a release identifier string\n" \
      "and exit.")) \
  OP (opt_add_signatures, 0, "add-signatures", 0, \
      N_("turn this archve into a signed archive")) \
  OP (opt_remove_signatures, 0, "remove-signatures", 0, \
      N_("remove digital signatures from this archive"))

t_uchar arch_change_archive_help[] = N_("Alter properties of an archive. For instance, add signatures to an "
                                   "unsigned archive.\n"
                                   "\n"
                                   "The following operations involve making a temporary mirror of the archive:\n"
                                   "- adding signatures\n"
                                   "- removing signatures\n"
                                   "- changing the archive format\n"
                                   "They may take some time as they involve a full mirror of the archive and all\n"
                                   "its contents\n"
                                   "The following operations will automatically propogate their changes to all\n"
                                   "writeable mirrors listed in ~/.arch-params/archives/NAME, and must have the\n"
                                   "master archive available to operate at all:\n"
                                   "- adding signatures\n"
                                   "- removing signatures\n");

enum options
{
  OPTS (OPT_ENUM)
};

static struct opt_desc opts[] =
{
  OPTS (OPT_DESC)
    {-1, 0, 0, 0, 0}
};

typedef struct change_options_
{
    /* should be url ?*/
    t_uchar * archivename;
    int add_signatures;
    int remove_signatures;
    struct arch_archive *from;
    struct arch_archive *to;
} change_options_t;


static void
simple_mirror (struct arch_archive *from, struct arch_archive *to)
{
    struct arch_archive_mirror_options mirror_opts;
    mem_set0 ((t_uchar *)&mirror_opts, sizeof (mirror_opts));
    /* TODO factor out a init method */
    mirror_opts.print_summary = 0;
    mirror_opts.cachedrevs = arch_mirror_all_cachedrevs;
    arch_archive_mirror (-1, from, to, NULL, &mirror_opts);
}

static int
rename_helper (struct arch_pfs_session *pfs, t_uchar **errstr, t_uchar * from, t_uchar *to)
{
    t_uchar *from_relpath = file_name_tail (0, from);
    t_uchar *to_relpath = file_name_tail (0, to);
    int result = arch_pfs_rename (pfs, errstr, from_relpath, to_relpath, 1);
    if (result)
        safe_printfmt (2, "failed to rename from %s to %s\n", from_relpath, to_relpath);
    lim_free (0, from_relpath);
    lim_free (0, to_relpath);
    return result;
}

static int
change_archive (change_options_t *changes)
{
    int must_mirror = 0;
    int result = 0;
    t_uchar * archive_parent;
    t_uchar * archive_dirpath;
    t_uchar * temp_archivepath = NULL;
    t_uchar * temp_oldarchivepath;
    t_uchar * original_location;
    t_uchar * tempname = NULL;
    struct arch_pfs_session * pfs = NULL;
    t_uchar * errstr = NULL;
    int target_signed;
    int debug = 1;
    inifile_t inifile;
    if (changes->add_signatures || changes->remove_signatures)
        must_mirror = 1;

    if (!must_mirror)
      {
        /* no ops yet */
        return 0;
      }

    /* connect */
    changes->from = arch_archive_connect_commitable_branch (changes->archivename, NULL);
    if (!changes->from)
        goto error_exit;
    /* determine target options */
    if (changes->add_signatures)
        target_signed = 1;
    else if (changes->remove_signatures)
        target_signed = 0;
    else 
        target_signed = changes->from->signed_archive;
    if (changes->add_signatures || changes->remove_signatures)
      {
        /* must have a master  */
        if (changes->from->mirror_of)
          {
            safe_printfmt (2, "Cannot add or remove signatures from mirrors, only from the master archive\n");
            goto error_exit;
          }
      }
/*  get location */
    archive_parent = file_name_directory_file (0, changes->from->location);
    archive_parent = str_replace (archive_parent, arch_uncached_location (archive_parent));
    archive_parent = str_replace (archive_parent, str_save (0, archive_parent + 9));
    archive_dirpath = file_name_directory_file (0, changes->from->location);
    archive_dirpath = str_replace (archive_dirpath, arch_uncached_location (archive_dirpath));
/*  make archive at location's basepath */
    if (debug)
        safe_printfmt (2, "Creating temporary archive\n");
    temp_archivepath = talloc_tmp_file_name (talloc_context, archive_dirpath, "change-archive");
    tempname = str_alloc_cat (0, changes->from->official_name, "CHANGE");
    arch_make_archive (tempname, temp_archivepath, changes->from->official_name, changes->from->http_blows, target_signed, arch_archive_tla == changes->from->type, 0);
    changes->to = arch_archive_connect_location_ext (tempname, temp_archivepath, changes->from->official_name, 1, 1);
    if (!changes->to)
        goto error_exit;
    /* FIXME get rid of the name bogon */
    if (!changes->from->in_registry)
      changes->to->registered_name = str_replace (changes->to->registered_name, str_save (0, tempname));
/*  add the mirror flag on the old one to prevent commits */
    if (debug)
        safe_printfmt (2, "Converting existing archive to readonly status.\n");
    arch_archive_set_mirror (&errstr, changes->from, 1);
/*  mirror */
    if (debug)
        safe_printfmt (2, "Mirroring archive to temporary archive.\n");
    simple_mirror (changes->from, changes->to);
/* close handles. */
    temp_oldarchivepath = file_name_directory_file (0, changes->from->location);
    temp_oldarchivepath = str_replace (temp_oldarchivepath, arch_uncached_location (archive_dirpath));
    temp_oldarchivepath = talloc_tmp_file_name (temp_archivepath, temp_oldarchivepath, "change-archive-original");
    original_location = str_save (0, changes->from->location);
    original_location = str_replace (original_location, arch_uncached_location (original_location));
    arch_archive_close (changes->from); changes->from = NULL;
    arch_archive_close (changes->to); changes->to = NULL;
/*  rename old */
    if (debug)
        safe_printfmt (2, "Renaming original archive out of the way.\n");
    pfs = arch_pfs_connect (archive_parent, 1);
    if (!pfs)
        goto error_exit;
    if (rename_helper (pfs, &errstr, original_location, temp_oldarchivepath))
        goto error_exit;
/*  rename new */
    if (debug)
        safe_printfmt (2, "Renaming new archive into position.\n");
    if (rename_helper (pfs, &errstr, temp_archivepath, original_location))
        goto error_exit;
/*  reopen both again */
    if (debug)
        safe_printfmt (2, "Reconnecting to original archive.\n");
    changes->from = arch_archive_connect_location (temp_oldarchivepath, 1);
    if (!changes->from)
        goto error_exit;
    if (debug)
        safe_printfmt (2, "Reconnecting to new archive.\n");
    changes->to = arch_archive_connect_location_ext (tempname, original_location, changes->from->official_name, 1, 1);
    if (!changes->to)
        goto error_exit;
/*  mirror again as a just-in-case */
    if (debug)
        safe_printfmt (2, "Mirroring new changesets from original archive to new archive.\n");
    simple_mirror (changes->from, changes->to);
/*  remove the mirror flag on the new one */
    if (debug)
        safe_printfmt (2, "Converting new archive into a master archive.\n");
    arch_archive_set_mirror (&errstr, changes->to, 0);
/*  update the archive registry */
    if (debug)
        safe_printfmt (2, "Update the archive registry with new archives status.\n");
    arch_archives_get_archive_ini_no_default (changes->from->official_name, &inifile);
    inifile_add_key (&inifile, "", "when_unsigned", target_signed ? "error" : "ignore", "");
    arch_archives_save_archive_ini_no_default (changes->from->official_name, &inifile);
/*  remove the old archive*/
    if (debug)
        safe_printfmt (2, "Removing the old archive.\n");
      {
        t_uchar * relpath = file_name_tail (0, temp_oldarchivepath);
        arch_pfs_rmrf_file (pfs, relpath);
        lim_free (0, relpath);
      }
    if (debug)
        safe_printfmt (2, "Operation complete.\n");
    goto ok_exit;

error_exit:
    result = -1;
    if (errstr)
        safe_printfmt (2, _("Error during archive change: '%s'\n"), errstr);
    else
        safe_printfmt (2, _("Error during archive change: 'unknown'\n"));
ok_exit:
    lim_free (0, tempname);
    arch_archive_close (changes->from); changes->from = NULL;
    arch_archive_close (changes->to); changes->to = NULL;
    if (pfs)
        arch_pfs_disconnect (&pfs);
    talloc_free (temp_archivepath);
    return result;
}

int
arch_change_archive (t_uchar * program_name, int argc, char * argv[])
{
  int o;
  struct opt_parsed * option;
  change_options_t changes = {NULL, 0, 0, NULL, NULL};
  int result;

  safe_buffer_fd (1, 0, O_WRONLY, 0);

  option = 0;

  while (1)
    {
      o = opt_standard (lim_use_must_malloc, &option, opts, &argc, argv, program_name, usage, libarch_version_string, arch_change_archive_help, opt_help_msg, opt_long_help, opt_version);
      if (o == opt_none)
        break;
      switch (o)
        {
        default:
          safe_printfmt (2, "unhandled option `%s'\n", option->opt_string);
          panic ("internal error parsing arguments");

        usage_error:
          opt_usage (2, argv[0], program_name, usage, 1);
          exit (1);

          /* bogus_arg: */
          safe_printfmt (2, "ill-formed argument for `%s' (`%s')\n", option->opt_string, option->arg_string);
          goto usage_error;

	case opt_add_signatures:
	  {
	    changes.add_signatures = 1;
            changes.remove_signatures = 0;
	    break;
	  }
        
	case opt_remove_signatures:
	  {
	    changes.add_signatures = 0;
            changes.remove_signatures = 1;
	    break;
	  }
        }
    }

  if (argc > 2)
    goto usage_error;

  changes.archivename = str_save (0, argv[1]);

  result = change_archive (&changes);

  lim_free (0, changes.archivename);
  return result;
}


syntax highlighted by Code2HTML, v. 0.9.1