/*
 * The contents of this file are subject to the AOLserver Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://aolserver.com/.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is AOLserver Code and related documentation
 * distributed by AOL.
 * 
 * The Initial Developer of the Original Code is America Online,
 * Inc. Portions created by AOL are Copyright (C) 1999 America Online,
 * Inc. All Rights Reserved.
 *
 * Alternatively, the contents of this file may be used under the terms
 * of the GNU General Public License (the "GPL"), in which case the
 * provisions of GPL are applicable instead of those above.  If you wish
 * to allow use of your version of this file only under the terms of the
 * GPL and not to allow others to use your version of this file under the
 * License, indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by the GPL.
 * If you do not delete the provisions above, a recipient may use your
 * version of this file under either the License or the GPL.
 */


/* 
 * cache.c --
 *
 *	Routines for a simple cache used by fastpath and Adp.
 */

static const char *RCSID = "@(#) $Header: /cvsroot/aolserver/aolserver/nsd/cache.c,v 1.14 2003/06/01 10:47:57 vasiljevic Exp $, compiled: " __DATE__ " " __TIME__;

#include "nsd.h"

struct Cache;

/*
 * An Entry is a node in a linked list as well as being a
 * hash table entry. The linked list is there to keep track of
 * usage for the purposes of cache pruning.
 */

typedef struct Entry {
    struct Entry *nextPtr;
    struct Entry *prevPtr;
    struct Cache *cachePtr;
    Tcl_HashEntry *hPtr;
    Ns_Time mtime;
    size_t size;
    void *value;
} Entry;

/*
 * The following structure defines a cache
 */

typedef struct Cache {
    Entry *firstEntryPtr;
    Entry *lastEntryPtr;
    Tcl_HashEntry *hPtr;
    int keys;
    time_t timeout;
    int schedId;
    int schedStop;
    size_t maxSize;
    size_t currentSize;
    Ns_Callback *freeProc;
    Ns_Mutex lock;
    Ns_Cond cond;
    unsigned int nhit;
    unsigned int nmiss;
    unsigned int nflush;
    Tcl_HashTable entriesTable;
    char    name[1];
} Cache;


/*
 * Local functions defined in this file
 */

static Ns_Cache * CacheCreate(char *name, int keys, time_t timeout,
			      size_t maxSize, Ns_Callback *freeProc);
static int GetCache(Tcl_Interp *interp, char *name, Cache **cachePtrPtr);
static void Delink(Entry *ePtr);
static void Push(Entry *ePtr);

/*
 * Static variables defined in this file
 */

static Tcl_HashTable caches;
static Ns_Mutex lock;


/*
 *----------------------------------------------------------------------
 *
 * NsInitCache --
 *
 *	Initialize the cache API.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
NsInitCache(void)
{
    Ns_MutexInit(&lock);
    Ns_MutexSetName(&lock, "ns:caches");
    Tcl_InitHashTable(&caches, TCL_STRING_KEYS);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CacheCreate --
 *
 *	Calls CacheCreate with an unlimited max size.
 *
 * Results:
 *	See CacheCreate()
 *
 * Side effects:
 *	See CacheCreate()
 *
 *----------------------------------------------------------------------
 */

Ns_Cache *
Ns_CacheCreate(char *name, int keys, time_t timeout, Ns_Callback *freeProc)
{
    return CacheCreate(name, keys, timeout, 0, freeProc);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CacheCreateSz --
 *
 *	See CacheCreate()
 *
 * Results:
 *	See CacheCreate()
 *
 * Side effects:
 *	See CacheCreate()
 *
 *----------------------------------------------------------------------
 */

Ns_Cache *
Ns_CacheCreateSz(char *name, int keys, size_t maxSize, Ns_Callback *freeProc)
{
    return CacheCreate(name, keys, -1, maxSize, freeProc);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CacheDestroy
 *
 *	Flush all entries and delete a cache.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Cache no longer usable.
 *
 *----------------------------------------------------------------------
 */

void
Ns_CacheDestroy(Ns_Cache *cache)
{
    Cache *cachePtr = (Cache *) cache;

    /*
     * Unschedule the flusher if time-based cache.
     */

    if (cachePtr->schedId >= 0) {
    	Ns_MutexLock(&cachePtr->lock);
	cachePtr->schedStop = 1;
    	if (Ns_Cancel(cachePtr->schedId)) {
	    cachePtr->schedId = -1;
	}

	/*
	 * Wait for currently running flusher to exit.
	 */

	while (cachePtr->schedId >= 0) {
	    Ns_CondWait(&cachePtr->cond, &cachePtr->lock);
	}
    	Ns_MutexUnlock(&cachePtr->lock);
    }

    /*
     * Flush all entries.
     */

    Ns_CacheFlush(cache);

    /*
     * Remove from cache table and free cache structure.
     */

    Ns_MutexLock(&lock);
    if (cachePtr->hPtr != NULL) {
    	Tcl_DeleteHashEntry(cachePtr->hPtr);
    }
    Ns_MutexUnlock(&lock);

    Ns_MutexDestroy(&cachePtr->lock);
    Ns_CondDestroy(&cachePtr->cond);
    Tcl_DeleteHashTable(&cachePtr->entriesTable);
    ns_free(cachePtr);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CacheFind --
 *
 *	Find a cache by name.
 *
 * Results:
 *	A pointer to an Ns_Cache or NULL 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

Ns_Cache *
Ns_CacheFind(char *name)
{
    Tcl_HashEntry *hPtr;
    Ns_Cache *cache;
    
    cache = NULL;
    Ns_MutexLock(&lock);
    hPtr = Tcl_FindHashEntry(&caches, name);
    if (hPtr != NULL) {
    	cache = (Ns_Cache *) Tcl_GetHashValue(hPtr);
    }
    Ns_MutexUnlock(&lock);
    return cache;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CacheMalloc --
 *
 *	Allocate memory from a cache-local pool. 
 *
 * Results:
 *	A pointer to new memory.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void *
Ns_CacheMalloc(Ns_Cache *cache, size_t len)
{
    return ns_malloc(len);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CacheFree --
 *
 *	Frees memory allocated from Ns_CacheMalloc 
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
Ns_CacheFree(Ns_Cache *cache, void *ptr)
{
    ns_free(ptr);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CacheFindEntry --
 *
 *	Find a cache entry 
 *
 * Results:
 *	A pointer to an Ns_Entry cache entry 
 *
 * Side effects:
 *	The cache entry will move to the top of the LRU list. 
 *
 *----------------------------------------------------------------------
 */

Ns_Entry *
Ns_CacheFindEntry(Ns_Cache *cache, char *key)
{
    Cache *cachePtr = (Cache *) cache;
    Tcl_HashEntry *hPtr;
    Entry *ePtr;

    hPtr = Tcl_FindHashEntry(&cachePtr->entriesTable, key);
    if (hPtr == NULL) {
	++cachePtr->nmiss;
	return NULL;
    }
    ++cachePtr->nhit;
    ePtr = Tcl_GetHashValue(hPtr);
    Delink(ePtr);
    Push(ePtr);
    
    return (Ns_Entry *) ePtr;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CacheCreateEntry --
 *
 *	Create a new cache entry; this function emulates 
 *	Tcl_CreateHashEntry's interface 
 *
 * Results:
 *	A pointer to a new cache entry 
 *
 * Side effects:
 *	Memory will be allocated for the new cache entry and it will 
 *	be inserted into the cache. 
 *
 *----------------------------------------------------------------------
 */

Ns_Entry *
Ns_CacheCreateEntry(Ns_Cache *cache, char *key, int *newPtr)
{
    Cache *cachePtr = (Cache *) cache;
    Tcl_HashEntry *hPtr;
    Entry *ePtr;

    hPtr = Tcl_CreateHashEntry(&cachePtr->entriesTable, key, newPtr);
    if (*newPtr == 0) {
	ePtr = Tcl_GetHashValue(hPtr);
    	Delink(ePtr);
	++cachePtr->nhit;
    } else {
	ePtr = ns_calloc(1, sizeof(Entry));
	ePtr->hPtr = hPtr;
	ePtr->cachePtr = cachePtr;
	Tcl_SetHashValue(hPtr, ePtr);
	++cachePtr->nmiss;
    }
    Push(ePtr);
    
    return (Ns_Entry *) ePtr;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CacheName --
 *
 *	Gets the name of the cache 
 *
 * Results:
 *	A pointer to a null-terminated string 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

char *
Ns_CacheName(Ns_Entry *entry)
{
    Entry *ePtr = (Entry *) entry;

    return ePtr->cachePtr->name;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CacheKey --
 *
 *	Gets the key of a cache entry 
 *
 * Results:
 *	A pointer to the key for the given entry 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

char *
Ns_CacheKey(Ns_Entry *entry)
{
    Entry *ePtr = (Entry *) entry;

    return Tcl_GetHashKey(&ePtr->cachePtr->entriesTable, ePtr->hPtr);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CacheGetValue --
 *
 *	Get the value (contents) of a cache entry 
 *
 * Results:
 *	A pointer to the cache entry's contents 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

void *
Ns_CacheGetValue(Ns_Entry *entry)
{
    Entry *ePtr = (Entry *) entry;

    return ePtr->value;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CacheSetValue --
 *
 *	Set the value of a cache entry 
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	See Ns_CacheSetValueSz 
 *
 *----------------------------------------------------------------------
 */

void
Ns_CacheSetValue(Ns_Entry *entry, void *value)
{
    Ns_CacheSetValueSz(entry, value, 0);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CacheSetValueSz --
 *
 *	Free the cache entry's previous contents, set it to the new 
 *	contents, increase the size of the cache, and prune until 
 *	it's back under the maximum size. 
 *
 * Results:
 *	None. 
 *
 * Side effects:
 *	Cache pruning and freeing of old contents may occur. 
 *
 *----------------------------------------------------------------------
 */

void
Ns_CacheSetValueSz(Ns_Entry *entry, void *value, size_t size)
{
    Entry *ePtr = (Entry *) entry;
    Cache *cachePtr = ePtr->cachePtr;

    Ns_CacheUnsetValue(entry);
    ePtr->value = value;
    ePtr->size = size;
    cachePtr->currentSize += size;
    if (ePtr->cachePtr->maxSize > 0) {
	while (cachePtr->currentSize > cachePtr->maxSize &&
	    cachePtr->lastEntryPtr != ePtr) {
	    Ns_CacheFlushEntry((Ns_Entry *) cachePtr->lastEntryPtr);
	}
    }
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CacheUnsetValue --
 *
 *	Reset the value of an entry to NULL, calling the free proc for
 *  	any previous entry an updating the cache size.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
Ns_CacheUnsetValue(Ns_Entry *entry)
{
    Entry *ePtr = (Entry *) entry;
    Cache *cachePtr;
 
    if (ePtr->value != NULL) {
	cachePtr = ePtr->cachePtr;
	cachePtr->currentSize -= ePtr->size;
	if (cachePtr->freeProc == NS_CACHE_FREE) {
	    Ns_CacheFree((Ns_Cache *) cachePtr, ePtr->value);
	} else if (cachePtr->freeProc != NULL) {
    	    (*cachePtr->freeProc)(ePtr->value);
	}
	ePtr->size = 0;
	ePtr->value = NULL;
    }
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CacheDeleteEntry --
 *
 *	Delete an entry from the cache table.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
Ns_CacheDeleteEntry(Ns_Entry *entry)
{
    Entry *ePtr = (Entry *) entry;

    Delink(ePtr);
    Tcl_DeleteHashEntry(ePtr->hPtr);
    ns_free(ePtr);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CacheFlushEntry --
 *
 *	Delete an entry from the cache table after first unsetting
 *  	the current entry value (if any).
 *
 * Results:
 *      None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
Ns_CacheFlushEntry(Ns_Entry *entry)
{
    Entry *ePtr = (Entry *) entry;

    ++ePtr->cachePtr->nflush;
    Ns_CacheUnsetValue(entry);
    Ns_CacheDeleteEntry(entry);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CacheFirstEntry --
 *
 *	Return a pointer to the first entry in the cache (in no 
 *	particular order) 
 *
 * Results:
 *	A pointer to said entry. 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

Ns_Entry *
Ns_CacheFirstEntry(Ns_Cache *cache, Ns_CacheSearch *search)
{
    Tcl_HashSearch *sPtr = (Tcl_HashSearch *) search;
    Cache *cachePtr = (Cache *) cache;
    Tcl_HashEntry *hPtr;

    hPtr = Tcl_FirstHashEntry(&cachePtr->entriesTable, sPtr);
    if (hPtr == NULL) {
	return NULL;
    }
    return (Ns_Entry *) Tcl_GetHashValue(hPtr);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CacheNextEntry --
 *
 *	When used in conjunction with Ns_CacheFirstEntry, one may 
 *	walk through the whole cache. 
 *
 * Results:
 *	NULL or a pointer to an entry 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

Ns_Entry *
Ns_CacheNextEntry(Ns_CacheSearch *search)
{
    Tcl_HashSearch *sPtr = (Tcl_HashSearch *) search;
    Tcl_HashEntry *hPtr;

    hPtr = Tcl_NextHashEntry(sPtr);
    if (hPtr == NULL) {
	return NULL;
    }
    return (Ns_Entry *) Tcl_GetHashValue(hPtr);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CacheFlush --
 *
 *	Flush every entry from a cache.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
Ns_CacheFlush(Ns_Cache *cache)
{
    Ns_CacheSearch search;
    Ns_Entry *entry;

    entry = Ns_CacheFirstEntry(cache, &search);
    while (entry != NULL) {
	Ns_CacheFlushEntry(entry);
	entry = Ns_CacheNextEntry(&search);
    }
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CacheLock --
 *
 *	Lock the cache
 *
 * Results:
 *      None.
 *
 * Side effects:
 *	Mutex locked.
 *
 *----------------------------------------------------------------------
 */

void
Ns_CacheLock(Ns_Cache *cache)
{
    Cache *cachePtr = (Cache *) cache;

    Ns_MutexLock(&cachePtr->lock);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CacheTryLock --
 *
 *	Try to lock the cache.
 *
 * Results:
 *      NS_OK if cache is locked, NS_TIMEOUT if not.
 *
 * Side effects:
 *	Mutex may eventually be locked.
 *
 *----------------------------------------------------------------------
 */

int
Ns_CacheTryLock(Ns_Cache *cache)
{
    Cache *cachePtr = (Cache *) cache;

    return Ns_MutexTryLock(&cachePtr->lock);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CacheUnlock --
 *
 *	Unlock the cache entry
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Mutex unlocked.
 *
 *----------------------------------------------------------------------
 */

void
Ns_CacheUnlock(Ns_Cache *cache)
{
    Cache *cachePtr = (Cache *) cache;

    Ns_MutexUnlock(&cachePtr->lock);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CacheTimedWait --
 *
 *	Wait for the cache's condition variable to be
 *  	signaled or the given absolute timeout if timePtr is not NULL.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Thread is suspended until condition is signaled or timeout.
 *
 *----------------------------------------------------------------------
 */

int
Ns_CacheTimedWait(Ns_Cache *cache, Ns_Time *timePtr)
{
    Cache *cachePtr = (Cache *) cache;
    
    return Ns_CondTimedWait(&cachePtr->cond, &cachePtr->lock, timePtr);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CacheWait --
 *
 *	Wait indefinitely for the cache's condition variable to be
 *  	signaled.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Thread is suspended until condition is signaled.
 *
 *----------------------------------------------------------------------
 */

void
Ns_CacheWait(Ns_Cache *cache)
{
    Ns_CacheTimedWait(cache, NULL);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CacheSignal --
 *
 *	Signal the cache's condition variable, waking the first waiting
 *  	thread (if any).
 *
 *  	NOTE:  Be sure you don't really want to wake all threads with
 *  	Ns_CacheBroadcast.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	A single thread may resume.
 *
 *----------------------------------------------------------------------
 */

void
Ns_CacheSignal(Ns_Cache *cache)
{
    Cache *cachePtr = (Cache *) cache;
    
    Ns_CondSignal(&cachePtr->cond);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CacheBroadcast --
 *
 *	Broadcast the cache's condition variable, waking all waiting
 *  	threads (if any).
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Threads may resume.
 *
 *----------------------------------------------------------------------
 */
    
void
Ns_CacheBroadcast(Ns_Cache *cache)
{
    Cache *cachePtr = (Cache *) cache;
    
    Ns_CondBroadcast(&cachePtr->cond);
}


/*
 *----------------------------------------------------------------------
 *
 * NsCacheArgProc --
 *
 *	Info proc for timed cache schedule procedure callback.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Adds name of cache to given dstring.
 *
 *----------------------------------------------------------------------
 */

void
NsCacheArgProc(Tcl_DString *dsPtr, void *arg)
{
    Cache *cachePtr = arg;

    Tcl_DStringAppendElement(dsPtr, cachePtr->name);
}


/*
 *----------------------------------------------------------------------
 *
 * NsTclNamesCmd --
 *
 *	Spit back a list of cache names
 *
 * Results:
 *	Tcl result.
 *
 * Side effects:
 *	A list of cache names will be appended to the interp result.
 *
 *----------------------------------------------------------------------
 */

int
NsTclCacheNamesCmd(ClientData dummy, Tcl_Interp *interp, int argc, char **argv)
{
    Tcl_HashEntry *hPtr;
    Tcl_HashSearch search;

    if (argc != 1) {
	Tcl_AppendResult(interp, "wrong # args: should be \"",
	    argv[0], "\"", NULL);
	return TCL_ERROR;
    }

    Ns_MutexLock(&lock);
    hPtr = Tcl_FirstHashEntry(&caches, &search);
    while (hPtr != NULL) {
	Tcl_AppendElement(interp, Tcl_GetHashKey(&caches, hPtr));
	hPtr = Tcl_NextHashEntry(&search);
    }
    Ns_MutexUnlock(&lock);

    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * NsTclCacheStatsCmds --
 *
 *	Returns stats on a cache
 *
 * Results:
 *	Tcl result.
 *
 * Side effects:
 *	Results will be appended to interp.
 *
 *----------------------------------------------------------------------
 */

int
NsTclCacheStatsCmd(ClientData dummy, Tcl_Interp *interp, int argc, char **argv)
{
    Cache *cachePtr;
    char buf[200];
    int entries, flushed, hits, misses, total, hitrate;

    if (argc != 2 && argc != 3) {
	Tcl_AppendResult(interp, "wrong # args: should be \"",
	    argv[0], " cache ?arrayVar?\"", NULL);
	return TCL_ERROR;
    }
    if (GetCache(interp, argv[1], &cachePtr) != TCL_OK) {
    	return TCL_ERROR;
    }
    Ns_MutexLock(&cachePtr->lock);
    entries = cachePtr->entriesTable.numEntries;
    flushed = cachePtr->nflush;
    hits = cachePtr->nhit;
    misses = cachePtr->nmiss;
    total = cachePtr->nhit + cachePtr->nmiss;
    hitrate = (total ? (cachePtr->nhit * 100) / total : 0);
    Ns_MutexUnlock(&cachePtr->lock);

    if (argc == 2) {
	sprintf(buf,
	    "entries: %d  flushed: %d  hits: %d  misses: %d  hitrate: %d",
	    entries, flushed, hits, misses, hitrate);
	Tcl_SetResult(interp, buf, TCL_VOLATILE);
    } else {
    	sprintf(buf, "%d", entries);
    	if (Tcl_SetVar2(interp, argv[2], "entries", buf,
			TCL_LEAVE_ERR_MSG) == NULL) {
	    return TCL_ERROR;
        }
    	sprintf(buf, "%d", flushed);
    	if (Tcl_SetVar2(interp, argv[2], "flushed", buf,
			TCL_LEAVE_ERR_MSG) == NULL) {
	    return TCL_ERROR;
        }
    	sprintf(buf, "%d", hits);
    	if (Tcl_SetVar2(interp, argv[2], "hits", buf,
			TCL_LEAVE_ERR_MSG) == NULL) {
	    return TCL_ERROR;
        }
    	sprintf(buf, "%d", misses);
    	if (Tcl_SetVar2(interp, argv[2], "misses", buf,
			TCL_LEAVE_ERR_MSG) == NULL) {
	    return TCL_ERROR;
        }
    	sprintf(buf, "%d", hitrate);
    	if (Tcl_SetVar2(interp, argv[2], "hitrate", buf,
			TCL_LEAVE_ERR_MSG) == NULL) {
	    return TCL_ERROR;
        }
    }

    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * NsTclCacheFlushCmd --
 *
 *	Wipe out a cache entry
 *
 * Results:
 *	TCL result.
 *
 * Side effects:
 *	A cache will be cleared.
 *
 *----------------------------------------------------------------------
 */

int
NsTclCacheFlushCmd(ClientData dummy, Tcl_Interp *interp, int argc, char **argv)
{
    Cache *cachePtr;
    Ns_Cache *cache;
    Ns_Entry *entry;
    
    if (argc != 2 && argc != 3) {
	Tcl_AppendResult(interp, "wrong # args: should be \"",
	    argv[0], " cache ?key?\"", NULL);
	return TCL_ERROR;
    }
    if (GetCache(interp, argv[1], &cachePtr) != TCL_OK) {
    	return TCL_ERROR;
    }
    if (argc > 2 && cachePtr->keys != TCL_STRING_KEYS) {
	Tcl_AppendResult(interp, "cache keys not strings: ",
	    argv[1], NULL);
	return TCL_ERROR;
    }
    cache = (Ns_Cache *) cachePtr;
    Ns_CacheLock(cache);
    if (argc == 2) {
	Ns_CacheFlush(cache);
    } else {
    	entry = Ns_CacheFindEntry(cache, argv[2]);
    	if (entry == NULL) {
    	    Tcl_SetResult(interp, "0", TCL_STATIC);
	} else {
    	    Tcl_SetResult(interp, "1", TCL_STATIC);
	    Ns_CacheFlushEntry(entry);
    	}
    }
    Ns_CacheUnlock(cache);
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * NsTclCacheSizeCmd --
 *
 *	Returns current size of a cache.
 *
 * Results:
 *	Tcl result.
 *
 * Side effects:
 *	Results will be appended to interp.
 *
 *----------------------------------------------------------------------
 */

int
NsTclCacheSizeCmd(ClientData dummy, Tcl_Interp *interp, int argc, char **argv)
{
    Cache *cachePtr;
    size_t maxSize, currentSize;
    char buf[200];
    
    if (argc != 2) {
	Tcl_AppendResult(interp, "wrong # args: should be \"",
	    argv[0], " cache\"", NULL);
	return TCL_ERROR;
    }
    if (GetCache(interp, argv[1], &cachePtr) != TCL_OK) {
    	return TCL_ERROR;
    }
    Ns_MutexLock(&cachePtr->lock);
    maxSize = cachePtr->maxSize;
    currentSize = cachePtr->currentSize;
    Ns_MutexUnlock(&cachePtr->lock);
    sprintf(buf, "%ld %ld", (long) maxSize, (long) currentSize);
    Tcl_SetResult(interp, buf, TCL_VOLATILE);
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * NsTclCacheKeysCmd --
 *
 *	Get cache keys.
 *
 * Results:
 *	TCL result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
NsTclCacheKeysCmd(ClientData dummy, Tcl_Interp *interp, int argc, char **argv)
{
    Ns_Cache *cache;
    Cache *cachePtr;
    Ns_Entry *entry;
    Ns_CacheSearch search;
    char *pattern, *key, *fmt, onebuf[20];
    int i, *iPtr;
    Ns_DString ds;
    
    if (argc != 2 && argc != 3) {
	Tcl_AppendResult(interp, "wrong # args: should be \"",
	    argv[0], " cache ?pattern?\"", NULL);
	return TCL_ERROR;
    }
    pattern = argv[2];
    if (GetCache(interp, argv[1], &cachePtr) != TCL_OK) {
	return TCL_ERROR;
    }
    Ns_DStringInit(&ds);
    cache = (Ns_Cache *) cachePtr;
    Ns_CacheLock(cache);
    entry = Ns_CacheFirstEntry(cache, &search);
    while (entry != NULL) {
	key = Ns_CacheKey(entry);
	if (cachePtr->keys == TCL_ONE_WORD_KEYS) {
	    sprintf(onebuf, "%p", key);
	    key = onebuf;
	} else if (cachePtr->keys != TCL_STRING_KEYS) {
	    iPtr = (int *) key;
	    fmt = "%u";
	    Ns_DStringTrunc(&ds, 0);
	    for (i = 0; i < cachePtr->keys; ++i) {
		Ns_DStringPrintf(&ds, fmt, *iPtr);
		++iPtr;
		fmt = ".%u";
	    }
	    key = ds.string;
	}
	if (pattern == NULL || Tcl_StringMatch(key, pattern)) {
	    Tcl_AppendElement(interp, key);
	}
	entry = Ns_CacheNextEntry(&search);
    }
    Ns_CacheUnlock(cache);
    Ns_DStringFree(&ds);
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * CacheCreate --
 *
 *	Create a new time or size based cache.
 *
 * Results:
 *	A pointer to the new cache.
 *
 * Side effects:
 *	Hash table is allocated, new pool created. A scheduled proc is
 *      created that will run every timeout seconds to flush the cache
 *  	if timeout is greater than zero.
 *
 *----------------------------------------------------------------------
 */

static Ns_Cache *
CacheCreate(char *name, int keys, time_t timeout, size_t maxSize,
	    Ns_Callback *freeProc)
{
    Cache *cachePtr;
    int new;

    cachePtr = ns_calloc(1, sizeof(Cache) + strlen(name));
    cachePtr->freeProc = freeProc;
    cachePtr->timeout = timeout;
    cachePtr->maxSize = maxSize;
    cachePtr->currentSize = 0;
    cachePtr->keys = keys;
    strcpy(cachePtr->name, name);
    cachePtr->nflush = cachePtr->nhit = cachePtr->nmiss = 0;
    Ns_MutexSetName2(&cachePtr->lock, "ns:cache", name);
    Tcl_InitHashTable(&cachePtr->entriesTable, keys);
    if (timeout > 0) {
    	cachePtr->schedId = Ns_ScheduleProc(NsCachePurge, cachePtr, 0, timeout);
    } else {
    	cachePtr->schedId = -1;
    }
    cachePtr->schedStop = 0;
    Ns_MutexLock(&lock);
    cachePtr->hPtr = Tcl_CreateHashEntry(&caches, name, &new);
    if (!new) {
	Cache *prevPtr;

	Ns_Log(Warning, "cache: duplicate cache name: %s", name);
	prevPtr = Tcl_GetHashValue(cachePtr->hPtr);
	prevPtr->hPtr = NULL;
    }
    Tcl_SetHashValue(cachePtr->hPtr, cachePtr);
    Ns_MutexUnlock(&lock);
    return (Ns_Cache *) cachePtr;
}


/*
 *----------------------------------------------------------------------
 *
 * Delink --
 *
 *	Remove a cache entry from the linked list of entries; this
 *      is used for maintaining the LRU list as well as removing entries
 *      that are still in use
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The linked list will be changed.
 *
 *----------------------------------------------------------------------
 */
 
static void
Delink(Entry *ePtr)
{
    if (ePtr->prevPtr != NULL) {
	ePtr->prevPtr->nextPtr = ePtr->nextPtr;
    } else {
	ePtr->cachePtr->firstEntryPtr = ePtr->nextPtr;
    }
    if (ePtr->nextPtr != NULL) {
	ePtr->nextPtr->prevPtr = ePtr->prevPtr;
    } else {
	ePtr->cachePtr->lastEntryPtr = ePtr->prevPtr;
    }
    ePtr->prevPtr = ePtr->nextPtr = NULL;
}


/*
 *----------------------------------------------------------------------
 *
 * Push --
 *
 *	Stick an entry at the top of the linked list of entries, making
 *      it the Most Recently Used
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The linked list will be changed and the mtime time will be
 *	updated for time-based caches.
 *
 *----------------------------------------------------------------------
 */

static void
Push(Entry *ePtr)
{
    if (ePtr->cachePtr->timeout > 0) {
	Ns_GetTime(&ePtr->mtime);
    }
    if (ePtr->cachePtr->firstEntryPtr != NULL) {
	ePtr->cachePtr->firstEntryPtr->prevPtr = ePtr;
    }
    ePtr->prevPtr = NULL;
    ePtr->nextPtr = ePtr->cachePtr->firstEntryPtr;
    ePtr->cachePtr->firstEntryPtr = ePtr;
    if (ePtr->cachePtr->lastEntryPtr == NULL) {
	ePtr->cachePtr->lastEntryPtr = ePtr;
    }
}


/*
 *----------------------------------------------------------------------
 *
 * GetCache --
 *
 *	Get a cache by name
 *
 * Results:
 *	Tcl result.
 *
 * Side effects:
 *	*cachePtrPtr will contain a pointer to the cache if TCL_OK is
 *      returned.
 *
 *----------------------------------------------------------------------
 */

static int
GetCache(Tcl_Interp *interp, char *name, Cache **cachePtrPtr)
{
    *cachePtrPtr = (Cache *) Ns_CacheFind(name);
    if (*cachePtrPtr == NULL) {
	Tcl_AppendResult(interp, "no such cache: ", name, NULL);
	return TCL_ERROR;
    }

    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * NsCachePurge --
 *
 *	Call free procs for all entries that have expired and
 *  	delete them.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Expired entries will be removed.
 *
 *----------------------------------------------------------------------
 */

void
NsCachePurge(void *arg)
{
    Entry *ePtr;
    Cache *cachePtr = (Cache *) arg;
    Ns_Time expired;

    Ns_MutexLock(&cachePtr->lock);
    if (cachePtr->schedStop) {
	cachePtr->schedId = -1;
	Ns_CondBroadcast(&cachePtr->cond);
    } else {
	Ns_GetTime(&expired);
	Ns_IncrTime(&expired, -cachePtr->timeout, 0);
	while ((ePtr = cachePtr->lastEntryPtr) != NULL) {
	    if (ePtr->mtime.sec > expired.sec) {
		break;
	    }
	    if (ePtr->mtime.sec == expired.sec
		    && ePtr->mtime.usec > expired.usec) {
		break;
	    }
	    Ns_CacheFlushEntry((Ns_Entry *) ePtr);
    	}
    }
    Ns_MutexUnlock(&cachePtr->lock);
}
