/* 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 #include 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); }