/* export.c:
 *
 ****************************************************************
 * Copyright (C) 2003 Tom Lord
 *               2005 Canonical Ltd
 *
 * 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 "hackerlab/fs/cwd.h"
#include "hackerlab/char/str.h"
#include "libfsutils/tmp-files.h"
#include "libfsutils/rmrf.h"
#include "libfsutils/copy-file.h"
#include "libarch/namespace.h"
#include "libarch/invent.h"
#include "libarch/project-tree.h"
#include "libarch/my.h"
#include "libarch/archive.h"
#include "libarch/pristines.h"
#include "libarch/build-revision.h"
#include "libarch/debug.h"
#include "libarch/inode-sig.h"
#include "libarch/libraries.h"
#include "libarch/local-cache.h"
#include "libarch/library-txn.h"
#include "commands/cmd.h"
#include "commands/cmdutils.h"
#include "commands/export.h"
#include "commands/version.h"


static t_uchar * usage = N_("[options] [revision] [dir]");

#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_silent, "s", "silent", 0, \
      N_("no output"))


t_uchar arch_cmd_export_help[] = N_("export all or some of a tree revision\n"
				    "Extract REVISION from an archive, creating a new source tree with no control information.\n"
				    "\n"
  );

enum options
{
  OPTS (OPT_ENUM)
};

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



static int
filename_matches (regex_t * pattern, char * filename)
{
  int answer;

  answer = regexec (pattern, filename, 0, 0, 0);

  if (answer == REG_NOMATCH)
    return 0;

  if (answer == REG_NOERROR)
    return 1;

  panic ("unexpected regexec error in arch_inventory_traversal");
  return -1;
}

/* FIXME: add doxygen docs
 * return true if the path is in .arch-ids or {arch}, ie if it matches
 *  s/(\{arch\}|\.arch-ids)/.
 */
int
arch_is_in_control_dir(t_uchar * path)
{
  static int compiled = 0;
  static regex_t control_pattern = {0,};

  if (!compiled)
    {
      int re_error;

      re_error = regcomp (&control_pattern,
                          "\\.arch-ids|\\{arch\\}",
                          REG_EXTENDED);
      invariant (!re_error);
      compiled = 1;
    }

  return filename_matches (&control_pattern, path);
}

/*FIXME: add doxygen docs
 * return a new rel_table which does not contain any control files
 */
rel_table
arch_filter_control_files (rel_table * index)
{
  rel_table new_index = 0;
  int l = 0;

  for (l = 0; l < rel_n_records (*index); ++l)
    {
      if (!(
	(arch_is_control_file ((*index)[l][0], NULL) && str_cmp_suffix_n((*index)[l][0], ".arch-inventory", 15)) ||
	arch_is_in_control_dir ((*index)[l][0])))
 	{ 
	  rel_add_records (&new_index, rel_copy_record ((*index)[l]), 0);
	}
    }
  safe_flush (1);
  return new_index;
}

/*FIXME: add doxygen docs
 * return a new rel_table which begins with prefix
 */
rel_table
arch_filter_to_subdir (rel_table * index, t_uchar * prefix)
{
  rel_table new_index = 0;
  int l = 0;
  for (l = 0; l < rel_n_records (*index); ++l)
    {
      if (!str_cmp_prefix(prefix, (t_uchar *)(*index)[l][0]))
	  rel_add_records (&new_index, rel_copy_record ((*index)[l]), 0);
    }
  return new_index;
}



int
arch_cmd_export (t_uchar * program_name, int argc, char * argv[])
{
  int o;
  struct opt_parsed * option;
  t_uchar * cache_dir_spec = 0;
  t_uchar * selected_library = 0;
  int silent = 0;

  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_export_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_silent:
          {
            silent = 1;
            break;
          }
        }
    }

  if ((argc < 2) || (argc > 3))
    goto usage_error;

  {
    int here_fd;
    t_uchar * revision_spec = 0;
    t_uchar * revision = 0;
    t_uchar * output_dir = 0;
    t_uchar * tmp_dir = 0;
    t_uchar * last_arg = 0;
    t_uchar * subdir = 0;
    struct arch_archive * arch = 0;
    arch_project_tree_t * tree;

    here_fd = safe_open (".", O_RDONLY, 0);

    tree = arch_project_tree_new (talloc_context, ".");

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

    /* three cases:
     * no args - tree version, automatic dir.
     *  XXX not currently supported. (see the check up above)
     * 1 arg - named revision or output dir if there is a tree, named revision otherwise.
     * 2 args - named revision and output dir
     */

    /* input canonicalisation */
      {
	/* disambiguate dir or revision */
	if (tree)
	  {
	    arch_project_tree_check_name (tree, &arch, &revision_spec, (argc > 1) ? argv[1] : (char *)tree->fqrevision);
	    if (!arch && argc == 2)
	      {
		/* ambigous, could be a dir spec */
		arch_project_tree_check_name (tree, &arch, &revision_spec, tree->fqrevision);
		output_dir = str_save (0, argv[1]);
	      }
	    else if (argc == 3)
		output_dir = str_save (0, argv[2]);
	  }
	else
	  {
	    if (argc < 2)
	      goto usage_error;
	    /* no tree, argv[1] must be a revision */
	    arch = arch_archive_connect_branch (argv[1], &revision_spec);
	    if (argc == 3)
		output_dir = str_save (0, argv[2]);
	  }
	if (!arch)
	  {
	    safe_printfmt (2, _("Cannot connect to archive for revision %s\n"), argv[1]);
	    exit (2);
	  }
      }

    invariant (!!arch);
    revision_spec = str_replace (revision_spec, arch_determine_fqrevision(&arch, NULL, revision_spec, argv[0]));
    revision = arch_parse_package_name (arch_ret_non_archive, NULL, revision_spec);
    
    if (!output_dir)
      {
	/* name of revision part */
	t_uchar * cwd = safe_current_working_directory ();
	output_dir = file_name_in_vicinity (0, cwd, revision);
	lim_free (0, cwd);
      }

    invariant (!!output_dir);
      {
	t_uchar * canonical_dir = file_name_from_directory (0, output_dir);
	t_uchar * output_dirname = file_name_directory (0, canonical_dir);
	int output_dirname_exists = safe_access (output_dirname, F_OK) == 0;

	if (!output_dirname_exists)
	  {
	    safe_printfmt (2, _("Parent dir of output directory %s does not exist\n"), output_dirname);
	    exit (2);
	  }
	tmp_dir = tmp_file_name_in_tmp ("export");
      }

    if (!safe_access (output_dir, F_OK))
      {
        safe_printfmt (2, "%s: output directory already exists (%s)\n",
                       argv[0], output_dir);
        exit (1);
      }

    rmrf_file (tmp_dir);

    safe_mkdir (tmp_dir, 0777);

    {
        t_uchar * revision_dir = 0;
        rel_table index = 0;
        rel_table index_stripped = 0;
        rel_table index_subdird = 0;
	arch_project_tree_t * tmp_tree;
        int old_debug_level = arch_debug_levels[dbg_builder];

	/* FIXME: inefficient, since arch_build_revision is already
	 * doing an inventory and throwing it away.
	 */
        if (silent)
          arch_debug_levels[dbg_builder] = -1;
	arch_build_revision (tmp_dir, arch, arch->official_name, revision, NULL);
        arch_debug_levels[dbg_builder] = old_debug_level;
	tmp_tree = arch_project_tree_new (talloc_context, tmp_dir);
	index = arch_source_inventory (tmp_tree, 1, 0, 0);
        safe_flush (1);
	index_stripped = arch_filter_control_files (&index);
	if (subdir)
	  {
	    safe_printfmt(2," limiting to %s", subdir);
	    index_subdird = arch_filter_to_subdir (&index_stripped, str_alloc_cat(0, "./", subdir));
	  }
	else
	  {
	    index_subdird = index_stripped;
	  }
        rel_sort_table_by_field (0, index_subdird, 0);
	copy_file_list (output_dir, tmp_dir, index_subdird);

        lim_free (0, revision_dir);
	arch_project_tree_delete (tmp_tree);
    }

    arch_archive_close (arch);

    safe_fchdir (here_fd);
    safe_close (here_fd);
    rmrf_file (tmp_dir);

    lim_free (0, revision_spec);
    lim_free (0, revision);
    lim_free (0, output_dir);
    lim_free (0, tmp_dir);
    arch_project_tree_delete (tree);
  }

  lim_free (0, selected_library);
  lim_free (0, cache_dir_spec);

  return 0;
}



syntax highlighted by Code2HTML, v. 0.9.1