/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/**
 * bonobo-storage-memory.c: Memory based Bonobo::Storage implementation
 *
 * Author:
 *   ÉRDI Gergõ <cactus@cactus.rulez.org>
 *
 * Copyright 2001 Gergõ Érdi
 *
 * TODO:
 *  * Make it implement PersistStream so you can flatten a
 *    StorageMem into a Stream
 *  * Create a subclass that supports commit/revert
 */
#include <config.h>
#include <string.h>

#include <bonobo/bonobo-storage-memory.h>
#include <bonobo/bonobo-stream-memory.h>
#include <bonobo/bonobo-exception.h>
#include <bonobo/bonobo-storage.h>

static BonoboObjectClass *bonobo_storage_mem_parent_class;

typedef struct {
	gboolean      is_directory;
	BonoboObject *child;
} BonoboStorageMemEntry;

struct _BonoboStorageMemPriv {
	GHashTable *entries;
};

typedef struct {
	GList                    *list;
	Bonobo_StorageInfoFields  mask;
} DirCBData;

static void
bonobo_storage_mem_entry_free (gpointer data)
{
	BonoboStorageMemEntry *entry = (BonoboStorageMemEntry*) data;

	if (!entry)
		return;
	
	bonobo_object_unref (entry->child);
	
	g_free (entry);
}

static BonoboStorageMemEntry *
bonobo_storage_mem_entry_dup (BonoboStorageMemEntry *entry)
{
	BonoboStorageMemEntry *ret_val = g_new0 (BonoboStorageMemEntry, 1);

	ret_val->is_directory = entry->is_directory;
	ret_val->child = entry->child;

	bonobo_object_ref (ret_val->child);

	return ret_val;
}

static void
split_path (const char  *path,
	    char       **path_head,
	    char       **path_tail)
{
	gchar **path_parts;
	
	if (g_path_is_absolute (path))
		path = g_path_skip_root (path);
	
	path_parts = g_strsplit (path, "/", 2);
	
	*path_head = path_parts[0];
	*path_tail = path_parts[1];

	g_free (path_parts);	
}

static BonoboStorageMem *
smem_get_parent (BonoboStorageMem       *storage,
		 const char             *path,
		 char                  **filename,  /* g_free this */
		 BonoboStorageMemEntry **ret_entry) /* g_free this */
{
	BonoboStorageMem      *ret;
	BonoboStorageMemEntry *entry;
	gchar                 *path_head, *path_tail;

	if (!strcmp (path, "/") || !strcmp (path, "")) {
		if (filename)
			*filename = g_strdup ("/");
		if (ret_entry) {
			*ret_entry = g_new0 (BonoboStorageMemEntry, 1);
			(*ret_entry)->is_directory = TRUE;
			(*ret_entry)->child = BONOBO_OBJECT (storage);
			bonobo_object_ref ((*ret_entry)->child);
		}

		return storage;
	}

	split_path (path, &path_head, &path_tail);
	entry = g_hash_table_lookup (storage->priv->entries,
				     path_head);

	/* No child is found */
	if (!entry) {
		g_free (path_head);

		if (filename)
			*filename = path_tail;

		if (ret_entry)
			*ret_entry = NULL;
		
		return NULL;
	}

	/* This is not the immediate parent */
	if (path_tail && entry->is_directory) {
		ret = smem_get_parent (
			BONOBO_STORAGE_MEM (entry->child),
			path_tail,
			filename,
			ret_entry);
		
		g_free (path_head);
		g_free (path_tail);
		return ret;
	}

	/* This is the immediate parent */
	if (filename)
		*filename = g_strdup (path_head);
	if (ret_entry)
		*ret_entry = bonobo_storage_mem_entry_dup (entry);
	
	g_free (path_tail);
	g_free (path_head);
	
	return storage;
}

static BonoboStorageMem *
smem_get_last_storage (BonoboStorageMem  *storage,
		       const char        *path,
		       char             **last_path)
{
	BonoboStorageMem      *ret;
	BonoboStorageMemEntry *entry;
	gchar *path_head, *path_tail;

	if (!strcmp (path, "/") || !strcmp (path, "")) {
		if (last_path)
			*last_path = NULL;
		return storage;
	}
	
	split_path (path, &path_head, &path_tail);
	entry = g_hash_table_lookup (storage->priv->entries,
				     path_head);

	/* No appropriate child is found */
	if (!entry) {
		if (path_tail) {
			g_free (path_head);
			g_free (path_tail);

			if (last_path)
				*last_path = NULL;
			return NULL;
		} else {
			if (last_path)
				*last_path = path_head;
			
			return storage;
		}
	}

	if (!path_tail) {
		if (entry->is_directory) {
			g_free (path_head);

			if (last_path)
				*last_path = NULL;
			
			return BONOBO_STORAGE_MEM (entry->child);
		} else {
			if (last_path)
				*last_path = path_head;
			
			return storage;
		}
	}

	if (path_tail && entry->is_directory) {
		ret = smem_get_last_storage (
			BONOBO_STORAGE_MEM (entry->child),
			path_tail, last_path);
		g_free (path_head);
		g_free (path_tail);
		return ret;
	}

	g_free (path_tail);
	g_free (path_head);

	if (last_path)
		*last_path = NULL;
	
	return NULL;
}

static Bonobo_StorageInfo *
smem_get_stream_info (BonoboObject                   *stream,
		      const Bonobo_StorageInfoFields  mask,
		      CORBA_Environment              *ev)
{
	Bonobo_StorageInfo *ret_val;
	CORBA_Environment   my_ev;
	
	CORBA_exception_init (&my_ev);
	ret_val = Bonobo_Stream_getInfo (bonobo_object_corba_objref (stream),
					 mask, &my_ev);
	
	if (BONOBO_EX (&my_ev)) {
		if (BONOBO_USER_EX (&my_ev, ex_Bonobo_Stream_IOError))
			bonobo_exception_set (ev, ex_Bonobo_Storage_IOError);
		if (BONOBO_USER_EX (&my_ev, ex_Bonobo_Stream_NoPermission))
			bonobo_exception_set (ev, ex_Bonobo_Storage_NoPermission);
		if (BONOBO_USER_EX (&my_ev, ex_Bonobo_Stream_NotSupported))
			bonobo_exception_set (ev, ex_Bonobo_Storage_NotSupported);
	}
	
	if (mask & Bonobo_FIELD_TYPE)
		ret_val->type = Bonobo_STORAGE_TYPE_REGULAR;
	
	CORBA_exception_free (&my_ev);

	return ret_val;
}

static void
smem_dir_hash_cb (gpointer key,
		  gpointer value,
		  gpointer user_data)
{
	DirCBData                *cb_data = user_data;
	gchar                    *filename = key;
	BonoboStorageMemEntry    *entry = value;
	Bonobo_StorageInfo       *info;
	Bonobo_StorageInfoFields  mask = cb_data->mask;
	
	if (entry->is_directory) {
		info = Bonobo_StorageInfo__alloc ();
		info->name = CORBA_string_dup (filename);
		info->type = Bonobo_STORAGE_TYPE_DIRECTORY;
	} else {
		if (mask & Bonobo_FIELD_CONTENT_TYPE ||
		    mask & Bonobo_FIELD_SIZE) {
			CORBA_Environment my_ev;
			
			CORBA_exception_init (&my_ev);
			info = smem_get_stream_info (entry->child, mask, &my_ev);
			CORBA_exception_free (&my_ev);
		}
		else
			info = Bonobo_StorageInfo__alloc ();

		info->name = CORBA_string_dup (filename);
		info->type = Bonobo_STORAGE_TYPE_REGULAR;
	}

	cb_data->list = g_list_prepend (cb_data->list, info);
}

static Bonobo_StorageInfo*
smem_get_info_impl (PortableServer_Servant          servant,
		    const CORBA_char               *path,
		    const Bonobo_StorageInfoFields  mask,
		    CORBA_Environment              *ev)
{
	BonoboStorageMem       *storage;
	Bonobo_StorageInfo     *ret_val = NULL;
	BonoboStorageMem       *parent_storage;
	BonoboStorageMemEntry  *entry = NULL;
	gchar                  *filename = NULL;

	storage = BONOBO_STORAGE_MEM (bonobo_object (servant));

	parent_storage = smem_get_parent (storage, path, &filename, &entry);

	if (!parent_storage) {
		bonobo_exception_set (ev, ex_Bonobo_Storage_NotFound);
		goto out;
	}

	if (entry->is_directory) {
		if (mask & Bonobo_FIELD_CONTENT_TYPE ||
		    mask & Bonobo_FIELD_SIZE) {
			bonobo_exception_set (ev, ex_Bonobo_Storage_NotSupported);
			goto out;
		}
		
		ret_val = Bonobo_StorageInfo__alloc ();

		ret_val->name = CORBA_string_dup (filename);

		if (mask & Bonobo_FIELD_TYPE)
			ret_val->type = Bonobo_STORAGE_TYPE_DIRECTORY;
		
	} else {
		
		if (mask & Bonobo_FIELD_CONTENT_TYPE ||
		    mask & Bonobo_FIELD_SIZE)
			ret_val = smem_get_stream_info (entry->child, mask, ev);
		else
			ret_val = Bonobo_StorageInfo__alloc ();

		ret_val->name = CORBA_string_dup (filename);
		ret_val->type = Bonobo_STORAGE_TYPE_REGULAR;
		
	}


 out:
	bonobo_storage_mem_entry_free (entry);	
	g_free (filename);
	
	return ret_val;
}

static void
smem_set_info_impl (PortableServer_Servant    servant,
		    const CORBA_char         *path,
		    const Bonobo_StorageInfo *info,
		    Bonobo_StorageInfoFields  mask,
		    CORBA_Environment        *ev)
{
	BonoboStorageMem      *storage;
	BonoboStorageMem      *parent_storage;
	BonoboStorageMemEntry *entry = NULL;
	gchar                 *filename;

	storage = BONOBO_STORAGE_MEM (bonobo_object (servant));

	parent_storage = smem_get_parent (storage, path, &filename, &entry);

	if (!parent_storage) {
		bonobo_exception_set (ev, ex_Bonobo_Storage_NotFound);
		goto out;
	}

	if (entry->is_directory)
		bonobo_exception_set (ev, ex_Bonobo_Storage_NotSupported);
	else {
		CORBA_Environment my_ev;

		CORBA_exception_init (&my_ev);
		Bonobo_Stream_setInfo (
			bonobo_object_corba_objref (entry->child),
			info, mask,
			&my_ev);
		
		if (BONOBO_EX (&my_ev)) {
			if (BONOBO_USER_EX (&my_ev, ex_Bonobo_Stream_IOError))
				bonobo_exception_set (ev, ex_Bonobo_Storage_IOError);
			if (BONOBO_USER_EX (&my_ev, ex_Bonobo_Stream_NoPermission))
				bonobo_exception_set (ev, ex_Bonobo_Storage_NoPermission);
			if (BONOBO_USER_EX (&my_ev, ex_Bonobo_Stream_NotSupported))
				bonobo_exception_set (ev, ex_Bonobo_Storage_NotSupported);
		}
			
		CORBA_exception_free (&my_ev);
	}
	
 out:
	g_free (filename);
	bonobo_storage_mem_entry_free (entry);
}

static Bonobo_Stream
smem_open_stream_impl (PortableServer_Servant   servant,
		       const CORBA_char        *path,
		       Bonobo_Storage_OpenMode  mode,
		       CORBA_Environment       *ev)
{
	BonoboStorageMem       *storage;
	BonoboStorageMem       *parent;
	BonoboStorageMemEntry  *entry;
	gchar                  *path_last;
	BonoboObject           *stream = NULL;

	storage = BONOBO_STORAGE_MEM (bonobo_object (servant));
	parent = smem_get_last_storage (storage, path, &path_last);
	
	if (!parent) {
		bonobo_exception_set (ev, ex_Bonobo_Storage_NotFound);
		goto ex_out;
	}

	entry = g_hash_table_lookup (parent->priv->entries, path_last); 

	/* Error cases */
	/* Case 1: Stream not found */
	if (!entry && !(mode & Bonobo_Storage_CREATE)) {
		bonobo_exception_set (ev, ex_Bonobo_Storage_NotFound);
		goto ex_out;
	}

	/* Case 2: A storage by the same name exists */
	if (entry && entry->is_directory) {
		if (mode & Bonobo_Storage_CREATE)
			bonobo_exception_set (ev, ex_Bonobo_Storage_NameExists);
		else
			bonobo_exception_set (ev, ex_Bonobo_Storage_NotStream);
		goto ex_out;
	}

	if (!entry) {
		stream = bonobo_stream_mem_create (NULL, 0,
						   FALSE, TRUE);
		entry = g_new0 (BonoboStorageMemEntry, 1);
		entry->is_directory = FALSE;
		entry->child = stream;

		g_hash_table_insert (parent->priv->entries,
				     g_strdup (path_last),
				     entry);
		goto ok_out;
	}
	
	stream = entry->child;
	
 ok_out:
	g_free (path_last);
	
	return bonobo_object_dup_ref (BONOBO_OBJREF (stream), ev);
	
 ex_out:
	g_free (path_last);
	
	return CORBA_OBJECT_NIL;
}

static Bonobo_Storage
smem_open_storage_impl (PortableServer_Servant   servant,
			const CORBA_char        *path,
			Bonobo_Storage_OpenMode  mode,
			CORBA_Environment       *ev)
{
	BonoboStorageMem       *storage;
	BonoboStorageMem       *parent_storage;
	BonoboStorageMemEntry  *entry;
	BonoboObject           *ret = NULL;
	gchar                  *path_last = NULL;
	
	storage = BONOBO_STORAGE_MEM (bonobo_object (servant));

	parent_storage = smem_get_last_storage (storage, path, &path_last);
	
	if (!parent_storage) {
		bonobo_exception_set (ev, ex_Bonobo_Storage_NotFound);
		goto ex_out;
	}

	entry = g_hash_table_lookup (parent_storage->priv->entries, path_last);
	
	/* Error cases */
	/* Case 1: Storage not found */
	if (!entry && !(mode & Bonobo_Storage_CREATE)) {
		bonobo_exception_set (ev, ex_Bonobo_Storage_NotFound);
		goto ex_out;
	}

	/* Case 2: A stream by the same name exists */
	if (entry && !entry->is_directory) {
		if (mode & Bonobo_Storage_CREATE)
			bonobo_exception_set (ev, ex_Bonobo_Storage_NameExists);
		else
			bonobo_exception_set (ev, ex_Bonobo_Storage_NotStorage);
		goto ex_out;
	}

	if (!entry) {
		ret = bonobo_storage_mem_create ();
		entry = g_new0 (BonoboStorageMemEntry, 1);
		entry->is_directory = TRUE;
		entry->child = ret;

		g_hash_table_insert (parent_storage->priv->entries,
				     g_strdup (path_last),
				     entry);
		goto ok_out;
	}

	ret = entry->child;
	
 ok_out:
	g_free (path_last);
	
	return bonobo_object_dup_ref (BONOBO_OBJREF (ret), ev);
	
 ex_out:
	g_free (path_last);
	
	return CORBA_OBJECT_NIL;
}

static void
smem_copy_to_impl (PortableServer_Servant  servant,
		   const Bonobo_Storage    target,
		   CORBA_Environment      *ev)
{
	BonoboStorageMem *storage;

	storage = BONOBO_STORAGE_MEM (bonobo_object (servant));

	bonobo_storage_copy_to (
		bonobo_object_corba_objref (BONOBO_OBJECT (storage)),
		target, ev);
}

static Bonobo_Storage_DirectoryList *
smem_list_contents_impl (PortableServer_Servant          servant,
			 const CORBA_char               *path,
			 const Bonobo_StorageInfoFields  mask,
			 CORBA_Environment              *ev)
{
	BonoboStorageMem             *storage;
	Bonobo_Storage_DirectoryList *ret_val = NULL;
	Bonobo_StorageInfo           *info;
	BonoboStorageMem             *last_storage;
	gchar                        *path_last;
	GList                        *list;
	DirCBData                     cb_data;
	int                           i;

	storage = BONOBO_STORAGE_MEM (bonobo_object (servant));
	
	last_storage = smem_get_last_storage (storage, path, &path_last);

	if (!last_storage) {
		bonobo_exception_set (ev, ex_Bonobo_Storage_NotFound);
		goto out;
	}

	if (path_last) { /* The requested entry is a stream or does not
			    exist */
		if (g_hash_table_lookup (last_storage->priv->entries, path_last))
			bonobo_exception_set (ev, ex_Bonobo_Storage_NotStorage);
		else
			bonobo_exception_set (ev, ex_Bonobo_Storage_NotFound);
		goto out;
	}

	cb_data.list = NULL;
	cb_data.mask = mask;

	g_hash_table_foreach (last_storage->priv->entries,
			      smem_dir_hash_cb, &cb_data);

	ret_val = Bonobo_Storage_DirectoryList__alloc ();
	list = cb_data.list;
	ret_val->_length = g_list_length (list);
	ret_val->_buffer = Bonobo_Storage_DirectoryList_allocbuf (ret_val->_length);

	for (i = 0; list != NULL; list = list->next, i++) {
		info = list->data;

		ret_val->_buffer[i].name = CORBA_string_dup (info->name);
		ret_val->_buffer[i].type = info->type;
		ret_val->_buffer[i].content_type = CORBA_string_dup (info->content_type);
		ret_val->_buffer[i].size = info->size;

		CORBA_free (info);
	}
	g_list_free (cb_data.list);
	
 out:
	g_free (path_last);
	return ret_val;
}

static void
smem_erase_impl (PortableServer_Servant  servant,
		 const CORBA_char       *path,
		 CORBA_Environment      *ev)
{
	BonoboStorageMem       *storage;
	BonoboStorageMemEntry  *entry = NULL;
	BonoboStorageMem       *parent_storage;
	gchar                  *filename = NULL;

	storage = BONOBO_STORAGE_MEM (bonobo_object (servant));

	parent_storage = smem_get_parent (storage, path, &filename, &entry);
	if (!parent_storage) {
		bonobo_exception_set (ev, ex_Bonobo_Storage_NotFound);
		goto out;
	}

	if (entry->is_directory) {
		BonoboStorageMem *storage_to_remove =
			BONOBO_STORAGE_MEM (entry->child);
		
		/* You can't remove the root item */
		if (!strcmp (path, "/") || !strcmp (path, "")) {
			bonobo_exception_set (ev, ex_Bonobo_Storage_IOError);
			goto out;
		}

		/* Is the storage empty? */
		if (g_hash_table_size (storage_to_remove->priv->entries)) {
			bonobo_exception_set (ev, ex_Bonobo_Storage_NotEmpty);
			goto out;
		}
		g_hash_table_remove (parent_storage->priv->entries, filename);
		
	} else
		g_hash_table_remove (parent_storage->priv->entries,
				     filename);

 out:
	bonobo_storage_mem_entry_free (entry);
	g_free (filename);
}

static void
smem_rename_impl (PortableServer_Servant  servant,
		  const CORBA_char       *path,
		  const CORBA_char       *new_path,
		  CORBA_Environment      *ev)
{
	BonoboStorageMem      *storage;
	BonoboStorageMem      *parent_storage, *target_storage;
	BonoboStorageMemEntry *entry;
	gchar                 *filename = NULL, *new_filename;

	if (!strcmp (path, "/") || !strcmp (path, "")) {
		bonobo_exception_set (ev, ex_Bonobo_Storage_IOError);
		goto out;
	}

	storage = BONOBO_STORAGE_MEM (bonobo_object (servant));

	parent_storage = smem_get_parent (storage, path, &filename, &entry);
	target_storage = smem_get_last_storage (storage, new_path, &new_filename);

	/* Source or target does not exists */
	if (!parent_storage || !target_storage) {
		bonobo_exception_set (ev, ex_Bonobo_Storage_NotFound);
		goto out;
	}

	/* Target exists and is not a storage */
	if (new_filename && g_hash_table_lookup (target_storage->priv->entries,
						 new_filename)) {
		bonobo_exception_set (ev, ex_Bonobo_Storage_NameExists);
		goto out;
	}

	g_hash_table_remove (parent_storage->priv->entries, filename);
	
	/* If target does not exists, new_filename will be non-NULL */
	if (new_filename)
		g_hash_table_insert (target_storage->priv->entries,
				     new_filename, entry);
	else
		g_hash_table_insert (target_storage->priv->entries,
				     g_strdup (filename), entry);

 out:
	g_free (filename);
}

static void
smem_commit_impl (PortableServer_Servant  servant,
		  CORBA_Environment      *ev)
{
	bonobo_exception_set (ev, ex_Bonobo_Storage_NotSupported);
}

static void
smem_revert_impl (PortableServer_Servant  servant,
		  CORBA_Environment      *ev)
{
	bonobo_exception_set (ev, ex_Bonobo_Storage_NotSupported);
}

static void
bonobo_storage_mem_finalize (GObject *object)
{
	BonoboStorageMem *smem = BONOBO_STORAGE_MEM (object);

	if (smem->priv) {
		g_hash_table_destroy (smem->priv->entries);
		g_free (smem->priv);
		smem->priv = NULL;
	}
	
	G_OBJECT_CLASS (bonobo_storage_mem_parent_class)->finalize (object);
}

static void
bonobo_storage_mem_init (BonoboStorageMem *smem)
{
	smem->priv = g_new0 (BonoboStorageMemPriv, 1);

	smem->priv->entries = g_hash_table_new_full (
		g_str_hash, g_str_equal, g_free,
		bonobo_storage_mem_entry_free);
}

static void
bonobo_storage_mem_class_init (BonoboStorageMemClass *klass)
{
	GObjectClass *object_class = (GObjectClass *) klass;
	POA_Bonobo_Storage__epv *epv = &klass->epv;
	
	bonobo_storage_mem_parent_class = g_type_class_peek_parent (klass);

	object_class->finalize = bonobo_storage_mem_finalize;

	epv->getInfo       = smem_get_info_impl;
	epv->setInfo       = smem_set_info_impl;
	epv->listContents  = smem_list_contents_impl;
	epv->openStream    = smem_open_stream_impl;
	epv->openStorage   = smem_open_storage_impl;
	epv->copyTo = smem_copy_to_impl;
	epv->erase  = smem_erase_impl;
	epv->rename = smem_rename_impl;
	epv->commit = smem_commit_impl;
	epv->revert = smem_revert_impl;
}

BONOBO_TYPE_FUNC_FULL (BonoboStorageMem, 
		       Bonobo_Storage,
		       BONOBO_TYPE_OBJECT,
		       bonobo_storage_mem)

BonoboObject *
bonobo_storage_mem_create (void)
{
	BonoboStorageMem *smem;

	smem = g_object_new (bonobo_storage_mem_get_type (), NULL);
	
	if (!smem)
		return NULL;
	
	return BONOBO_STORAGE (smem);
}



syntax highlighted by Code2HTML, v. 0.9.1