/* Jungle Monkey
 * Copyright (C) 1999-2001  The Regents of the University of Michigan
 *
 * This program 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 of the License, or
 * (at your option) any later version.
 *
 * This program 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <time.h>
#include <errno.h>
#include <string.h>

#include "sha_cache.h"
#include "jmgnet.h"
#include "util.h"

typedef struct _Entry
{
  GSHA*  sha;
  time_t mtime;	  /* Last time modified */

} Entry;


static void delete_hfunc (gpointer key, gpointer value, gpointer user_data);

static void read_entry (ShaCache* sha_cache, gchar* buf);
static void write_hfunc (gpointer key, gpointer value, gpointer user_data);


ShaCache* 
sha_cache_new (void)
{
  ShaCache* sha_cache;

  sha_cache = g_new0 (ShaCache, 1);
  sha_cache->path_to_sha = g_hash_table_new (g_str_hash, g_str_equal);

  return sha_cache;
}

void
sha_cache_delete (ShaCache* sha_cache)
{
  if (!sha_cache)
    return;

  g_hash_table_foreach (sha_cache->path_to_sha, delete_hfunc, NULL);
  g_hash_table_destroy (sha_cache->path_to_sha);
  g_free (sha_cache);
}

static void
delete_hfunc (gpointer key, gpointer value, gpointer user_data)
{
  Entry* entry = (Entry*) value;

  g_free ((gchar*) key);
  gnet_sha_delete (entry->sha);
  g_free (entry);
}


GSHA*	  
sha_cache_get (ShaCache* sha_cache, const gchar* path)
{
  Entry* entry;
  
  g_return_val_if_fail (sha_cache, NULL);
  g_return_val_if_fail (path,      NULL);

  entry = g_hash_table_lookup (sha_cache->path_to_sha, path);
  if (!entry)
    return NULL;

  return gnet_sha_clone (entry->sha);
}


void
sha_cache_add (ShaCache* sha_cache, const gchar* path, const GSHA* sha)
{
  Entry* entry;
  
  g_return_if_fail (sha_cache);
  g_return_if_fail (path);

  entry = g_hash_table_lookup (sha_cache->path_to_sha, path);
  if (entry)
    {
      gnet_sha_delete (entry->sha);
      entry->sha = gnet_sha_clone (sha);
      return;
    }

  entry = g_new0 (Entry, 1);
  entry->sha = gnet_sha_clone (sha);
  entry->mtime = time(NULL);

  g_hash_table_insert (sha_cache->path_to_sha, g_strdup(path), entry);
}


guint
sha_cache_size (ShaCache* sha_cache)
{
  g_return_val_if_fail (sha_cache, 0);

  return g_hash_table_size (sha_cache->path_to_sha);
}


void
sha_cache_read (ShaCache* sha_cache, FILE* file)
{
  gchar buf[MAXPATHLEN + 2 * GNET_SHA_HASH_LENGTH + 10 + 64];

  g_return_if_fail (sha_cache);
  g_return_if_fail (file);

  while (fgets (buf, sizeof(buf), file) != NULL)
    {
      read_entry (sha_cache, buf);
    }
}


static void
read_entry (ShaCache* sha_cache, gchar* buf)
{
  gchar** split;
  gchar* path;
  struct stat statbuf;
  time_t mtime;
  GSHA* sha;
  gchar* p;
  Entry* entry;

  split = g_strsplit (buf, "|", 3);
  if (!(split[0] && split[1] && split[2]))
    {
      g_strfreev (split);
      return;
    }

  /* Unescape path */
  path = strunesc (split[0], ",\\", '\\');

  /* Ignore if we have it - assume old copy is equivalent */
  entry = g_hash_table_lookup (sha_cache->path_to_sha, path);
  if (entry)
    {
      g_strfreev (split);
      g_free (path);
      return;
    }

  /* Get the time */
  mtime = 0;
  for (p = split[2]; *p && *p >= '0' && *p <= '9'; ++p)
    mtime = mtime * 10 + (*p - '0');

  /* Check if file exists */
  if (stat (path, &statbuf))
    {
      if (errno != ENOENT)
	g_warning ("stat failed: %s\n", strerror(errno));
      g_strfreev (split);
      g_free (path);
      return;
    }

  /* Check if file has been modified since last SHA */
  if (statbuf.st_mtime > mtime)
    {
      g_strfreev (split);
      g_free (path);
      return;
    }

  /* Get the sha */
  sha = gnet_sha_new_string (split[1]);
  g_return_if_fail (sha);

  /* Create an add a new entry */
  entry = g_new0 (Entry, 1);
  entry->sha = sha;
  entry->mtime = mtime;

  g_hash_table_insert (sha_cache->path_to_sha, path, entry);

  g_strfreev (split);
}



void
sha_cache_write (ShaCache* sha_cache, FILE* file)
{
  g_return_if_fail (sha_cache);
  g_return_if_fail (file);

  if(sha_cache) g_hash_table_foreach (sha_cache->path_to_sha, write_hfunc, file);
}


static void
write_hfunc (gpointer key, gpointer value, gpointer user_data)
{
  gchar* path    = (gchar*) key;
  Entry* entry   = (Entry*) value;
  FILE*  file    = user_data;
  gchar* pathe;
  gchar shastr[2 * GNET_SHA_HASH_LENGTH + 1];
  
  pathe = stresc (path, ",\\", ',');
  gnet_sha_copy_string (entry->sha, shastr);
  shastr[2 * GNET_SHA_HASH_LENGTH] = '\0';

  fprintf (file, "%s|%s|%lu\n", pathe, shastr, entry->mtime);
  g_free (pathe);
}


syntax highlighted by Code2HTML, v. 0.9.1