/*
 * Copyright 2001-2004 Brandon Long
 * All Rights Reserved.
 *
 * ClearSilver Templating System
 *
 * This code is made available under the terms of the ClearSilver License.
 * http://www.clearsilver.net/license.hdf
 *
 */

/*
 * revision-controlled file system (RCFS) with meta-info storage
 */

#include "cs_config.h"

#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>

#include "util/neo_misc.h"
#include "util/neo_err.h"
#include "util/neo_files.h"
#include "util/neo_hdf.h"
#include "util/ulocks.h"
#include "rcfs.h"

NEOERR * rcfs_meta_load (const char *path, HDF **meta)
{
  NEOERR *err;
  char fpath[_POSIX_PATH_MAX];
  HDF *m;

  snprintf (fpath, sizeof(fpath), "%s,log", path);

  err = hdf_init (&m);
  if (err) return nerr_pass (err);
  err = hdf_read_file (m, fpath);
  if (err)
  {
    hdf_destroy (&m);
    return nerr_pass (err);
  }
  *meta = m;
  return STATUS_OK;
}

static NEOERR * _meta_save (const char *path, HDF *meta)
{
  NEOERR *err;
  char ftmp[_POSIX_PATH_MAX];
  char fpath[_POSIX_PATH_MAX];

  snprintf (ftmp, sizeof(ftmp), "%s,log.tmp", path);
  snprintf (fpath, sizeof(fpath), "%s,log", path);

  err = hdf_write_file (meta, ftmp);
  if (err) return nerr_pass (err);
  if (rename (ftmp, fpath) == -1)
  {
    unlink (ftmp);
    return nerr_raise_errno (NERR_IO, "Unable to rename file %s", ftmp);
  }

  return STATUS_OK;
}

NEOERR * rcfs_meta_save (const char *path, HDF *meta)
{
  NEOERR *err;
  int lock;
  HDF *m;

  err = rcfs_lock (path, &lock);
  if (err) return nerr_pass (err);
  do
  {
    err = rcfs_meta_load (path, &m);
    if (err) break;
    err = hdf_copy (m, "Meta", meta);
    if (err) break;
    err = _meta_save (path, m);
  } while (0);

  rcfs_unlock (lock);
  return nerr_pass (err);
}

/* load a specified version of the file, version -1 is latest */
NEOERR * rcfs_load (const char *path, int version, char **data)
{
  NEOERR *err;
  char fpath[_POSIX_PATH_MAX];

  if (version == -1)
  {
    HDF *meta, *vers;
    int x;

    err = rcfs_meta_load (path, &meta);
    if (err) return nerr_pass (err);
    for (vers = hdf_get_child (meta, "Versions");
	vers;
	vers = hdf_obj_next (vers))
    {
      x = atoi (hdf_obj_name (vers));
      if (x > version) version = x;
    }
    hdf_destroy (&meta);
  }
  snprintf (fpath, sizeof (fpath), "%s,%d", path, version);
  err = ne_load_file (fpath, data);
  return nerr_pass (err);
}

NEOERR * rcfs_save (const char *path, const char *data, const char *user, 
                    const char *log)
{
  NEOERR *err;
  HDF *meta = NULL, *vers;
  char fpath[_POSIX_PATH_MAX];
  char buf[256];
  int version = 0;
  int fd;
  int lock;
  int x, l, w;

  err = rcfs_lock (path, &lock);
  if (err) return nerr_pass (err);
  do
  {
    err = rcfs_meta_load (path, &meta);
    if (err && nerr_handle (&err, NERR_NOT_FOUND))
    {
      /* new file! */
      err = hdf_init (&meta);
    }
    if (err) return nerr_pass (err);
    for (vers = hdf_get_child (meta, "Versions");
	vers;
	vers = hdf_obj_next (vers))
    {
      x = atoi (hdf_obj_name (vers));
      if (x > version) version = x;
    }

    /* new version */
    version++;
    snprintf (fpath, sizeof (fpath), "%s,%d", path, version);
    fd = open (fpath, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
    if (fd == -1)
    {
      err = nerr_raise_errno (NERR_IO, "Unable to create file %s", fpath);
      break;
    }
    l = strlen(data);
    w = write (fd, data, l);
    if (w != l)
    {
      err = nerr_raise_errno (NERR_IO, "Unable to write file %s", fpath);
      close (fd);
      break;
    }
    close (fd);
    snprintf (buf, sizeof(buf), "Versions.%d.Log", version);
    err = hdf_set_value (meta, buf, log);
    if (err) break;
    snprintf (buf, sizeof(buf), "Versions.%d.User", version);
    err = hdf_set_value (meta, buf, user);
    if (err) break;
    snprintf (buf, sizeof(buf), "Versions.%d.Date", version);
    err = hdf_set_int_value (meta, buf, ne_timef());
    if (err) break;
    err = _meta_save (path, meta);
  } while (0);

  rcfs_unlock (lock);
  hdf_destroy (&meta);
  return nerr_pass (err);
}

NEOERR * rcfs_lock (const char *path, int *lock)
{
  NEOERR *err;
  char fpath[_POSIX_PATH_MAX];

  snprintf (fpath, sizeof (fpath), "%s,lock", path);
  err = fCreate (lock, fpath);
  if (err) return nerr_pass (err);
  err = fLock (*lock);
  if (err) 
  {
    fDestroy (*lock);
    return nerr_pass (err);
  } 
  return STATUS_OK;
}

void rcfs_unlock (int lock)
{
  fUnlock (lock);
  fDestroy (lock);
}

NEOERR * rcfs_listdir (const char *path, ULIST **list)
{
  NEOERR *err;
  DIR *dp;
  ULIST *files;
  struct dirent *de;
  int l;
  char *f;

  *list = NULL;
  err = uListInit (&files, 10, 0);
  if (err) return nerr_pass (err);
  dp = opendir(path);
  if (dp == NULL)
  {
    uListDestroy(&files, ULIST_FREE);
    if (errno == ENOENT)
      return nerr_raise (NERR_NOT_FOUND, "Directory %s doesn't exist", path);
    return nerr_raise_errno (NERR_IO, "Unable to open directory %s", path);
  }
  while ((de = readdir (dp)) != NULL)
  {
    l = strlen (de->d_name);
    if (l>4 && !strcmp (de->d_name+l-4, ",log"))
    {
      f = (char *) malloc ((l-3) * sizeof(char));
      if (f == NULL)
      {
	uListDestroy (&files, ULIST_FREE);
	closedir(dp);
	return nerr_raise (NERR_NOMEM, 
	    "Unable to allocate memory for filename %s", de->d_name);
      }
      strncpy (f, de->d_name, l-4);
      f[l-4] = '\0';
      err = uListAppend (files, f);
      if (err)
      {
	free (f);
	uListDestroy (&files, ULIST_FREE);
	closedir(dp);
	return nerr_pass (err);
      }
    }
  }
  *list = files;
  closedir(dp);

  return STATUS_OK;
}