/* 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 "jmmsp_cache.h"
#include "jmmsp_debug.h"
#include "util/util.h"
#include <sys/types.h>
#include <sys/time.h>
typedef struct _Entry
{
JMMSPReply* reply;
struct timeval time;
} Entry;
static void cache_add (JMMSPCache* cache, const JMMSPReply* reply);
static void cache_check (JMMSPCache* cache);
static void cache_query (JMMSPHandler* handler, JMMSP* jmmsp,
const gchar* keyword);
static void cache_reply (JMMSPHandler* handler, JMMSP* jmmsp,
const gchar* keyword, const JMMSPReply* reply);
static JMMSPHandlerFuncs cache_vtbl = { cache_query, cache_reply, NULL, NULL};
/* **************************************** */
JMMSPCache*
jmmsp_cache_new (void)
{
JMMSPCache* cache;
cache = g_new0 (JMMSPCache, 1);
jmmsp_handler_init (&cache->handler, &cache_vtbl);
cache->entries = NULL;
cache->entry_last = NULL;
cache->entry_set = g_hash_table_new (jmmsp_reply_hash, jmmsp_reply_equal);
cache->cache_size = 0;
cache->max_cache_size = JMMSP_CACHE_SIZE_DEFAULT;
cache->cache_timeout = JMMSP_CACHE_TIMEOUT_DEFAULT;
return cache;
}
void
jmmsp_cache_delete (JMMSPCache* cache)
{
if (!cache)
return;
/* Delete entries */
jmmsp_cache_clear (cache);
g_hash_table_destroy (cache->entry_set);
/* Remove handlers */
jmmsp_handler_uninit (&cache->handler);
g_free (cache);
}
void
jmmsp_cache_set_policy (JMMSPCache* cache, guint max_cache_size, guint cache_timeout)
{
g_return_if_fail (cache);
cache->max_cache_size = max_cache_size;
cache->cache_timeout = cache_timeout;
cache_check (cache);
}
void
jmmsp_cache_clear (JMMSPCache* cache)
{
GSList* i;
g_return_if_fail (cache);
for (i = cache->entries; i != NULL; i = i->next)
{
Entry* entry = (Entry*) i->data;
jmmsp_reply_delete (entry->reply);
g_free (entry);
}
g_slist_free (cache->entries);
cache->entries = NULL;
cache->entry_last = NULL;
g_hash_table_destroy (cache->entry_set);
cache->entry_set = g_hash_table_new (jmmsp_reply_hash, jmmsp_reply_equal);
cache->cache_size = 0;
}
/* **************************************** */
static void
cache_add (JMMSPCache* cache, const JMMSPReply* reply)
{
guint length;
Entry* entry;
GSList* new_list;
g_return_if_fail (cache);
g_return_if_fail (reply);
if (cache->max_cache_size <= 0)
return;
JMMSPP (0x02, "cache_add: %s\n", reply->name);
/* Skip if we already have it */
if (g_hash_table_lookup_extended(cache->entry_set, reply, NULL, NULL))
return;
length = jmmsp_reply_size (reply);
/* Ignore if too large */
if (length > JMMSP_CACHE_MAX_ENTRY_SIZE)
return;
entry = g_new (Entry, 1);
entry->reply = jmmsp_reply_clone (reply);
g_assert (gettimeofday(&entry->time, NULL) == 0);
/* Add to entry list */
new_list = g_slist_alloc();
new_list->data = entry;
if (cache->entry_last)
cache->entry_last->next = new_list;
else
cache->entries = new_list;
cache->entry_last = new_list;
g_hash_table_insert (cache->entry_set, entry->reply, NULL);
cache->cache_size += length;
cache_check (cache);
}
static void
cache_check (JMMSPCache* cache)
{
Entry* entry;
struct timeval timeofday;
/* If the cache is too big, remove entries until small enough */
while (cache->cache_size > cache->max_cache_size)
{
guint length;
g_return_if_fail (cache->entries);
entry = (Entry*) cache->entries->data;
length = jmmsp_reply_size (entry->reply);
JMMSPP (0x02, "cache_check: EVICT %s (size)\n", entry->reply->name);
cache->entries = g_slist_remove (cache->entries, entry);
if (!cache->entries)
cache->entry_last = NULL;
g_hash_table_remove (cache->entry_set, entry->reply);
cache->cache_size -= length;
jmmsp_reply_delete (entry->reply);
g_free (entry);
}
/* Remove entries that have expired. The cache is sorted, so this
is not expensive. */
if (cache->cache_timeout <= 0 || !cache->entries)
return;
g_assert (gettimeofday(&timeofday, NULL) == 0);
while (cache->entries != NULL)
{
struct timeval diff;
entry = (Entry*) cache->entries->data;
g_assert (entry != NULL);
timersub(&timeofday, &entry->time, &diff);
if ((diff.tv_sec * 1000 + diff.tv_usec / 1000) >
cache->cache_timeout)
{
guint length;
JMMSPP (0x02, "cache_check: EVICT %s (time)\n", entry->reply->name);
length = jmmsp_reply_size (entry->reply);
cache->entries = g_slist_remove (cache->entries, entry);
if (!cache->entries)
cache->entry_last = NULL;
g_hash_table_remove (cache->entry_set, entry->reply);
cache->cache_size -= length;
jmmsp_reply_delete (entry->reply);
g_free (entry);
}
else
break;
}
}
/* **************************************** */
static void
cache_query (JMMSPHandler* handler, JMMSP* jmmsp, const gchar* keyword)
{
JMMSPCache* cache = (JMMSPCache*) handler;
GSList* i;
int num = 0;
JMMSPP (0x02, "cache_query: %s\n", keyword);
/* Send REPLYs for things in cache that match */
for (i = cache->entries; i != NULL; i = i->next)
{
Entry* entry = (Entry*) i->data;
if (jmmsp_reply_matches(entry->reply, keyword))
{
/* Send REPLY */
jmmsp_send_reply (jmmsp, keyword, entry->reply, -1);
/* Count it. If sent enough, quit */
++num;
if (num >= JMMSP_CACHE_MAX_REPLIES)
return;
}
}
}
static void
cache_reply (JMMSPHandler* handler, JMMSP* jmmsp,
const gchar* keyword, const JMMSPReply* reply)
{
JMMSPCache* cache = (JMMSPCache*) handler;
JMMSPP (0x02, "cache_reply: %s\n", reply->name);
/* Add reply to cache */
cache_add (cache, reply);
}
syntax highlighted by Code2HTML, v. 0.9.1