/* $Id: mktexlsr 3.2 1998/07/26 10:34:20 stoklund Exp stoklund $ */
/* mktexlsr.c: generate filename databases.

   Copyright (C) 1998 Jakob Stoklund Olesen

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this library; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */

#include "kpathsea:config.h"
#include "kpathsea:progname.h"
#include "kpathsea:getopt.h"
#include "kpathsea:str-list.h"
#include "kpathsea:expand.h"
#include "kpathsea:pathsearch.h"
#include "kpathsea:absolute.h"
#include "kpathsea:riscos.h"

#include "OS:osgbpb.h"

extern string kpathsea_version_string;
extern string kpse_bug_address;

static void make_database (const_string dirname);

#define VERSION "0.0"
#define USAGE "\
  Rebuild ls-R database files in the given or default directories.\n\
\n\
-version     Display version string and quit.\n\
-help        Display this message and quit.\n\
-debug=<NUM> Set kpathsea debugging options.\n\
"

static struct option long_options[] =
{
  {"version", no_argument, NULL, 'V'},
  {"help", no_argument, NULL, 'h'},
#ifdef KPSE_DEBUG
  {"debug", required_argument, NULL, 'D'},
#endif
  {0, 0, 0, 0}
};

static void
read_command_line (int argc, string argv[])
{
  int option_index, opt;

  kpse_set_program_name (argv[0], NULL);

  while (1)
    {
      opt = getopt_long_only (argc, argv, "Vh", long_options, &option_index);
      if (opt == -1)
	break;
      switch (opt)
	{
	case 'h':		/* -h or -help */
	  printf ("Usage: %s [OPTIONS] [DIR] ...\n",
		  program_invocation_short_name);
	  puts (USAGE);
	  puts (kpse_bug_address);
	  exit (EXIT_SUCCESS);
	case 'V':		/* -V or -version */
	  printf ("%s " VERSION "\n", program_invocation_short_name);
	  puts (kpathsea_version_string);
	  puts ("Copyright (C) 1998 Jakob Stoklund Olesen.\n\
There is NO warranty.  You may redistribute this software\n\
under the terms of the GNU General Public License.\n\
For more information about these matters, see the files named COPYING.");
	  exit (EXIT_SUCCESS);
#ifdef KPSE_DEBUG
	case 'D':		/* -D or -debug */
	  kpathsea_debug |= atoi (optarg);
	  break;
#endif
	}
    }
}

/* The list of directories where ls-R files should be generated. */
static str_list_type dir_list;

/* Add the elements of a path to dir_list */
static void
add_path (const_string path, boolean allow_relative)
{
  string exp = kpse_path_expand (path);

  if (exp && *exp)
    {
      string dir = kpse_path_element (exp);
      for (; dir; dir = kpse_path_element (NULL))
	{
	  string end = dir + strlen (dir) - 1;
	  while (end >= dir && *end == '.')
	    *end-- = '\0';
	  /* Only use absolute paths */
	  if (allow_relative || kpse_absolute_p (dir, false))
	    str_list_add (&dir_list, riscos_canonicalise (dir, NULL));
	  else
	    {
	      WARNING1 ("skipping %s\n", dir);
	    }
	}
    }
}

int
main (int argc, string argv[])
{
  int n;

  read_command_line (argc, argv);

  /* Get directories given on the commandline */
  if (optind < argc)
    {
      for (; optind < argc; optind++)
	add_path (argv[optind], true);
    }
  else
    add_path ("$TEXMFDBS", false);

  if (STR_LIST_LENGTH (dir_list) == 0)
    {
      FATAL ("no directories to scan");
    }
  /* OK, we're ready to go */
  for (n = 0; n < STR_LIST_LENGTH (dir_list); n++)
    {
      make_database (STR_LIST_ELT (dir_list, n));
    }
}

/*
   Making an ls-R file.
 */

/* Filenames are converted to Unix format */

/* Change all occurences of '/' in NAME to '.' and return true if any */
static boolean
slash_to_dot (string name)
{
  boolean ret;

  for (ret = false; *name; name++)
    if (*name == '/')
      ret = true, *name = '.';
  return ret;
}

/* Filenames without a '/' are normally put in the parent directory. This would be disastrous for aliases files. We let files called 'aliases', 'ls-R' and anything with a ! be a real file. */
static boolean
is_filename (const_string name)
{
  return FILESTRCASEEQ (name, "aliases")
    || FILESTRCASEEQ (name, "ls-R")
    || strchr (name, '!') != NULL;
}

/*
   We scan directories recursively and it is necessary to store the results since we want all files to appear before any subdirectories.
 */

typedef struct subdir_s
{
  /* malloc'ed versions of the full pathname and the leafname of this dir */
  string full_name, leaf_name;
  /* We build a tree structure */
  struct subdir_s *parent, *sibling, *child;
  /* The files in this directory */
  str_list_type files;
}
subdir;

/* The dir hold the ls-R file we're currently generating */
static const_string current_root;

/* allocate and initialise a new subdir. If PARENT==NULL use LEAFNAME as the full name. */
static subdir *
new_subdir (subdir * parent, const_string leafname)
{
  subdir *ret = xmalloc (sizeof (subdir));

  if (parent)
    {
      ret->leaf_name = xstrdup (leafname);
      ret->full_name = concat3 (parent->full_name, ".", leafname);
    }
  else
    {
      ret->leaf_name = NULL;
      ret->full_name = xstrdup (leafname);
    }

  ret->parent = parent;
  ret->sibling = ret->child = NULL;

  ret->files = str_list_init ();

  return ret;
}

/* The size of the buffer passed to OS_GBPB */
#define osgbpb_bufsize 256
/* Scan a directory and allocate subdirectories but don't scan them */
static void
scan_subdir (subdir * dir)
{
  subdir *last_child = NULL, *new_child;

  osgbpb_info gbpb_buffer[osgbpb_bufsize];
  os_error *erp;
  int count, context;

  /* We only use the leafnme for files so we might as well convert it here */
  slash_to_dot (dir->leaf_name);

  context = 0;
  do
    {
      osgbpb_info *object;

      erp = xosgbpb_dir_entries_info (dir->full_name,
				      (osgbpb_info_list *) gbpb_buffer,
				      osgbpb_bufsize,
				      context, sizeof (gbpb_buffer), NULL,
				      &count, &context);
      if (erp != NULL)
	{
	  FATAL2 ("%s: %s\n", dir->full_name, erp->errmess);
	}

      object = gbpb_buffer;
      while (count > 0)
	{
	  switch (object->obj_type)
	    {
	    case 1:		/* This is a file */
	      /* Add the file to this dir or the parent dir */
	      if (slash_to_dot (object->name)
		  || !dir->parent
		  || is_filename (object->name))
		str_list_add (&dir->files, xstrdup (object->name));
	      else
		{
		  string upname = concat3 (dir->leaf_name, ".", object->name);
		  str_list_add (&dir->parent->files, upname);
		}
	      break;
	    case 2:		/* This is a directory */
	    case 3:		/* This is an image file */
	      /* Ignore subdirectories that start with '/' */
	      if (object->name[0] == '/')
		break;
	      new_child = new_subdir (dir, object->name);
	      if (last_child)
		last_child->sibling = new_child;
	      else
		dir->child = new_child;
	      last_child = new_child;
	      break;
	    }
	  /* Find the next element in the buffer. */
	  count--;
	  object = (osgbpb_info *) ((char *) (object + 1)
				    + (strlen (object->name) & ~3));
	}
    }
  while (context != -1);
}

/* comparison function for qsort() */
static int
filename_cmp (const void *p1, const void *p2)
{
  return strcasecmp (*(string *) p1, *(string *) p2);
}

/* Print a directory and all its subdirectories to FILE, then deallocate it. */
static void
put_dir (FILE * file, subdir * dir)
{
  subdir *sub, *next;

  /* We must scan all the subdirectories first because the may contain
     filenames for us. */
  for (sub = dir->child; sub; sub = sub->sibling)
    scan_subdir (sub);

  /* Skip empty dirs */
  if (STR_LIST_LENGTH (dir->files) > 0)
    {
      string p;
      int n;
      /* Current_root is printed as '.' */
      fputs ("\n.", file);
      for (p = dir->full_name + strlen (current_root); *p; p++)
	{
	  switch (*p)
	    {
	    case '.':
	      putc ('/', file);
	      break;
	    case '/':
	      putc ('.', file);
	      break;
	    default:
	      putc (*p, file);
	      break;
	    }
	}
      fputs (":\n", file);

      /* and list the files */
      qsort (STR_LIST (dir->files), STR_LIST_LENGTH (dir->files),
	     sizeof (string), filename_cmp);
      for (n = 0; n < STR_LIST_LENGTH (dir->files); n++)
	{
	  fprintf (file, "%s\n", STR_LIST_ELT (dir->files, n));
	  free (STR_LIST_ELT (dir->files, n));
	}
    }
  /* We don't need the filenames any more */
  str_list_free (&dir->files);

  /* Now we can print all the subdirectories */
  for (sub = dir->child; sub; sub = next)
    {
      next = sub->sibling;
      put_dir (file, sub);
    }
  free (dir);
}

#define MAGIC "% ls-R -- filename database for kpathsea; do not change this line."

/* Create a ls-R database in DIR */
static void
make_database (const_string dirname)
{
  int len = strlen (dirname);
  string elt = xmalloc (len + sizeof (".ls-R"));
  FILE *db;
  subdir *rootdir;

  current_root = dirname;

  strcpy (elt, dirname);
  strcat (elt, ".ls-R");
  db = fopen (elt, "w");
  if (db == NULL)
    {
      WARNING1 ("%s: cannot open for output\n", elt);
      return;
    }
  fprintf (db, "%s\n%% %s\n", MAGIC, dirname);

  if (ferror (db))
    {
      WARNING1 ("%s: cannot write to file\n", elt);
      fclose (db);
      return;
    }

  printf ("Generating %s...\n", elt);
  free (elt);

  rootdir = new_subdir (NULL, current_root);
  scan_subdir (rootdir);
  put_dir (db, rootdir);
  fclose (db);
}
