/** @file tree.c
 * Directory tree handling and file system utility functions
 * @author Marko Mäkelä <msmakela@nic.funet.fi>
 */

/* Copyright © 2003 Marko Mäkelä.

   This file is part of PHOTOMOLO, a program for generating
   thumbnail images and HTML files for browsing digital photographs.

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

   PHOTOMOLO 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
   General Public License for more details.

   The GNU General Public License is often shipped with GNU software, and
   is generally kept in a file called COPYING or LICENSE.  If you do not
   have a copy of the license, write to the Free Software Foundation,
   59 Temple Place, Suite 330, Boston, MA 02111 USA. */

#undef __STRICT_ANSI__ /* need Unix-style interfaces in Win32 */
#include "tree.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#if defined WIN32 || defined __WIN32
# include <sys/utime.h>
#else
# include <utime.h>
#endif
#include <errno.h>

/** Initialize a directory tree structure
 * @param tree	the directory tree object
 * @param name	the path name of the directory
 */
void
inittree (struct dirtree* tree,
	  const char* name)
{
  memset (tree, 0, sizeof *tree);
  tree->name = strdup (name);
}

/** Compare two struct imagefile objects
 * @param a	the first object
 * @param b	the second object
 * @return	negative, zero or positive
 */
static int
cmp_files (const void* a,
	   const void* b)
{
  register const struct imagefile* aa = a;
  register const struct imagefile* bb = b;
  return aa->mtime == bb->mtime
    ? strcmp (aa->name, bb->name)
    : aa->mtime - bb->mtime;
}

/** Read a directory tree
 * @param tree	the destination tree (with the path name filled in)
 * @param dir_p	callback for pruning directories by name
 * @return	zero on success, nonzero on failure
 */
int
readtree (struct dirtree* tree,
	  int (*dir_p) (const char*))
{
  /** directory handle */
  DIR* dir;
  /** directory entry */
  struct dirent* dirent;
  /** path name for stat(2) calls */
  char* pathname;
  /** counter */
  unsigned i;

  if (!(dir = opendir (tree->name))) {
    fputs (tree->name, stderr);
    perror (": opendir");
    return 1;
  }

  /* set up the path name for stat(2) calls */
  i = strlen (tree->name);
  if (!(pathname = malloc (i + 1))) {
  memory0:
    fputs (tree->name, stderr);
    fputs (": out of memory\n", stderr);
    return 2;
  }
  memcpy (pathname, tree->name, i);
  pathname[i++] = '/';

  while ((dirent = readdir (dir))) {
    struct stat statbuf;
    if (!strcmp (dirent->d_name, ".") ||
	!strcmp (dirent->d_name, "..") ||
	(dir_p && (*dir_p) (dirent->d_name)))
      continue;
    else {
      unsigned namelen = strlen (dirent->d_name) + 1;
      char* s = realloc (pathname, i + namelen);
      if (!s) {
	free (pathname);
	goto memory0;
      }
      pathname = s;
      memcpy (pathname + i, dirent->d_name, namelen);
    }
    if (stat (pathname, &statbuf)) {
      fputs (pathname, stderr);
      perror (": stat");
      free (pathname);
      freetree (tree);
      return 1;
    }

    if (S_ISDIR (statbuf.st_mode)) {
      /* subdirectory */
      if (!(tree->n_dirs & (tree->n_dirs + 1))) {
	/* double the size allocated for the image file buffer */
	struct dirtree* dirs =
	  realloc (tree->dirs, (tree->n_dirs + 1) * (2 * sizeof *dirs));
	if (!dirs) {
	memory:
	  fputs (pathname, stderr);
	  fputs (": out of memory\n", stderr);
	  free (pathname);
	  freetree (tree);
	  return 2;
	}
	tree->dirs = dirs;
      }
      inittree (&tree->dirs[tree->n_dirs++], pathname);
    }

    if (S_ISREG (statbuf.st_mode)) {
      /* regular file */
      if (!(tree->n_files & (tree->n_files + 1))) {
	/* double the size allocated for the image file buffer */
	struct imagefile* files =
	  realloc (tree->files, (tree->n_files + 1) * (2 * sizeof *files));
	if (!files)
	  goto memory;
	tree->files = files;
      }
      tree->files[tree->n_files].name = strdup (dirent->d_name);
      tree->files[tree->n_files].mtime = statbuf.st_mtime;
      tree->n_files++;
      if (tree->mtime < statbuf.st_mtime)
	tree->mtime = statbuf.st_mtime;
    }
  }

  closedir (dir);
  free (pathname);

  /* process the subdirectories */
  for (i = 0; i < tree->n_dirs; i++) {
    int status = readtree (tree->dirs + i, dir_p);
    if (status) {
      freetree (tree);
      return status;
    }
    if (tree->mtime < tree->dirs[i].mtime)
      tree->mtime = tree->dirs[i].mtime;
  }
  /* sort the files */
  qsort (tree->files, tree->n_files, sizeof *tree->files, cmp_files);
  /* sort the directories */
  qsort (tree->dirs, tree->n_dirs, sizeof *tree->dirs, cmp_files);

  return 0;
}

/** Deallocate a directory tree
 * @param tree	the directory tree
 */
void
freetree (struct dirtree* tree)
{
  register unsigned i;
  free (tree->name);
  for (i = tree->n_files; i--; )
    free (tree->files[i].name);
  free (tree->files);
  for (i = tree->n_dirs; i--; )
    freetree (tree->dirs + i);
  free (tree->dirs);
  memset (tree, 0, sizeof *tree);
}

/** Determine if the specified file exists
 * @param name	the path name of the file
 * @param mtime	the modification time
 * @return	nonzero if the file exists with the given date stamp
 */
int
exists_mtime (const char* name,
	      time_t mtime)
{
  struct stat statbuf;
  return !stat (name, &statbuf) &&
    S_ISREG (statbuf.st_mode) &&
    statbuf.st_mtime == mtime;
}

/** Touch a file to the specified modification time
 * @param path	the path name of the file
 * @param mtime	the modification time
 * @return	zero on success, nonzero on failure
 */
int
touch_mtime (const char* path,
	     time_t mtime)
{
  struct utimbuf utim;
  int status;
  utim.actime = utim.modtime = mtime;
  if ((status = utime (path, &utim))) {
    fputs (path, stderr);
    perror (": utime");
  }
  return status;
}

/** Create a directory
 * @param path	the path name of the directory
 * @return	zero on success, nonzero on failure
 */
int
make_directory (const char* path)
{
  if (!mkdir (path
#if defined WIN32 || defined __WIN32
	      /* no permissions in Win32 */
#else
	      , 0777
#endif
	      ) ||
      errno == EEXIST)
    return 0;
  fputs (path, stderr);
  perror (": mkdir");
  return 1;
}

/** Remove a directory tree
 * @param path	the path name of the directory
 * @return	zero on success, nonzero on failure
 */
int
remove_directory (const char* path)
{
  /** directory handle */
  DIR* dir;
  /** directory entry */
  struct dirent* dirent;
  /** path name for unlink(2) calls */
  char* pathname;
  /** counter */
  unsigned i;

  if (!rmdir (path) || errno == ENOENT)
    return 0; /* directory does not exist, good enough */
  if (!(dir = opendir (path))) {
    fputs (path, stderr);
    perror (": opendir");
    return 1;
  }

  /* set up the path name for stat(2) calls */
  i = strlen (path);
  if (!(pathname = malloc (i + 1))) {
  memory0:
    fputs (path, stderr);
    fputs (": remove_directory: out of memory\n", stderr);
    return 2;
  }
  memcpy (pathname, path, i);
  pathname[i++] = '/';

  while ((dirent = readdir (dir))) {
    if (!strcmp (dirent->d_name, ".") ||
	!strcmp (dirent->d_name, ".."))
      continue;
    else {
      unsigned namelen = strlen (dirent->d_name) + 1;
      char* s = realloc (pathname, i + namelen);
      if (!s) {
	free (pathname);
	goto memory0;
      }
      pathname = s;
      memcpy (pathname + i, dirent->d_name, namelen);
      if (unlink (pathname)) {
	fputs (pathname, stderr);
	perror (": unlink");
	free (pathname);
	return 2;
      }
    }
  }

  closedir (dir);
  free (pathname);

  if (rmdir (path)) {
    fputs (path, stderr);
    perror (": rmdir");
    return 2;
  }

  return 0;
}
