/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* e-file-cache.c
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
* This library is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see .
*
* Authors: Rodrigo Moya
*/
/**
* SECTION: e-file-cache
* @short_description: Simple file-based hash table for strings
*
* An #EFileCache is a simple hash table of strings backed by an XML file
* for permanent storage. The XML file is written to disk with every unless
* the cache is temporarily frozen with e_file_cache_freeze_changes().
**/
#include
#include
#include
#include
#include
#include "e-file-cache.h"
#define E_FILE_CACHE_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), E_TYPE_FILE_CACHE, EFileCachePrivate))
struct _EFileCachePrivate {
gchar *filename;
EXmlHash *xml_hash;
gboolean dirty;
guint32 frozen;
};
enum {
PROP_0,
PROP_FILENAME
};
G_DEFINE_TYPE (EFileCache, e_file_cache, G_TYPE_OBJECT)
static void
file_cache_set_filename (EFileCache *cache,
const gchar *filename)
{
g_return_if_fail (filename != NULL);
g_return_if_fail (cache->priv->filename == NULL);
cache->priv->filename = g_strdup (filename);
}
static void
file_cache_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_FILENAME :
file_cache_set_filename (
E_FILE_CACHE (object),
g_value_get_string (value));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
file_cache_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_FILENAME :
g_value_set_string (
value,
e_file_cache_get_filename (
E_FILE_CACHE (object)));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
file_cache_finalize (GObject *object)
{
EFileCachePrivate *priv;
priv = E_FILE_CACHE_GET_PRIVATE (object);
g_free (priv->filename);
if (priv->xml_hash != NULL)
e_xmlhash_destroy (priv->xml_hash);
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (e_file_cache_parent_class)->finalize (object);
}
static void
file_cache_constructed (GObject *object)
{
EFileCache *cache;
const gchar *filename;
gchar *dirname;
cache = E_FILE_CACHE (object);
filename = e_file_cache_get_filename (cache);
/* Make sure the directory for the cache exists. */
dirname = g_path_get_dirname (filename);
g_mkdir_with_parents (dirname, 0700);
g_free (dirname);
cache->priv->xml_hash = e_xmlhash_new (filename);
/* If opening the cache file fails, remove it and try again. */
if (cache->priv->xml_hash == NULL) {
g_unlink (filename);
cache->priv->xml_hash = e_xmlhash_new (filename);
if (cache->priv->xml_hash == NULL)
g_warning (
"%s: could not re-create cache file %s",
G_STRFUNC, filename);
}
/* Chain up to parent's constructed() method. */
G_OBJECT_CLASS (e_file_cache_parent_class)->constructed (object);
}
static void
e_file_cache_class_init (EFileCacheClass *class)
{
GObjectClass *object_class;
g_type_class_add_private (class, sizeof (EFileCachePrivate));
object_class = G_OBJECT_CLASS (class);
object_class->set_property = file_cache_set_property;
object_class->get_property = file_cache_get_property;
object_class->finalize = file_cache_finalize;
object_class->constructed = file_cache_constructed;
/**
* EFileCache:filename
*
* The filename of the cache.
**/
g_object_class_install_property (
object_class,
PROP_FILENAME,
g_param_spec_string (
"filename",
"Filename",
"The filename of the cache",
"",
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
}
static void
e_file_cache_init (EFileCache *cache)
{
cache->priv = E_FILE_CACHE_GET_PRIVATE (cache);
}
/**
* e_file_cache_new
* @filename: filename where the cache is kept
*
* Creates a new #EFileCache object, which implements a cache of
* objects. Useful for remote backends.
*
* Returns: a new #EFileCache
*/
EFileCache *
e_file_cache_new (const gchar *filename)
{
g_return_val_if_fail (filename != NULL, NULL);
return g_object_new (E_TYPE_FILE_CACHE, "filename", filename, NULL);
}
/**
* e_file_cache_remove:
* @cache: an #EFileCache
*
* Remove the cache from disk.
*
* Returns: %TRUE if successful, %FALSE if a file error occurred
*/
gboolean
e_file_cache_remove (EFileCache *cache)
{
EFileCachePrivate *priv;
g_return_val_if_fail (E_IS_FILE_CACHE (cache), FALSE);
priv = cache->priv;
if (priv->filename) {
gchar *dirname, *full_path;
const gchar *fname;
GDir *dir;
gboolean success;
/* remove all files in the directory */
dirname = g_path_get_dirname (priv->filename);
dir = g_dir_open (dirname, 0, NULL);
if (dir) {
while ((fname = g_dir_read_name (dir))) {
full_path = g_build_filename (
dirname, fname, NULL);
if (g_unlink (full_path) != 0) {
g_free (full_path);
g_free (dirname);
g_dir_close (dir);
return FALSE;
}
g_free (full_path);
}
g_dir_close (dir);
}
/* remove the directory itself */
success = g_rmdir (dirname) == 0;
/* free all memory */
g_free (dirname);
g_free (priv->filename);
priv->filename = NULL;
e_xmlhash_destroy (priv->xml_hash);
priv->xml_hash = NULL;
return success;
}
return TRUE;
}
static void
add_key_to_slist (const gchar *key,
const gchar *value,
gpointer user_data)
{
GSList **keys = user_data;
*keys = g_slist_append (*keys, (gchar *) key);
}
/**
* e_file_cache_clean:
* @cache: an #EFileCache
*
* Clean up the cache's contents.
*
* Returns: %TRUE always
*/
gboolean
e_file_cache_clean (EFileCache *cache)
{
GSList *keys = NULL;
gboolean iFroze;
g_return_val_if_fail (E_IS_FILE_CACHE (cache), FALSE);
iFroze = !cache->priv->frozen;
if (iFroze)
e_file_cache_freeze_changes (cache);
e_xmlhash_foreach_key (
cache->priv->xml_hash,
(EXmlHashFunc) add_key_to_slist, &keys);
while (keys != NULL) {
e_file_cache_remove_object (cache, (const gchar *) keys->data);
keys = g_slist_remove (keys, keys->data);
}
if (iFroze)
e_file_cache_thaw_changes (cache);
return TRUE;
}
typedef struct {
const gchar *key;
gboolean found;
const gchar *found_value;
} CacheFindData;
static void
find_object_in_hash (gpointer key,
gpointer value,
gpointer user_data)
{
CacheFindData *find_data = user_data;
if (find_data->found)
return;
if (!strcmp (find_data->key, (const gchar *) key)) {
find_data->found = TRUE;
find_data->found_value = (const gchar *) value;
}
}
/**
* e_file_cache_get_object:
* @cache: an #EFileCache
* @key: the hash key of the object to find
*
* Returns the object corresponding to @key. If no such object exists
* in @cache, the function returns %NULL.
*
* Returns: the object corresponding to @key
*/
const gchar *
e_file_cache_get_object (EFileCache *cache,
const gchar *key)
{
CacheFindData find_data;
g_return_val_if_fail (E_IS_FILE_CACHE (cache), NULL);
g_return_val_if_fail (key != NULL, NULL);
find_data.key = key;
find_data.found = FALSE;
find_data.found_value = NULL;
e_xmlhash_foreach_key (
cache->priv->xml_hash,
(EXmlHashFunc) find_object_in_hash, &find_data);
return find_data.found_value;
}
static void
add_object_to_slist (const gchar *key,
const gchar *value,
gpointer user_data)
{
GSList **list = user_data;
*list = g_slist_prepend (*list, (gchar *) value);
}
/**
* e_file_cache_get_objects:
* @cache: an #EFileCache
*
* Returns a list of objects in @cache. The objects are owned by @cache and
* must not be modified or freed. Free the returned list with g_slist_free().
*
* Returns: a list of objects
*/
GSList *
e_file_cache_get_objects (EFileCache *cache)
{
GSList *list = NULL;
g_return_val_if_fail (E_IS_FILE_CACHE (cache), NULL);
e_xmlhash_foreach_key (
cache->priv->xml_hash,
(EXmlHashFunc) add_object_to_slist, &list);
return list;
}
/**
* e_file_cache_get_keys:
* @cache: an #EFileCache
*
* Returns a list of keys in @cache. The keys are owned by @cache and must
* not be modified or freed. Free the returned list with g_slist_free().
*
* Returns: a list of keys
*/
GSList *
e_file_cache_get_keys (EFileCache *cache)
{
GSList *list = NULL;
g_return_val_if_fail (E_IS_FILE_CACHE (cache), NULL);
e_xmlhash_foreach_key (
cache->priv->xml_hash,
(EXmlHashFunc) add_key_to_slist, &list);
return list;
}
/**
* e_file_cache_add_object:
* @cache: an #EFileCache
* @key: the hash key of the object to add
* @value: the object to add
*
* Adds a new @key / @value entry to @cache. If an object corresponding
* to @key already exists in @cache, the function returns %FALSE.
*
* Returns: %TRUE if successful, %FALSE if @key already exists
*/
gboolean
e_file_cache_add_object (EFileCache *cache,
const gchar *key,
const gchar *value)
{
g_return_val_if_fail (E_IS_FILE_CACHE (cache), FALSE);
g_return_val_if_fail (key != NULL, FALSE);
if (e_file_cache_get_object (cache, key))
return FALSE;
e_xmlhash_add (cache->priv->xml_hash, key, value);
if (cache->priv->frozen)
cache->priv->dirty = TRUE;
else {
e_xmlhash_write (cache->priv->xml_hash);
cache->priv->dirty = FALSE;
}
return TRUE;
}
/**
* e_file_cache_replace_object:
* @cache: an #EFileCache
* @key: the hash key of the object to replace
* @new_value: the new object for @key
*
* Replaces the object corresponding to @key with @new_value.
* If no such object exists in @cache, the function returns %FALSE.
*
* Returns: %TRUE if successful, %FALSE if @key was not found
*/
gboolean
e_file_cache_replace_object (EFileCache *cache,
const gchar *key,
const gchar *new_value)
{
g_return_val_if_fail (E_IS_FILE_CACHE (cache), FALSE);
g_return_val_if_fail (key != NULL, FALSE);
if (!e_file_cache_get_object (cache, key))
return FALSE;
if (!e_file_cache_remove_object (cache, key))
return FALSE;
return e_file_cache_add_object (cache, key, new_value);
}
/**
* e_file_cache_remove_object:
* @cache: an #EFileCache
* @key: the hash key of the object to remove
*
* Removes the object corresponding to @key from @cache.
* If no such object exists in @cache, the function returns %FALSE.
*
* Returns: %TRUE if successful, %FALSE if @key was not found
*/
gboolean
e_file_cache_remove_object (EFileCache *cache,
const gchar *key)
{
EFileCachePrivate *priv;
g_return_val_if_fail (E_IS_FILE_CACHE (cache), FALSE);
g_return_val_if_fail (key != NULL, FALSE);
priv = cache->priv;
if (!e_file_cache_get_object (cache, key))
return FALSE;
e_xmlhash_remove (priv->xml_hash, key);
if (priv->frozen)
priv->dirty = TRUE;
else {
e_xmlhash_write (priv->xml_hash);
priv->dirty = FALSE;
}
return TRUE;
}
/**
* e_file_cache_freeze_changes:
* @cache: an #EFileCache
*
* Disables temporarily all writes to disk for @cache.
*/
void
e_file_cache_freeze_changes (EFileCache *cache)
{
g_return_if_fail (E_IS_FILE_CACHE (cache));
cache->priv->frozen++;
g_return_if_fail (cache->priv->frozen > 0);
}
/**
* e_file_cache_thaw_changes:
* @cache: an #EFileCache
*
* Reverts the affects of e_file_cache_freeze_changes().
* Each change to @cache is once again written to disk.
*/
void
e_file_cache_thaw_changes (EFileCache *cache)
{
g_return_if_fail (E_IS_FILE_CACHE (cache));
g_return_if_fail (cache->priv->frozen > 0);
cache->priv->frozen--;
if (!cache->priv->frozen && cache->priv->dirty) {
e_xmlhash_write (cache->priv->xml_hash);
cache->priv->dirty = FALSE;
}
}
/**
* e_file_cache_get_filename:
* @cache: A %EFileCache object.
*
* Gets the name of the file where the cache is being stored.
*
* Returns: The name of the cache.
*/
const gchar *
e_file_cache_get_filename (EFileCache *cache)
{
g_return_val_if_fail (E_IS_FILE_CACHE (cache), NULL);
return cache->priv->filename;
}