/* library-txn.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 "hackerlab/bugs/panic.h"
#include "hackerlab/char/str.h"
#include "hackerlab/char/char-class.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/vu/safe.h"
#include "libfsutils/rmrf.h"
#include "libfsutils/link-tree.h"
#include "libfsutils/ensure-dir.h"
#include "libfsutils/tmp-files.h"
#include "libfsutils/copy-file.h"
#include "libfsutils/same.h"
#include "libarch/arch.h"
#include "libarch/chatter.h"
#include "libarch/namespace.h"
#include "libarch/patch-logs.h"
#include "libarch/build-revision.h"
#include "libarch/invent.h"
#include "libarch/libraries.h"
#include "libarch/project-tree.h"
#include "libarch/apply-changeset.h"
#include "libarch/inode-sig.h"
#include "libarch/replay.h"
#include "libarch/my.h"
#include "libarch/library-txn.h"


/* __STDC__ prototypes for static functions */
static void library_txn_callback (void * vfp, char * fmt, va_list ap);



/*(c arch_library_add)
 * t_uchar * arch_library_add (int chatter_fd,
 *                             struct arch_archive * arch,
 *                             t_uchar * revision,
 *                             t_uchar * opt_library_spec,
 *                             t_uchar * opt_same_dev_path)
 * 
 * Return the path to the _library_ where REVISION from ARCH
 * should be added by library-add.
 *
 * OPT_LIBRARY_SPEC is the (optional) path to a library to which 
 * the user explicitly requested addition.   If not 0, we essentially
 * validate that parameter.
 * 
 * OPT_SAME_DEV_PATH is the (optional) path to a file or directory
 * on the device to which we want to library-add.   This is for
 * things like --link: we need to make sure that the revision
 * is added to a library on the same device.
 * 
 * Subject to the constraints of the OPT_ parameters, the users 
 * library path is searched (in add order):
 * 
 * If the revision exists in some library, the first such library
 * is returned.
 * 
 * Otherwise, if the revision's version exists in some library, the 
 * first such library is returned.
 * 
 * Otherwise, if the revision's branch exists in some library, the 
 * first such library is returned.
 * 
 * Otherwise, if the revision's category exists in some library, the 
 * first such library is returned.
 * 
 * Otherwise, if the revision's archive exists in some library, the 
 * first such library is returned.
 * 
 * Otherwise, the first library in the (qualifying part of) the path
 * is returned.
 * 
 * This function will not return 0.   If no place can be found to add
 * the revision subject to the constraints of the OPT_* parameters,
 * an error message is printed and the process exitted with status 2.
 */
t_uchar *
arch_library_add_choice (arch_patch_id * revision,
                         t_uchar const * const opt_library,
                         t_uchar const * const opt_same_dev_path)
{
  rel_table full_user_path = 0;
  rel_table search_path = 0;
  t_uchar * target_library = 0;


  if (opt_library && opt_same_dev_path)
    {
      if (!on_same_device (opt_library, opt_same_dev_path))
        {
          safe_printfmt (2, "baz: indicated library is not on the right device for dir\n  lib %s\n  dir %s\n", opt_library, opt_same_dev_path);
          exit (2);
        }
    }


  /* For the library path on which to search for
   * a place to add this revision.
   * 
   * Note that the path is in general a subsequence of
   * the user's full path:
   */

  full_user_path = arch_my_library_path (arch_library_path_add_order);

  if (opt_library)
    {
      int x;

      /* The user wants a specific library.
       * 
       * Make sure it is really on the user's library path,
       * then make that one library the entire search path.
       */

      for (x = 0; x < rel_n_records (full_user_path); ++x)
        {
          if (names_same_inode (opt_library, full_user_path[x][0]))
            break;
        }

      if (x == rel_n_records (full_user_path))
        {
          safe_printfmt (2, "baz: indicated library is not on library path\n  dir %s\n", opt_library);
          exit (2);
        }

      rel_add_records (&search_path, rel_make_record (full_user_path[x][0], 0), 0);
    }

  else
    search_path=make_search_path(full_user_path, opt_same_dev_path, 0);

  if (!search_path)
    {
      if (opt_same_dev_path)
	safe_printfmt (2, "baz: no library on the same device as dir\n  dir %s\n", opt_same_dev_path);
      else
	safe_printfmt (2, "baz: no library to add to\n");
      exit (2);
    }


  /* Now the search path is set.
   * 
   * If the requested revision is already somewhere on that path,
   * we're done.
   */
  {
    arch_project_tree_t * already_there = 0;

    already_there = arch_library_find (search_path, revision, 1);

    if (already_there)
      {
        t_uchar * there_vsn = 0;
        t_uchar * there_branch = 0;
        t_uchar * there_category = 0;
        t_uchar * there_archive = 0;

        there_vsn = file_name_directory_file (0, already_there->root);
        there_branch = file_name_directory_file (0, there_vsn);
        there_category = file_name_directory_file (0, there_branch);
        there_archive = file_name_directory_file (0, there_category);

        target_library = file_name_directory_file (0, there_archive);

        lim_free (0, there_vsn);
        lim_free (0, there_branch);
        lim_free (0, there_category);
        lim_free (0, there_archive);
        arch_project_tree_delete (already_there);
      }
    /* Not there -- we must decide where to add it */
    else
      {
	t_uchar *best_guess = arch_find_best_library(arch_patch_id_archive (revision), search_path, arch_patch_id_revision (revision));
	target_library = str_save (0, best_guess);      
      }
  }

  rel_free_table (full_user_path);
  rel_free_table (search_path);

  return target_library;
}

/**
 * This function is similar to the above, but it may return NULL
 *
 * It finds the best greedy library to add a given revision to
 * If opt_same_dev_path has a value, matches are restricted to libraries on the
 * device containing this path
 */
extern t_uchar * arch_library_greedy_add_choice(arch_patch_id * revision, 
						t_uchar const * const opt_same_dev_path,
						int require_greedy)

{
  rel_table full_user_path = 0;
  rel_table search_path = 0;

  t_uchar * target_library = 0;  
  t_uchar * best_guess = 0;

  full_user_path = arch_my_library_path (arch_library_path_add_order);
  search_path = make_search_path (full_user_path, opt_same_dev_path, require_greedy);

  best_guess = arch_find_best_library (arch_patch_id_archive (revision), search_path, arch_patch_id_revision (revision));
  if (best_guess)
    target_library=str_save(0, best_guess);
  
  rel_free_table (full_user_path);
  rel_free_table (search_path);
  return target_library;
}

/* Produces a search path containing only libraries that matches the given
 * criteria:
 * if opt_same_dev_path has a value, canditates must be on the same device as
 * the provided path
 * if require_greedy is true, candidates must be greedy
 * returns 0 if there are no matching candidates
 */
rel_table make_search_path(rel_table full_user_path, t_uchar const *const opt_same_dev_path, int require_greedy)
{
  rel_table search_path = 0;
  int x;

  for (x = 0; x < rel_n_records (full_user_path); ++x)
    {
      if (opt_same_dev_path && !on_same_device (opt_same_dev_path, full_user_path[x][0]))
	continue;

      if (require_greedy && !arch_library_is_greedy(full_user_path[x][0]))
	continue;
      rel_add_records (&search_path, rel_make_record (full_user_path[x][0], 0), 0);
    }

  return search_path;
}


t_uchar * arch_library_find_picky (t_uchar * archive, t_uchar * revision, int check_inode_sigs, t_uchar * opt_same_dev_path, int require_greedy)
{
  arch_project_tree_t * answer_tree;
  t_uchar * answer = 0;
  rel_table search_path = 0;
  rel_table full_user_path = arch_my_library_path (arch_library_path_add_order);
  arch_patch_id patch_id;
  search_path = make_search_path (full_user_path, opt_same_dev_path, require_greedy);

  rel_free_table (full_user_path);
  if (!search_path)
    return answer;

  arch_patch_id_init_archive (&patch_id, archive, revision);
  answer_tree = arch_library_find (search_path, &patch_id, check_inode_sigs);
  answer = str_save (0, answer_tree->root);
  arch_project_tree_delete (answer_tree);
  arch_patch_id_finalise (&patch_id);
  rel_free_table (search_path);
  return answer;
}


/* We search along the path and pick the first revision
 * with the most specific already-present stuff (same version,
 * branch, category, or archive) -- defaulting to the first in the 
 * path.
 */
t_uchar * arch_find_best_library(t_uchar const * const archive, 
				  rel_table search_path,
				  t_uchar const * const revision)
{
  int guess_priority;
  int x;
  t_uchar * best_guess = 0;

  t_uchar * category = 0;
  t_uchar * branch = 0;
  t_uchar * version = 0;
  
  if (rel_n_records (search_path) == 0)
    return 0;
  if (rel_n_records (search_path) == 1)
    return search_path[0][0];
  
  category = arch_parse_package_name (arch_ret_category, 0, revision);
  branch = arch_parse_package_name (arch_ret_package, 0, revision);
  version = arch_parse_package_name (arch_ret_package_version, 0, revision);

  best_guess = search_path[0][0];
  guess_priority = 0;

  for (x = 0; x < rel_n_records (search_path); ++x)
    {
      if (arch_library_has_version (search_path[x][0], archive, version))
	{
	  best_guess = search_path[x][0];
	  break;
	}
      else if (arch_library_has_branch (search_path[x][0], archive, branch))
	{
	  best_guess = search_path[x][0];
	  guess_priority = 3;
	}
      else if ((guess_priority < 2) && arch_library_has_category (search_path[x][0], archive, category))
	{
	  best_guess = search_path[x][0];
	  guess_priority = 2;
	}
      else if ((guess_priority < 1) && arch_library_has_archive (search_path[x][0], archive))
	{
	  best_guess = search_path[x][0];
	  guess_priority = 1;
	}
    }
  lim_free (0, category);
  lim_free (0, branch);
  lim_free (0, version);

  return best_guess;
}


void
arch_library_add (int chatter_fd,
                  int no_patch_noise,
                  struct arch_archive * arch,
                  t_uchar const * const revision,
                  t_uchar const * const opt_library,
                  t_uchar const * const opt_same_dev_path,
                  int sparse,
                  int escape_classes)
{
  arch_patch_id * patch_id = arch_patch_id_new_archive (arch->official_name, revision);
  t_uchar * dest_library = arch_library_add_choice (patch_id, opt_library, opt_same_dev_path);
  t_uchar * dest_directory = arch_library_revision_dir_in_lib (dest_library, patch_id);

  if (!safe_access (dest_directory, F_OK))
    {
      lim_free (0, dest_library);
      lim_free (0, dest_directory);
      talloc_free (patch_id);
      return;
    }

  if (sparse < 0)
      sparse = arch_library_is_sparse (dest_library);

  arch_build_library_revision(no_patch_noise, dest_directory, arch, patch_id, opt_library, opt_same_dev_path, sparse, library_txn_callback, escape_classes);

  lim_free (0, dest_library);
  lim_free (0, dest_directory);
  talloc_free (patch_id);
}


static void
library_txn_callback (void * vfp, char * fmt, va_list ap)
{
  int fd;

  fd = (int)(t_ulong)vfp;

  if (fd >= 0)
    {
      safe_printfmt_va_list (fd, fmt, ap);
      safe_flush (1);
    }
}




/* tag: Tom Lord Fri May 30 13:50:27 2003 (library-txn.c)
 */


syntax highlighted by Code2HTML, v. 0.9.1