/* archive-mirror.c
 *
 * vim:smartindent ts=8:sts=2:sta:et:ai:shiftwidth=2
 ****************************************************************
 * Copyright (C) 2003 Tom Lord
 *
 * 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 "libawk/trim.h"
#include "libarch/namespace.h"
#include "libarch/archive.h"
#include "libarch/archives.h"
#include "libarch/archive-mirror.h"
#include "libarch/cached-archive.h"
#include "libarch/debug.h"
#include "libarch/my.h"
#include "libarch/project-tree.h"
#include "commands/cmd.h"
#include "commands/archive-mirror.h"
#include "commands/version.h"



static t_uchar * usage = N_("[options] [from [to] [limit]]");

#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_no_cachedrevs, 0, "no-cached", 0, \
      N_("don't copy cached revisions")) \
  OP (opt_summary, "s", "summary", 0, \
      N_("print the summary of each patch")) \
  OP (opt_id_cachedrevs, 0, "cached-tags", 0, \
      N_("copy only cachedrevs for tags to other archives")) \
  OP (opt_all_mirrors, "a", "all-mirrors", 0, \
      N_("mirror to all known mirrors"))


t_uchar arch_cmd_archive_mirror_help[] = N_("update an archive mirror\n"
                                          "If no arguments are given, update either the archive for the project tree\n"
					  "you are in, or your `my-default-archive'-MIRROR\n"
                                          "archive with the contents of `my-default-archive'.\n"
                                          "\n"
                                          "If a [FROM] archive is given, update the [FROM]-MIRROR archive with\n"
                                          "the contents of the [FROM] archive\n"
                                          "\n"
                                          "If both [FROM] and [TO] archives are specified, update [TO] with\n"
                                          "the contents of [FROM]\n"
                                          "\n"
                                          "If LIMIT is provided, it should be a category, branch,\n"
                                          "version, or revision name.   Only the indicated part\n"
                                          "of FROM will be copied to TO. If LIMIT is a revision,\n"
					  "then cached revisions will be copied and deleted to TO.\n"
                                          "\n"
                                          "(see \"baz make-archive -H\".).\n");

enum options
{
  OPTS (OPT_ENUM)
};

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

typedef struct archive_mirror_state_
{
    struct arch_archive * from;
    ar_arch_archive sources;
    ar_arch_archive destinations;
}
archive_mirror_state_t;

static int archive_mirror_state_finalise (void *data)
{
    return 0;
}

static archive_mirror_state_t *
archive_mirror_state_new (void)
{
  archive_mirror_state_t * result = talloc (NULL, archive_mirror_state_t);
  talloc_set_destructor (result, archive_mirror_state_finalise);
  result->from = NULL;
  result->sources = NULL;
  result->destinations = NULL;
  return result;
}

static void
connect_if_url (archive_mirror_state_t * state, struct arch_archive **archp, t_uchar **namep)
{
    t_uchar *from_name = *namep;
    t_uchar * temp_name = NULL;
    /* if its a url, we just take it */
    if (!arch_valid_archive_name (from_name) &&
	!arch_valid_package_name (from_name, arch_req_archive, arch_req_category, 1))
      {
	  t_uchar * uncached_location = arch_uncached_location (from_name);
	  *archp = arch_archive_connect_branch (uncached_location, &temp_name);
	  *archp = talloc_steal (state, arch_archive_connect_branch (uncached_location, &temp_name));
	  lim_free (0, uncached_location);
	  if (!*archp)
	    {
	      if (arch_valid_package_name (from_name, arch_no_archive, arch_req_category, 1))
		  return;
	      safe_printfmt (2, "could not connect to archive '%s'\n", from_name);
	      exit (2);
	    }
	  *namep = str_replace (*namep, arch_parse_name (arch_ret_archive, NULL, temp_name));
	  lim_free (0, temp_name);
      }
}
static int
url_param (archive_mirror_state_t * state, struct arch_archive **arch, t_uchar const * const param1)
{
    t_uchar * temp_name = str_save (0, param1);
    *arch = NULL;
    connect_if_url (state, arch, &temp_name);
    lim_free (0, temp_name);
    return *arch != NULL;
}

/**
 * \brief find a target archive, that is different from location
 * \param state the state object
 * \param arch_pointer output pointer
 * \param wanted_name the name we're looking for
 * \param location skip this location
 */
static void
find_target_archive (archive_mirror_state_t * state, t_uchar const * const wanted_name, t_uchar const * const location, int try_mirror_aliases, int get_all)
{
    struct arch_archive *temp = NULL;
    int index;
    ar_archive_location locations = arch_archive_locations (wanted_name);
    /* FIXME: 20050328 order by priority ? */
    ar_for_each (locations, index)
      {
        t_uchar * uncached;
        if (!arch_archive_cmp_location (locations[index]->url, location))
            continue;
        if (locations[index]->master == 2 && !state->from)
            continue;
        if (locations[index]->readonly == 2 && !state->from)
            continue;
        uncached = arch_uncached_location (locations[index]->url);
        temp = talloc_steal (state, arch_archive_connect_location (uncached, 1));
        lim_free (0, uncached);
        if (temp && str_cmp (wanted_name, temp->official_name))
          {
            /* FIXME: remove this from the registered list RBC 20050327 */
            arch_archive_close (temp);
            temp = NULL;
          }
        if (temp && state->from && (!temp->mirror_of || locations[index]->readonly == 2))
          {
            /* we connected to the master, switch it for the source */
            /* FIXME: mark this as master in the registry RBC 20050327 */
            struct arch_archive * swap = state->from;
            state->from = temp;
            temp = swap;
            debug (dbg_ui, 2, "swapped source and target archives\n");
          }
        if (!temp->mirror_of)
          continue; /* The master archive shouldn't be mirrored to ... */
        if (temp && str_cmp(temp->mirror_of, wanted_name))
            /* should also probe for readonly status */
          {
            /* RBC 20050327 This archive is corrupt (name and mirror_of mismatch), report or act on it somehow */
            debug (dbg_ui, 1, "corrupt archive found (mname and mirror_of mismatch). %s name %s mirror_of %s\n", temp->location, temp->registered_name, temp->mirror_of);
            arch_archive_close (temp);
            temp = NULL;
          }
        if (temp)
          {
            ar_insert_arch_archive (&state->destinations, ar_size_arch_archive (state->destinations), temp);
            if (!get_all)
                return;
          }
      }
    ar_free_archive_location (&locations);
    if (get_all && ar_size_arch_archive (state->destinations))
        return;
    /* LEGACY FIXME-REMOVENAME support */
    if (try_mirror_aliases)
      {
        /* from is connected, and we've been asked to look for -MIRROR and -SOURCE aliases */
        /* these go FOO->FOO-MIRROR, or FOO-SOURCE->FOO.
         * if from is a master, we only need too look for foo-MIRROR.
         * we look for a -SOURCE first.
         */
        if (state->from->mirror_of)
            /* from isn't a master, there may be a better source */
          {
            t_uchar *source_name = arch_mirrored_from(wanted_name);
            if (source_name)
              {
                struct arch_archive * swap;
                t_uchar *source_loc = arch_archive_location (source_name, 0);
                debug (dbg_ui, 2, "trying source archive %s\n", source_loc);
                source_loc = str_replace (source_loc, arch_uncached_location (source_loc));
                swap = arch_archive_connect_location (source_loc, 1);
                if (!swap)
                  {
                    safe_printfmt (2, "Cannot connect to source archive %s\n", source_name);
                    talloc_free (state);
                    exit (2);
                  }
                temp = state->from;
                state->from = swap;
                lim_free (0, source_loc);
                lim_free (0, source_name);
              }
          }
        if (!temp)
          {
            t_uchar * target_name = arch_mirrored_at (wanted_name);
            if (target_name)
              {
                debug (dbg_ui, 2, "trying target archive %s\n", target_name);
                /* FIXME-REMOVENAMES this goes 20050329 */
                temp = arch_archive_connect_ext (target_name, wanted_name, 1);
                if (!temp)
                  {
                    safe_printfmt (2, "Cannot connect to target archive %s\n", target_name);
                    talloc_free (state);
                    exit (2);
                  }
                lim_free (0, target_name);
              }
            else
                safe_printfmt (2, "no source or destination mirror registered for %s\n", wanted_name);
          }
      }
    if (!temp)
      {
        safe_printfmt (2, "Unable to connect to target archive %s\n", wanted_name);
        talloc_free (state);
        exit (2);
      }
    ar_insert_arch_archive (&state->destinations, ar_size_arch_archive (state->destinations), temp);
}

/** 
 * \brief find a source archive, that is different from location.
 * \param state the state object
 * \param arch_pointer the output archive
 * \param location to not match
 */
static void
find_source_archive (archive_mirror_state_t * state, t_uchar const * const wanted_name, t_uchar const * const location)
{
    int index;
    ar_archive_location locations = arch_archive_locations (wanted_name);
    /* FIXME: 20050328 order by priority ? */
    ar_for_each (locations, index)
      {
        t_uchar * uncached;
        if (!arch_archive_cmp_location (locations[index]->url, location))
            continue;
        uncached = arch_uncached_location (locations[index]->url);
        state->from = talloc_steal (state, arch_archive_connect_location (uncached, 1));
        lim_free (0, uncached);
        if (state->from && str_cmp (wanted_name, state->from->official_name))
            /* should also probe for readonly status */
          {
            arch_archive_close (state->from);
            state->from = NULL;
          }
      }
    ar_free_archive_location (&locations);
}

/**
 * \brief connect to the best source
 * \param state the state object
 * \param spec1 the source spec
 */
static void
setup_from (archive_mirror_state_t * state, t_uchar const * const spec1, t_uchar const * const location)
{
    url_param (state, &state->from, spec1);
    if (!state->from)
      {
        if (!arch_valid_archive_name (spec1))
          {
            /* invalid name, or inaccessible url */
            safe_printfmt (2, "Cannot connect to archive %s\n", spec1);
            talloc_free (state);
            exit (2);
          }
        find_source_archive (state, spec1, location);
        if (!state->from)
          {
            safe_printfmt (2, "Cannot connect to archive %s\n", spec1);
            exit (2);
          }
      }
}

/**
 * \brief figure out what archive connections we need
 *
 * \param state the mirror state to user
 * \param spec1 the first user spec to use
 * \param spec2 the second user spec, NULL if no used.
 * \param all is an all_mirrors run requested
 * \return the second spec if it wasn't used
 */
static t_uchar *
setup_connections (archive_mirror_state_t * state, t_uchar const * const spec1, t_uchar const * const spec2, int all)
{
    t_uchar * result = NULL;
    /* find the best source available */
    if (all)
      {
        setup_from (state, spec1, NULL);
        find_target_archive (state, state->from->official_name, state->from->location, 0, 1);
        return str_save (0, spec2);
      }
#if 0
    1 param url - get name, determine if url is source or dest, magic find other.
    1 param name - find best source, any other dest
    2 param url url - use as given
    2 param url name - use url as source. magic find other.
    2 param name url - use url as dest, magic find other
    2 param name name - must be
#endif
    if (!spec2)
      {
        /* name or url one param form */
        setup_from (state, spec1, NULL);
        find_target_archive (state, state->from->official_name, state->from->location, 1, 0);
      }
    else 
      {
        struct arch_archive * temp = NULL;
        url_param (state, &temp, spec2);
        if (temp)
            ar_insert_arch_archive (&state->destinations, 0, temp);
        if (ar_size_arch_archive(state->destinations))
            setup_from (state, spec1, state->destinations[0]->location);
        else
            setup_from (state, spec1, NULL);

        if (!ar_size_arch_archive(state->destinations) && !arch_valid_archive_name (spec2) && 
            !arch_valid_package_name (spec2, arch_no_archive, arch_req_category, 1))
          {
            /* param 2 was a url, is not reachable */
            safe_printfmt (2, "Cannot connect to archive %s\n", spec2);
            talloc_free (state);
            exit (2);
          }

        if (!ar_size_arch_archive(state->destinations))
          {
            if (!arch_valid_archive_name (spec2))
              {
                /* name limit */
                find_target_archive (state, state->from->official_name, state->from->location, 1, 0);
                result = str_save (0, spec2);
              }
            else
              {
                /* name name */
                find_target_archive (state, spec2, state->from->location, 0, 0);
              }
          }
      }
    if (ar_size_arch_archive(state->destinations))
      {
        if (str_cmp (state->from->official_name, state->destinations[0]->official_name))
          {
            safe_printfmt (2, "Cannot mirror between archives with different official names. Selected archives:\n%s(%s)\n%s(%s)\n", state->from->official_name, state->from->location, state->destinations[0]->official_name, state->destinations[0]->location);
            talloc_free (state);
            exit (2);
          }
      }
  return result;
}

int
arch_cmd_archive_mirror (t_uchar * program_name, int argc, char * argv[])
{
  int o;
  struct opt_parsed * option;
  struct arch_archive_mirror_options * mirror_opts = 0;
  int do_all_mirrors = 0;

  mirror_opts = lim_malloc (0, sizeof (*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;
  
  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_cmd_archive_mirror_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_no_cachedrevs:
          {
            mirror_opts->cachedrevs = arch_mirror_skip_cachedrevs;
            break;
          }

        case opt_summary:
          {
            mirror_opts->print_summary = 1;
            break;
          }

        case opt_id_cachedrevs:
          {
            mirror_opts->cachedrevs = arch_mirror_foreign_continuation_cachedrevs;
            break;
          }

	case opt_all_mirrors:
	  {
	    do_all_mirrors = -1;
	    break;
	  }
        }
    }

  if (argc > 4)
    goto usage_error;

  {
    int a;
    t_uchar * from_name = 0;
    t_uchar * ambiguous_param = 0;
    t_uchar * to_name = 0;
    t_uchar * limit_spec = 0;
    archive_mirror_state_t *state = archive_mirror_state_new ();

    a = 1;

    if (argc <= 1)
      {
	arch_project_tree_t * tree = arch_project_tree_new (talloc_context, ".");
	if (tree->archive)
	  {
	    from_name = str_save (0, tree->archive);
	  }
	else
	  {
	    from_name = arch_my_default_archive (0);
	  }
	
        if (!from_name)
          {
            safe_printfmt (2, "%s: no default archive set, and not in a baz project tree->\n", argv[0]);
            exit (1);
          }
	arch_project_tree_delete (tree);
      }
    else
      {
        from_name = str_save (0, argv[a]);
        ++a;
      }

    if (a < argc)
      {
        ambiguous_param = str_save (0, argv[2]);
        ++a;
      }
    
    if (!(limit_spec = setup_connections (state, from_name, ambiguous_param, do_all_mirrors)))
      {
	limit_spec = (argc > a ? str_save (0, argv[a]) : 0);
      }
    
    {
      int index;
      for (index = 0; index < ar_size_arch_archive (state->destinations); ++index)
	{
	  /* YEEEUCK. RBC 20030514 */
	  state->destinations[index]->client_anticipates_mirror = str_save (0, state->destinations[index]->official_name);
	}
    }
    
    if (limit_spec && !arch_valid_package_name (limit_spec, arch_no_archive, arch_req_category, 1))
      {
	safe_printfmt (2, "%s: invalid limit spec name (%s)\n",
		       argv[0], limit_spec);
	exit (2);
      }

    if (!ar_size_arch_archive (state->destinations))
      {
	safe_printfmt (2, "%s: no mirror registered for %s\n",
		       argv[0], from_name);
	exit (1);
      }

    if (do_all_mirrors)
      {
	int index = 0;
	for (index = 0; index < ar_size_arch_archive (state->destinations); ++index)
	    arch_archive_mirror (1, state->from, state->destinations[index], limit_spec, mirror_opts);
      }
    else
	arch_archive_mirror (1, state->from, state->destinations[0], limit_spec, mirror_opts);

    lim_free (0, from_name);
    lim_free (0, ambiguous_param);
    lim_free (0, to_name);
    lim_free (0, limit_spec);
    talloc_free (state);
  }

  lim_free (0, mirror_opts);

  return 0;
}



/* tag: Tom Lord Tue Jun 10 14:15:27 2003 (cmd-archive-mirror.c)
 */


syntax highlighted by Code2HTML, v. 0.9.1