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

#include "hackerlab/bugs/panic.h"
#include "hackerlab/os/time.h"
#include "hackerlab/char/str.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/vu/safe.h"
#include "tla/libdate/date-string.h"
#include "tla/libfsutils/rmrf.h"
#include "tla/libfsutils/string-files.h"
#include "tla/libfsutils/copy-file.h"
#include "tla/libfsutils/tmp-files.h"
#include "tla/libfsutils/rmrf.h"
#include "tla/libarch/patch-logs.h"
#include "tla/libarch/invent.h"
#include "tla/libarch/my.h"
#include "tla/libarch/hooks.h"
#include "tla/libarch/namespace.h"
#include "tla/libarch/pristines.h"
#include "tla/libarch/project-tree.h"
#include "tla/libarch/changelogs.h"
#include "tla/libarch/local-cache.h"
#include "tla/libarch/import.h"


/* __STDC__ prototypes for static functions */



static t_uchar * arch_prepare_import_pristine (t_uchar ** changelog_loc_ret,
                                               t_uchar ** cooked_log_ret,
                                               t_uchar * tree_root,
                                               t_uchar * raw_log,
                                               t_uchar * archive,
                                               t_uchar * version,
                                               int full_meta);
static void arch_import_mid_commit (t_uchar * tree_root, t_uchar * cooked_log);
static void arch_finish_import (t_uchar * tree_root,
                                t_uchar * archive,
                                t_uchar * version,
                                t_uchar * pristine,
                                t_uchar * changelog_loc,
                                int full_meta);
static void arch_import_failed (t_uchar * tree_root, t_uchar * archive, t_uchar * version, t_uchar * pristine);
static rel_table all_patches (t_uchar * tree_root);



void
arch_import (struct arch_archive * arch,
             t_uchar * version,
             t_uchar * tree_root,
             t_uchar * raw_log)
{
  t_uchar * changelog_loc = 0;
  t_uchar * errstr;
  t_uchar * pristine_path = 0;
  t_uchar * cooked_log = 0;
  t_uchar * my_uid = 0;
  t_uchar * txn_id = 0;
  t_uchar * revision;
  const int full_meta = arch_tree_has_meta_flag (tree_root);
  
  revision = str_alloc_cat (0, version, "--base-0");

  pristine_path = arch_prepare_import_pristine (&changelog_loc, &cooked_log, tree_root, raw_log, arch->name, version, full_meta);

  my_uid = arch_my_id_uid ();
  txn_id = arch_generate_txn_id ();
  if (arch_revision_exists (arch, revision))
    {
      safe_printfmt (2, "arch_import: the revision already exists\n    revision: %s/%s\n", arch->official_name, revision);
      exit (2);
    }

  if (arch_archive_lock_revision (&errstr, arch, version, 0, my_uid, txn_id, "base-0"))
    {
      safe_printfmt (2, "arch_import: unable to acquire revision lock (%s)\n    tree: %s\n    revision: %s/%s--base-0\n",
                     errstr, tree_root, arch->name, version);
      exit (2);
    }

  if (arch_archive_put_log (&errstr, arch, version, 0, my_uid, txn_id, cooked_log))
    {
      safe_printfmt (2, "arch_import: unable to send log message to archive (%s)\n    tree: %s\n    revision: %s/%s\n",
                     errstr, tree_root, arch->name, version);
      exit (2);
    }

  if (arch_archive_put_import (&errstr, arch, version, 0, my_uid, txn_id, "base-0", pristine_path))
    {
      safe_printfmt (2, "arch_import: unable to send import tree to archive (%s)\n    tree: %s\n    revision: %s/%s\n",
                     errstr, tree_root, arch->name, version);
      exit (2);
    }

  if (arch_revision_ready (&errstr, arch, version, 0, my_uid, txn_id, "base-0"))
    {
      safe_printfmt (2, "arch_import: error sending tree to archive (%s)\n    tree: %s\n    revision: %s/%s\n",
                     errstr, tree_root, arch->name, version);
      exit (2);
    }

  arch_import_mid_commit (tree_root, cooked_log);

  if (arch_archive_finish_revision (&errstr, arch, version, 0, my_uid, txn_id, "base-0"))
    {
      arch_import_failed (tree_root, arch->name, version, pristine_path);
      safe_printfmt (2, "arch_import: unable to complete import transaction (%s)\n    tree: %s\n    revision: %s/%s\n",
                     errstr, tree_root, arch->name, version);
      exit (2);
    }

  arch_finish_import (tree_root, arch->name, version, pristine_path, changelog_loc, full_meta);

  arch_run_hook ("import", "ARCH_ARCHIVE", arch->name, "ARCH_REVISION", revision, "ARCH_TREE_ROOT", tree_root, (t_uchar*)0);
  
  lim_free (0, revision);
  lim_free (0, changelog_loc);
  lim_free (0, pristine_path);
  lim_free (0, cooked_log);
  lim_free (0, my_uid);
  lim_free (0, txn_id);
}




static t_uchar *
arch_prepare_import_pristine (t_uchar ** changelog_loc_ret,
                              t_uchar ** cooked_log_ret,
                              t_uchar * tree_root,
                              t_uchar * raw_log,
                              t_uchar * archive,
                              t_uchar * version,
                              int full_meta)
{
  t_uchar * tmp_stem = 0;
  t_uchar * tmp_path = 0;
  t_uchar * revision = 0;
  t_uchar * pristine_path = 0;
  rel_table inventory = rel_table_nil;
  rel_table new_files_list = rel_table_nil;
  t_uchar * log_message = 0;
  t_uchar * pristine_log_path = 0;
  int pristine_log_fd;

  /****************************************************************
   * double check that we were handed a valid log message, if any
   */
  if (raw_log)
    invariant (arch_valid_log_file (raw_log));


  /****************************************************************
   * make a temp dir for the pristine copy of the import
   */
  tmp_stem = str_alloc_cat_many (0, ",,import.", version, "--base-0--", archive, str_end);
  tmp_path = tmp_file_name (tree_root, tmp_stem);

  rmrf_file (tmp_path);
  safe_mkdir (tmp_path, 0777);

  revision = str_alloc_cat (0, version, "--base-0");
  pristine_path = file_name_in_vicinity (0, tmp_path, revision);

  inventory = arch_source_inventory (tree_root, 1, 0, 0);

  safe_mkdir (pristine_path, 0777);

  copy_file_list (pristine_path, tree_root, inventory, full_meta);


  /****************************************************************
   * As long as the cache is hot with directories and inodes, compute
   * a list of files for the New-files: header in the log message.
   */
  new_files_list = arch_source_files_inventory (tree_root, 0, 0);


  /****************************************************************
   * Create a log message for the newly committed base-0.
   *
   * Although the message is empty at this point, it's important for
   * it to appear in directory listings of the patch-log, so that it
   * shows up in the New-patches: header.
   */
  pristine_log_path = arch_log_file (pristine_path, archive, revision);
  pristine_log_fd = safe_open (pristine_log_path, O_WRONLY | O_CREAT | O_EXCL, 0666);


  /****************************************************************
   * Generate the actual log message, adding automatically generated
   * headers either to the supplied raw-log or to a default raw-log.
   */
  {
    t_uchar * my_id = 0;
    time_t now;
    t_uchar * std_date = 0;
    t_uchar * human_date = 0;
    int log_fd;
    rel_table patches = rel_table_nil;
    t_uchar * eoh;

    my_id = arch_my_id ();
    now = time(0);
    std_date = standard_date (now);
    human_date = pretty_date (now);

    log_fd = make_output_to_string_fd ();

    safe_printfmt (log_fd, "Revision: %s--base-0\n", version);
    safe_printfmt (log_fd, "Archive: %s\n", archive);
    safe_printfmt (log_fd, "Creator: %s\n", my_id);
    safe_printfmt (log_fd, "Date: %s\n", human_date);
    safe_printfmt (log_fd, "Standard-date: %s\n", std_date);

    /********************************
     * Copy headers from the raw log
     */
    if (raw_log)
      {
        eoh = raw_log;
        while (1)
          {
            eoh = str_chr_index (eoh, '\n');
            if (!eoh || (eoh[1] == '\n') || (!eoh[1]))
              break;
            ++eoh;
          }

        if (eoh)
          {
            eoh = eoh + 1;
            safe_printfmt (log_fd, "%.*s", (int)(eoh - raw_log), raw_log);
          }
      }
    else
      {
        safe_printfmt (log_fd, "Summary: initial import\n");
      }

    /********************************
     * automatic headers for New-files: and New-patches:
     */
    arch_print_log_list_header (log_fd, "New-files", new_files_list, 0);
    patches = all_patches (pristine_path);
    arch_print_log_list_header (log_fd, "New-patches", patches, 0);

    /********************************
     * copy or generate the log body
     */
    if (!raw_log)
      safe_printfmt (log_fd, "\n\n(automatically generated log message)\n");
    else
      {
        if (*eoh)
          {
            safe_printfmt (log_fd, "%s", eoh);
          }
        else
          {
            safe_printfmt (log_fd, "\n\n");
          }
      }

    /********************************
     * oh... did i mention we were writing
     * the log to a string?
     */
    log_message = string_fd_close (log_fd);

    lim_free (0, my_id);
    lim_free (0, std_date);
    lim_free (0, human_date);
    rel_free_table (patches);
  }

  /****************************************************************
   * Write the log into the patch-log of the pristine tree.
   */
  safe_printfmt (pristine_log_fd, "%s", log_message);
  safe_close (pristine_log_fd);

  /****************************************************************
   * If the import tree has a ChangeLog for this version,
   * update it in the pristine tree.
   */
  {
    int x;
    t_uchar * changelog_id_suffix = 0;
    t_uchar * changelog_x_id = 0;
    t_uchar * changelog_i_id = 0;

    changelog_id_suffix = str_alloc_cat_many (0, "_automatic-ChangeLog--", archive, "/", version, str_end);
    changelog_x_id = str_alloc_cat (0, "x", changelog_id_suffix);
    changelog_i_id = str_alloc_cat (0, "i", changelog_id_suffix);

    for (x = 0; x < rel_n_records (inventory); ++x)
      {
        if (!str_cmp (changelog_x_id, rel_peek_str (inventory, x, 1)) || !str_cmp (changelog_i_id, rel_peek_str (inventory, x, 1)))
          {
            struct stat clstatb;
            t_uchar * changelog_path = 0;
            mode_t clmode;
            int out_fd = -1;

            if (changelog_loc_ret)
              *changelog_loc_ret = str_save (0, rel_peek_str (inventory, x, 0));

            changelog_path = file_name_in_vicinity (0, pristine_path, rel_peek_str (inventory, x, 0));

            safe_stat (changelog_path, &clstatb);
            if (full_meta)
              {
                clmode = clstatb.st_mode & 07777;
              }
            else
              {
                clmode = clstatb.st_mode & 0777;
              }
            out_fd = safe_open (changelog_path, O_WRONLY | O_CREAT | O_TRUNC, clmode);
            safe_fchmod (out_fd, clmode);
            if (full_meta)
              safe_fchown (out_fd, clstatb.st_uid, clstatb.st_gid);
            arch_generate_changelog (out_fd, pristine_path, 0, 0, 0, 0, archive, version);
            safe_close (out_fd);

            lim_free (0, changelog_path);

            break;
          }
      }

    lim_free (0, changelog_id_suffix);
    lim_free (0, changelog_x_id);
    lim_free (0, changelog_i_id);
  }


  /****************************************************************
   * Give a copy of the log to the caller.
   */
  if (cooked_log_ret)
    *cooked_log_ret = str_save (0, log_message);


  lim_free (0, tmp_stem);
  lim_free (0, tmp_path);
  lim_free (0, revision);
  rel_free_table (inventory);
  rel_free_table (new_files_list);
  lim_free (0, log_message);
  lim_free (0, pristine_log_path);

  /****************************************************************
   * Give the user the path to the pristine tree for base-0.
   * It's up tot he caller to stash this in the archive.
   */
  return pristine_path;
}


static void
arch_import_mid_commit (t_uchar * tree_root, t_uchar * cooked_log)
{
  arch_start_tree_commit (tree_root, cooked_log);
}


static void
arch_finish_import (t_uchar * tree_root,
                    t_uchar * archive,
                    t_uchar * version,
                    t_uchar * pristine,
                    t_uchar * changelog_loc,
                    int full_meta)
{
  t_uchar * revision = 0;
  t_uchar * pristine_dir = 0;
  t_uchar * pristine_dir_tail = 0;

  revision = str_alloc_cat (0, version, "--base-0");


  /****************************************************************
   * txnally install the log file in the patch log
   */
  arch_finish_tree_commit (tree_root, archive, revision, changelog_loc, full_meta);

  /****************************************************************
   * Install the pristine tree unless we have a greedy library
   * that is eager to slurp it up.
   * 
   * If we don't snarf the library here, it'll be deleted 
   * during "clean up", below.
   */

  if (!arch_greedy_library_wants_revision (archive, revision))
    arch_install_pristine (tree_root, archive, revision, pristine);

  /****************************************************************
   * clean up
   */
  pristine_dir = file_name_directory_file (0, pristine);
  invariant (!!pristine_dir);
  pristine_dir_tail = file_name_tail (0, pristine_dir);
  invariant (!str_cmp_prefix (",,import.", pristine_dir_tail));
  rmrf_file (pristine_dir);

  lim_free (0, revision);
  lim_free (0, pristine_dir);
  lim_free (0, pristine_dir_tail);
}

static void
arch_import_failed (t_uchar * tree_root, t_uchar * archive, t_uchar * version, t_uchar * pristine)
{
  t_uchar * revision = 0;
  t_uchar * pristine_dir = 0;
  t_uchar * pristine_dir_tail = 0;

  revision = str_alloc_cat (0, version, "--base-0");

  /****************************************************************
   * get out of mid-commit state in the project tree
   */
  arch_abort_tree_commit (tree_root, archive, revision);

  /****************************************************************
   * clean up
   */
  pristine_dir = file_name_directory_file (0, pristine);
  invariant (!!pristine_dir);
  pristine_dir_tail = file_name_tail (0, pristine_dir);
  invariant (str_cmp_prefix (",,import.", pristine_dir_tail));
  rmrf_file (pristine_dir);

  lim_free (0, revision);
  lim_free (0, pristine_dir);
  lim_free (0, pristine_dir_tail);
}





static rel_table
all_patches (t_uchar * tree_root)
{
  rel_table log_versions = rel_table_nil;
  rel_table answer = rel_table_nil;
  int x;

  log_versions = arch_log_versions (tree_root, 0, 0, 0, 0);

  for (x = 0; x < rel_n_records (log_versions); ++x)
    {
      t_uchar * archive = 0;
      t_uchar * version = 0;
      rel_table patch_list = rel_table_nil;

      archive = arch_parse_package_name (arch_ret_archive, 0, rel_peek_str (log_versions, x, 0));
      version = arch_parse_package_name (arch_ret_non_archive, 0, rel_peek_str (log_versions, x, 0));

      patch_list = arch_logs (tree_root, archive, version, 1);
      rel_append_x (&answer, patch_list);

      lim_free (0, archive);
      lim_free (0, version);
      rel_free_table (patch_list);
    }

  rel_free_table (log_versions);
  return answer;
}





/* tag: Tom Lord Sat May 24 22:40:45 2003 (import.c)
 */
