/* cmd-grab.c
 *
 ****************************************************************
 * Copyright (C) 2003 Tom Lord, Mark Thomas
 *
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */


#include "config-options.h"
#include "hackerlab/bugs/panic.h"
#include "hackerlab/char/str.h"
#include "hackerlab/cmd/main.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/fs/cwd.h"
#include "hackerlab/vu/safe.h"
#include "tla/libarch/archive.h"
#include "tla/libarch/namespace.h"
#include "tla/libarch/build-revision.h"
#include "tla/libarch/pfs-dav.h"
#include "tla/libfsutils/file-contents.h"
#include "tla/libfsutils/rmrf.h"
#include "tla/libfsutils/tmp-files.h"
#include "tla/libarch/archives.h"
#include "tla/libarch/cmd-grab.h"
#include "tla/libarch/configs.h"
#include "tla/libarch/cmd.h"
#include "tla/libarch/cmd-build-config.h"
#include "tla/libarch/project-tree.h"



/* __STDC__ prototypes for static functions */
static void arch_grab (int chatter_fd, t_uchar *location);
t_uchar * find_latest_revision (t_uchar *archive_name, t_uchar *revision);



static t_uchar * usage = "[options] location";
static t_uchar * version_string = (cfg__std__package " from regexps.com\n"
                                   "\n"
                                   "Copyright 2003 Tom Lord\n"
                                   "\n"
                                   "This is free software; see the source for copying conditions.\n"
                                   "There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\n"
                                   "PARTICULAR PURPOSE.\n"
                                   "\n"
                                   "Report bugs to " cfg__tla_bug_mail ".\n"
                                   "\n"
                                   cfg__std__release_id_string
                                   "\n");

#define OPTS(OP) \
  OP (opt_help_msg, "h", "help", 0, \
      "Display a help message and exit.") \
  OP (opt_long_help, "H", 0, 0, \
      "Display a verbose help message and exit.") \
  OP (opt_version, "V", "version", 0, \
      "Display a release identifier string\n" \
      "and exit.")

t_uchar arch_cmd_grab_help[] = ("grab a published revision\n"
                                "Grabs a published revision from LOCATION.\n");

enum options
{
  OPTS (OPT_ENUM)
};

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



int
arch_cmd_grab (t_uchar * program_name, int argc, char * argv[])
{
  int o;
  struct opt_parsed * option;
  t_uchar * errname;
  t_uchar * location;

  errname = "grab";

  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, version_string, arch_cmd_grab_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;

        }
    }

  if (argc != 2)
    goto usage_error;

  location = str_save (0, argv[1]);

  arch_grab (1, location);

  lim_free (0, location);

  return 0;
}



void
arch_grab (int chatter_fd, t_uchar * location)
{
  t_uchar * publication = NULL;
  t_uchar * archive_name = NULL;
  t_uchar * archive_location = NULL;
  t_uchar * target_revision = NULL;
  t_uchar * target_directory = NULL;
  t_uchar * target_config = NULL;
  t_uchar * current_directory = NULL;
  t_uchar * target_full_directory = NULL;
  t_uchar * tmp_tail;
  t_uchar * tmp_directory = NULL;
  t_uchar * line = NULL;
  t_uchar * colon = NULL;
  t_uchar * eol = NULL;
  t_uchar * name = NULL;
  t_uchar * value = NULL;
  t_uchar * final_revision;
  t_uchar * uri = NULL;
  t_uchar * path = NULL;
  struct arch_pfs_session * session;


  if (str_chr_index (location, ':'))
    {
      /************************************************************
       * grab from http location
       */
      
      path = location + 7;
      path = str_chr_index(path, '/');
      if (path)
        {
          uri = str_save_n (0, location, path - location);
          path = str_save (0, path);
        }
      else
        {
          path = str_save (0, "/");
          uri = str_save (0, location);
        }
      
      session = arch_pfs_connect ( uri);
      publication = arch_pfs_file_contents(session, path, 0);

    }
  else
    {
      /************************************************************
       * try local file system
       */
      publication = file_contents (location);
    }

  if (publication == NULL)
    {
      safe_printfmt (2, "could not obtain publication data from %s.\n", location);
      return;
    }

  /****************************************************************
   * parse the publication
   */
  line = publication;
  while (*line)
    {
      while (*line == '\n')
        ++line;

      /************************************************************
       * split the line up
       */
      colon = str_chr_index (line, ':');
      eol = str_chr_index (line, '\n');

      if (!eol)
        eol = line + str_length (line);

      if (!colon || colon > eol)
        {
          line = eol;
          continue;
        }

      name = str_save_trimming_n (0, line, colon - line);
      value = str_save_trimming_n (0, colon + 1, eol - colon - 1);

      /************************************************************
       * save any useful values
       */
      if (str_cmp (name, "Archive-Name") == 0 && !archive_name)
        archive_name = value;
      else if (str_cmp (name, "Archive-Location") == 0 && !archive_location)
        archive_location = value;
      else if (str_cmp (name, "Target-Revision") == 0 && !target_revision)
        target_revision = value;
      else if (str_cmp (name, "Target-Directory") == 0 && !target_directory)
        target_directory = value;
      else if (str_cmp (name, "Target-Config") == 0 && !target_config)
        target_config = value;
      else
        lim_free (0, value);

      lim_free (0, name);
      line = eol;
    }

  lim_free (0, publication);


  if (!archive_name || !archive_location || !target_revision)
    {
      safe_printfmt (2, "Invalid publication file at %s.\n", location);
      return;
    }
  else
    {
      struct arch_archive *archive;

      if (target_directory)
        {
          /********************************************************
           * perform sanity check on target dir
           * we don't want to allow any old target dir (to avoid
           * "/etc" or "../../../etc" exploits), so we only take
           * the tail of the specified target directory.
           */
          t_uchar * target_directory_file = file_name_from_directory (0, target_directory);
          t_uchar * target_directory_tail = file_name_tail (0, target_directory_file);
          lim_free (0, target_directory);
          lim_free (0, target_directory_file);
          if (str_length (target_directory_tail) == 0)
            {
              lim_free (0, target_directory_tail);
              target_directory_tail = NULL;
            }
          target_directory = target_directory_tail;
        }

      if (target_directory == NULL)
        {
          target_directory = str_save (0, target_revision);
        }

      current_directory = safe_current_working_directory ();

      tmp_tail = str_alloc_cat_many (0, ",,grab.", target_directory, str_end);
      target_full_directory = file_name_in_vicinity (0, current_directory, target_directory);
      tmp_directory = tmp_file_name (current_directory, tmp_tail);
      rmrf_file (tmp_directory);
      safe_mkdir (tmp_directory, 0777);


      safe_printfmt(1, "Grabbing: %s/%s\n", archive_name, archive_location);
      safe_printfmt(1, "Source: %s, Dest: %s\n", archive_location, target_directory);
      if (target_config)
        safe_printfmt(1, "Config: %s\n", target_config);
      
      
      /********************
       * Time to grab the archive
       */
      arch_set_archive_location(archive_name, archive_location, 0, ARCH_REG_FAIL_QUIET);

      final_revision = find_latest_revision (archive_name, target_revision);
      archive = arch_archive_connect (archive_name, 0);

      arch_build_revision (chatter_fd, tmp_directory, archive, archive_name, final_revision, current_directory);

      arch_archive_close (archive);

      safe_rename (tmp_directory, target_full_directory);

      /****************
       * Time to config, if we should
       */

      if (target_config)
        {
          t_uchar *tree_root;
          t_uchar *tree_version;
          t_uchar *def_archive = NULL;
          struct arch_build_config_params config_params = {0, };


          if (!arch_valid_config_name (target_config))
            {
              safe_printfmt(2, "grab: given invalid config name '%s'\n", target_config);
              exit(1);
            }

          tree_root = arch_tree_root (0, target_full_directory, 0);
          if (! tree_root)
              panic("grab: Not in a valid tree root");

          tree_version = arch_tree_version (tree_root);

          if (tree_version)
            {
              def_archive = arch_parse_package_name (arch_ret_archive, 0, tree_version);
              lim_free(0, tree_version);
            }

          arch_build_config (tree_root, target_config, &config_params , def_archive);
          lim_free (0, tree_root);
          lim_free (0, def_archive);

        }

    }

  lim_free (0, archive_name);
  lim_free (0, archive_location);
  lim_free (0, target_revision);
  lim_free (0, target_directory);
  lim_free (0, current_directory);
  lim_free (0, target_full_directory);
  lim_free (0, tmp_directory);
  lim_free (0, tmp_tail);
}



t_uchar * find_latest_revision (t_uchar *archive_name, t_uchar *revision)
{
  t_uchar *oldrevision;
  t_uchar *workrevision;

  struct arch_archive *archive;

  if (! arch_valid_package_name (revision, arch_no_archive, arch_req_package, 1))
    {
      panic("Invalid Package");
    }

  archive = arch_archive_connect(archive_name, 0);


  workrevision = str_save(0, revision);

  if (! arch_valid_package_name (workrevision, arch_no_archive, arch_req_version, 1))
    {
      rel_table versions = rel_table_nil;
      
      versions = arch_archive_versions (archive, revision);
      if (!rel_n_records (versions))
        {
          panic("No versions for that package exist in the archive");
        }
      arch_sort_table_by_name_field (1, versions, 0);

      oldrevision = workrevision;
      workrevision = str_save(0, rel_peek_str (versions, 0, 0));
      lim_free(0, oldrevision);
      rel_free_table (versions);
    }

  if (! arch_valid_package_name (workrevision, arch_no_archive, arch_req_patch_level, 1))
    {
      oldrevision = workrevision;
      workrevision = str_alloc_cat_many (0, workrevision, "--",
                                         arch_archive_latest_revision(archive, workrevision, 0),
                                         str_end);
      lim_free(0, oldrevision);
    }

  return workrevision;
}


/* tag: Mark Thomas Tue Jul 29 22:32:52 BST 2003 (cmd-grab.c)
 */
