/* archive-mirror.c:
 *
 ****************************************************************
 * Copyright (C) 2003 Tom Lord
 *
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */


#include "hackerlab/os/errno.h"
#include "hackerlab/os/errno-to-string.h"
#include "hackerlab/char/str.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/vu/safe.h"
#include "tla/libfsutils/ensure-dir.h"
#include "tla/libfsutils/tmp-files.h"
#include "tla/libawk/relational.h"
#include "tla/libawk/associative.h"
#include "tla/libarch/my.h"
#include "tla/libarch/chatter.h"
#include "tla/libarch/archive.h"
#include "tla/libarch/namespace.h"
#include "tla/libarch/archive-mirror.h"
#include "tla/libarch/archive-setup.h"
#include "tla/libarch/patch-logs.h"


/* __STDC__ prototypes for static functions */
static void mirror_revision (struct arch_archive * arch,
                             struct arch_archive * to_arch,
                             const t_uchar * revision,
                             const t_uchar * prev_level,
                             struct arch_archive_mirror_options * archive_mirror_opts);
static int list_contains (rel_table list, const t_uchar * elt);



void
arch_archive_mirror (int chatter_fd,
                     struct arch_archive * arch,
                     struct arch_archive * to_arch,
                     t_uchar * limit,
		     struct arch_archive_mirror_options * archive_mirror_opts)
{
  t_uchar * only_category = 0;
  t_uchar * only_branch = 0;
  t_uchar * only_version = 0;
  t_uchar * only_revision = 0;
  rel_table categories = rel_table_nil;
  rel_table to_categories = rel_table_nil;
  rel_table missing_categories = rel_table_nil;
  int c;

  arch_chatter (chatter_fd, "* mirroring %s to %s with limit %s\n", arch->name, to_arch->name, limit);

  if (limit)
    {
      only_category = arch_parse_package_name (arch_ret_category, 0, limit);

      if (arch_valid_package_name (limit, arch_no_archive, arch_req_package, 1))
        {
          only_branch = arch_parse_package_name (arch_ret_package, 0, limit);

          if (!str_cmp (only_branch, only_category))
            {
              lim_free (0, only_branch);
              only_branch = 0;
            }
          else if (arch_valid_package_name (limit, arch_no_archive, arch_req_version, 1))
            {
              only_version = arch_parse_package_name (arch_ret_package_version, 0, limit);

              if (arch_valid_package_name (limit, arch_no_archive, arch_req_patch_level, 1))
                {
                  only_revision = arch_parse_package_name (arch_ret_non_archive, 0, limit);
                }
            }
        }
    }


  categories = arch_archive_categories (arch);
  to_categories = arch_archive_categories (to_arch);
  missing_categories = rel_join (1, rel_join_output (1,0, -1), 0, 0, categories, to_categories);

  for (c = 0; c < rel_n_records (categories); ++c)
    {
      rel_table branches = rel_table_nil;
      rel_table to_branches = rel_table_nil;
      rel_table missing_branches = rel_table_nil;
      int b;

      if (only_category && str_cmp (only_category, rel_peek_str (categories, c, 0)))
        continue;

      branches = arch_archive_branches (arch, rel_peek_str (categories, c, 0));
      to_branches = arch_archive_branches (to_arch, rel_peek_str (categories, c, 0));
      missing_branches = rel_join (1, rel_join_output (1,0, -1), 0, 0, branches, to_branches);

      for (b = 0; b < rel_n_records (branches); ++b)
        {
          rel_table versions = rel_table_nil;
          rel_table to_versions = rel_table_nil;
          rel_table missing_versions = rel_table_nil;
          int v;

          if (only_branch && str_cmp (only_branch, rel_peek_str (branches, b, 0)))
            continue;

          versions = arch_archive_versions (arch, rel_peek_str (branches, b, 0));
          to_versions = arch_archive_versions (to_arch, rel_peek_str (branches, b, 0));
          missing_versions = rel_join (1, rel_join_output (1,0, -1), 0, 0, versions, to_versions);

          for (v = 0; v < rel_n_records (versions); ++v)
            {
              rel_table revisions = rel_table_nil;
              rel_table to_revisions = rel_table_nil;
              rel_table missing_revisions = rel_table_nil;
              int r;

              if (only_version && str_cmp (only_version, rel_peek_str (versions, v, 0)))
                continue;

              if (list_contains (missing_versions, rel_peek_str (versions, v, 0)))
                {
                  arch_setup_archive_simple_ext (chatter_fd, to_arch, rel_peek_str (versions, v, 0));
                }

              revisions = arch_archive_revisions (arch, rel_peek_str (versions, v, 0), 2);
              to_revisions = arch_archive_revisions (to_arch, rel_peek_str (versions, v, 0), 2);
              missing_revisions = rel_join (1, rel_join_output (1,0, -1), 0, 0, revisions, to_revisions);

              for (r = 0; r < rel_n_records (revisions); ++r)
                {
                  const t_uchar * revision = 0;

                  revision = rel_peek_str (revisions, r, 0);

                  if (only_revision && str_cmp (only_revision, revision))
                    continue;

                  if (list_contains (missing_revisions, revision))
                    {
                      t_uchar * prev_level = 0;

                      if (r)
                        {
                          prev_level = arch_parse_package_name (arch_ret_patch_level, 0, rel_peek_str (revisions, r - 1, 0));
                        }

                      arch_chatter (chatter_fd, "** adding revision %s\n", revision);
                      mirror_revision (arch, to_arch, revision, prev_level, archive_mirror_opts);

                      lim_free (0, prev_level);
                    }
                  else if (only_revision && !str_cmp (only_revision, revision))
                    {
                      enum arch_revision_type type; 
                      int is_cached;
                      t_uchar * continuation = 0;
                      t_uchar * continuation_archive = 0;

                      arch_chatter (chatter_fd, "** adding revision %s\n", revision); 
                      arch_revision_type (&type, &is_cached, arch, revision); 
                      if (type == arch_continuation_revision)
                        {
                          continuation = arch_get_continuation (arch, revision);
                          continuation_archive = arch_parse_package_name (arch_ret_archive, 0, continuation);
                        }
                      mirror_cached_revision (is_cached, archive_mirror_opts, type, arch, to_arch, revision, continuation_archive);
                      lim_free (0, continuation);
                      lim_free (0, continuation_archive);
		    }
                }


              rel_free_table (revisions);
              rel_free_table (to_revisions);
              rel_free_table (missing_revisions);
            }

          rel_free_table (versions);
          rel_free_table (to_versions);
          rel_free_table (missing_versions);
        }

      rel_free_table (branches);
      rel_free_table (to_branches);
      rel_free_table (missing_branches);
    }



  lim_free (0, only_category);
  lim_free (0, only_branch);
  lim_free (0, only_version);
  lim_free (0, only_revision);
  rel_free_table (categories);
  rel_free_table (to_categories);
  rel_free_table (missing_categories);
}


static void
mirror_revision (struct arch_archive * arch,
                 struct arch_archive * to_arch,
                 const t_uchar * revision,
                 const t_uchar * prev_level,
                 struct arch_archive_mirror_options * archive_mirror_opts)
{
  t_uchar * version = 0;
  t_uchar * this_level = 0;
  t_uchar * errstr = 0;
  enum arch_revision_type type;
  int is_cached;
  t_uchar * my_uid = 0;
  t_uchar * txn_id = 0;
  t_uchar * log = 0;
  t_uchar * continuation_archive = 0;

  version = arch_parse_package_name (arch_ret_package_version, 0, revision);
  this_level = arch_parse_package_name (arch_ret_patch_level, 0, revision);

  arch_revision_type (&type, &is_cached, arch, revision);

  my_uid = arch_my_id_uid ();
  txn_id = arch_generate_txn_id ();

  if (arch_archive_lock_revision (&errstr, to_arch, version, prev_level, my_uid, txn_id, this_level))
    {
      safe_printfmt (2, "arch-mirror: unable to acquire revision lock (%s)\n    revision: %s/%s\n",
                     errstr, to_arch->name, revision);
      exit (2);
    }

  log = arch_archive_log (arch, revision);
  if (arch_archive_put_log (&errstr, to_arch, version, prev_level, my_uid, txn_id, log))
    {
      safe_printfmt (2, "arch-mirror: unable to send log message to archive (%s)\n    revision: %s/%s\n",
                     errstr, to_arch->name, version);
      exit (2);
    }

  if (type == arch_import_revision)
    {
      t_uchar * tmp_path = 0;
      int fd;

      tmp_path = tmp_file_name ("/tmp", ",,arch-mirror-changeset");
      fd = safe_open (tmp_path, O_RDWR | O_CREAT | O_EXCL, 0000);
      safe_unlink (tmp_path);

      arch_get_import_targz (fd, arch, revision);

      safe_lseek (fd, (off_t)0, SEEK_SET);

      if (arch_archive_put_import_targz (&errstr, to_arch, version, prev_level, my_uid, txn_id, this_level, fd))
        {
          safe_printfmt (2, "arch-mirror: unable to send import tree to archive (%s)\n    revision: %s/%s\n",
                         errstr, arch->name, version);
          exit (2);
        }

      safe_close (fd);

      lim_free (0, tmp_path);
    }
  else
    {
      t_uchar * tmp_path = 0;
      int fd;

      tmp_path = tmp_file_name ("/tmp", ",,arch-mirror-full-text");
      fd = safe_open (tmp_path, O_RDWR | O_CREAT | O_EXCL, 0000);
      safe_unlink (tmp_path);

      arch_get_patch_targz (fd, arch, revision);

      safe_lseek (fd, (off_t)0, SEEK_SET);

      if (arch_archive_put_changeset_targz (&errstr, to_arch, version, prev_level, my_uid, txn_id, this_level, fd))
        {
          safe_printfmt (2, "arch-mirror: unable to send changeset to archive (%s)\n    revision: %s/%s\n",
                         errstr, arch->name, version);
          exit (2);
        }

      safe_close (fd);

      lim_free (0, tmp_path);
    }

  if (type == arch_continuation_revision)
    {
      t_uchar * continuation = 0;

      continuation = arch_get_continuation (arch, revision);

      continuation_archive = arch_parse_package_name (arch_ret_archive, 0, continuation);

      if (arch_archive_put_continuation (&errstr, to_arch, version, prev_level, my_uid, txn_id, continuation))
        {
          safe_printfmt (2, "arch-mirror: unable to send tagged revision id to archive (%s)\n    revision: %s/%s\n",
                         errstr, to_arch->name, revision);
          exit (2);
        }

      lim_free (0, continuation);
    }

  if (arch_revision_ready (&errstr, to_arch, version, prev_level, my_uid, txn_id, this_level))
    {
      safe_printfmt (2, "arch-mirror: unable to complete phase-1 commit transaction (%s)\n    revision: %s/%s\n",
                     errstr, to_arch->name, version);
      exit (2);
    }

  if (arch_archive_finish_revision (&errstr, to_arch, version, prev_level, my_uid, txn_id, this_level))
    {
      safe_printfmt (2, "arch-mirror: unable to complete commit transaction (%s)\n    revision: %s/%s\n",
                     errstr, to_arch->name, version);
      exit (2);
    }

  if (archive_mirror_opts->print_summary)
    {
      t_uchar * log = arch_archive_log (to_arch, revision);
      assoc_table headers = 0;
      t_uchar * body = 0;
      
      arch_parse_log (0, &headers, (const t_uchar **)&body, log);
      arch_print_headers_summary (1, 4, headers, arch_include_summary);
      lim_free (0, log);
      free_assoc_table (headers);
      lim_free (0, body);
    }
  
  mirror_cached_revision (is_cached, archive_mirror_opts, type, arch, to_arch, revision, continuation_archive);

  lim_free (0, version);
  lim_free (0, my_uid);
  lim_free (0, txn_id);
  lim_free (0, log);
  lim_free (0, continuation_archive);
}

void
mirror_cached_revision (int is_cached,
                        struct arch_archive_mirror_options * archive_mirror_opts,
                        enum arch_revision_type type,
                        struct arch_archive * arch,
                        struct arch_archive * to_arch, 
                        const t_uchar * revision, 
                        const t_uchar * continuation_archive)
{
  t_uchar * version = 0;
  version = arch_parse_package_name (arch_ret_package_version, 0, revision);
  if (is_cached
      && ((archive_mirror_opts->cachedrevs == arch_mirror_all_cachedrevs)
          || ((archive_mirror_opts->cachedrevs == arch_mirror_foreign_continuation_cachedrevs)
              && (type == arch_continuation_revision)
              && (str_cmp (arch->official_name, continuation_archive)))))
    {
      t_uchar * errstr = 0;
      t_uchar * tmp_path = 0;
      int fd;

      tmp_path = tmp_file_name ("/tmp", ",,arch-mirror-archive-cached");
      fd = safe_open (tmp_path, O_RDWR | O_CREAT | O_EXCL, 0000);
      safe_unlink (tmp_path);

      arch_get_cached_revision_targz (fd, arch, revision);

      safe_lseek (fd, (off_t)0, SEEK_SET);

      if (arch_archive_put_cached_targz (&errstr, to_arch, revision, fd))
        {
          safe_printfmt (2, "arch-mirror: unable to send cached revision to archive (%s)\n    revision: %s/%s\n",
                         errstr, arch->name, version);
          exit (2);
        }

      safe_close (fd);

      lim_free (0, tmp_path);
    }
  else 
    {
      t_uchar * errstr = 0;

      if (arch_archive_delete_cached (&errstr, to_arch, revision))
        {
          safe_printfmt (2, "%s: unable to delete cached revision of %s (%s)\n",
                         to_arch->name, revision, errstr);
          exit (1);
        }
    }
  lim_free (0, version);
}



static int
list_contains (rel_table list, const t_uchar * elt)
{
  int x;

  for (x = 0; x < rel_n_records (list); ++x)
    {
      if (!str_cmp (elt, rel_peek_str (list, x, 0)))
        return 1;
    }
  return 0;
}





/* tag: Tom Lord Mon Jun  9 23:29:24 2003 (archive-mirror.c)
 */
