#ifndef lint static char rcsId[]="$Header: /usr/local/rcs/Newt/XmHTML/RCS/cache.c,v 1.2 1997/08/30 01:47:16 newt Exp newt $"; #endif /***** * cache.c : Generic caching routines. * * This file Version $Revision: 1.2 $ * * Creation date: Fri May 23 14:42:10 GMT+0100 1997 * Last modification: $Date: 1997/08/30 01:47:16 $ * By: $Author: newt $ * Current State: $State: Exp $ * * Author: newt * * Copyright (C) 1994-1997 by Ripley Software Development * All Rights Reserved * * This file is part of the XmHTML Widget Library * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * *****/ /***** * ChangeLog * $Log: cache.c,v $ * Revision 1.2 1997/08/30 01:47:16 newt * getObjectFromCache now takes two args: file and url. Fixed a few potential * misalignment bugs when an url mapping goes bad. * * Revision 1.1 1997/05/28 02:01:28 newt * Initial Revision * *****/ #include #include #include #include #include "../src/debug.h" /* we want to use XmHTML's debugging features */ #include "cache.h" /*** External Function Prototype Declarations ***/ /*** Public Variable Declarations ***/ /*** Private Datatype Declarations ****/ /* cache object */ typedef struct _CacheObject{ XtPointer object; /* object data */ String file; /* location of object on local file system */ int ref_count; /* reference count for this entry */ int map_count; /* no of mappings to this entry */ int size; /* size of this object */ struct _CacheObject *prev; /* previous entry */ struct _CacheObject *next; /* next entry */ }CacheObject; /* mapping between object urls and the object cache */ typedef struct _objectMapping{ String url; /* url referencing this object */ int ref_count; /* reference counter for this mapping */ CacheObject *cache_entry; /* ptr. to cache entry */ struct _objectMapping *prev; /* previous entry */ struct _objectMapping *next; /* next entry */ }ObjectMapping; /*** Private Function Prototype Declarations ****/ static void addObjectMapping(CacheObject *cache_entry, String url); static void discardCacheEntry(CacheObject *cache_entry); static void discardMapEntry(ObjectMapping *cache_entry); static ObjectMapping *newMap(ObjectMapping *map, ObjectMapping *prev_map, CacheObject *cache_entry, String url); static CacheObject *newEntry(CacheObject *entry, CacheObject *prev_entry, XtPointer object, String file, String url); /*** Private Variable Declarations ***/ /* initial cache size in bytes: 512 kb */ #define DEFAULT_CACHE_SIZE 524288 static int cacheSize = DEFAULT_CACHE_SIZE; static int currentCacheSize = 0; /* used size of the cache */ static CacheObject *objectCache; /* actual object cache */ static ObjectMapping *objectMapping; /* url->file mapping */ static cleanObjectProc cleanProc = NULL; /* clean-up function */ static sizeObjectProc sizeProc = NULL; /* object size function */ static XtPointer clientData; /* client_data for external funcs */ /****** *** Private functions ******/ /***** * Name: newMap * Return Type: ObjectMapping* * Description: add a new entry to the chain of url->cache_entry mappings * In: * map: parent map; * prev_map: previous map; * cache_e..: cache entry referenced; * url: url used to reference cache_entry * Returns: * a newly created map entry. *****/ static ObjectMapping* newMap(ObjectMapping *map, ObjectMapping *prev_map, CacheObject *cache_entry, String url) { if(map == NULL) { map = (ObjectMapping*)malloc(sizeof(ObjectMapping)); map->url = strdup(url); map->cache_entry = cache_entry; map->next = NULL; map->prev = prev_map; map->ref_count = 1; cache_entry->map_count++; _XmHTMLDebug(17, ("cache.c, newMap: adding map entry:\n" "\tFrom: %s\n\tTo : %s\n", url, cache_entry->file)); } else map->next = newMap(map->next, map, cache_entry, url); return(map); } /***** * Name: newEntry * Return Type: CacheObject* * Description: add a new entry to the cache * In: * entry: parent entry; * prev_en..: previous entry; * object: data to be stored; * file: location on the local file system; * url: url used to reference this object; * Returns: * *****/ static CacheObject* newEntry(CacheObject *entry, CacheObject *prev_entry, XtPointer object, String file, String url) { if(entry == NULL) { /* entry does not yet exists, add one */ entry = (CacheObject*)malloc(sizeof(CacheObject)); entry->object = object; entry->file = strdup(file); entry->next = NULL; entry->prev = prev_entry; entry->ref_count = 1; entry->map_count = 0; entry->size = sizeof(CacheObject) + strlen(file); if(sizeProc) entry->size += sizeProc(object, clientData); currentCacheSize += entry->size; _XmHTMLDebug(17, ("cache.c, newEntry: adding cache entry for object:\n" "\t%s\n", file)); _XmHTMLDebug(17, ("\tcache size: %i (out of %i max.)\n", currentCacheSize, cacheSize)); /* and add a url mapping for this one as well */ addObjectMapping(entry, url); } else entry->next = newEntry(entry->next, entry, object, file, url); return(entry); } /***** * Name: addObjectMapping * Return Type: void * Description: add a mapping between the given cache item and url * In: * cache_e..: cache entry referenced; * url: url used to reference cache_entry * Returns: * nothing, but updates the overall objectMappings. *****/ static void addObjectMapping(CacheObject *cache_entry, String url) { ObjectMapping *tmp; /* see if this mapping has already been added */ for(tmp = objectMapping; tmp != NULL && strcmp(tmp->url, url); tmp = tmp->next); /* entry already exists, return */ if(tmp) { _XmHTMLDebug(17, ("cache.c, addObjectMapping, incrementing reference " "count for mapping\n\t%s to %i\n", url, tmp->ref_count+1)); tmp->ref_count++; cache_entry->map_count++; } else objectMapping = newMap(objectMapping, NULL, cache_entry, url); } /***** * Name: discardCacheEntry * Return Type: void * Description: Remove the given object cache entry from the object cache. * Does not delete the entry but removes it from the linked list. * In: * cache_e..: entry to be removed; * Returns: * nothing. *****/ static void discardCacheEntry(CacheObject *cache_entry) { CacheObject *tmp; /* special case if cache_entry is the first entry in the object cache */ if(objectCache == cache_entry) { objectCache = objectCache->next; /* can happen if this is the last item in the list */ if(objectCache != NULL) objectCache->prev = NULL; /* invalidate previous entry */ /* update cache size */ currentCacheSize -= cache_entry->size; _XmHTMLDebug(17, ("\tcache size: %i (out of %i max.)\n", currentCacheSize, cacheSize)); return; } for(tmp = objectCache; tmp != NULL; tmp = tmp->next) { if(tmp == cache_entry) { CacheObject *next, *prev; /* update cache size */ currentCacheSize -= tmp->size; _XmHTMLDebug(17, ("\tcache size: %i (out of %i max.)\n", currentCacheSize, cacheSize)); /* unhook this entry */ next = tmp->next; /* ptr to next element */ prev = tmp->prev; /* ptr to previous element */ if(tmp->prev) tmp->prev->next = next; if(tmp->next) tmp->next->prev = prev; return; } } } /***** * Name: discardMapEntry * Return Type: void * Description: same as discardCacheEntry but for the mappings * In: * map_e..: mapping to be removed; * Returns: * nothing. *****/ static void discardMapEntry(ObjectMapping *map_entry) { ObjectMapping *tmp; /* special case if map_entry is the first entry in the object cache */ if(objectMapping == map_entry) { objectMapping = objectMapping->next; /* can happen if this is the last item in the list */ if(objectMapping != NULL) objectMapping->prev = NULL; /* invalidate previous entry */ return; } for(tmp = objectMapping; tmp != NULL; tmp = tmp->next) { if(tmp == map_entry) { ObjectMapping *prev, *next; /* unhook this entry */ next = tmp->next; /* ptr to next element */ prev = tmp->prev; /* ptr to previous element */ if(tmp->prev) tmp->prev->next = next; if(tmp->next) tmp->next->prev = prev; return; } } } /****** *** Public functions ******/ /***** * Name: getURLObjectFromCache * Return Type: XtPointer * Description: get an object from the objectCache, given it's url * In: * url: url for which to retrieve a cached object. * Returns: * the cached object if it exists or NULL of it doesn't. *****/ XtPointer getURLObjectFromCache(String url) { ObjectMapping *tmp; if(url == NULL) return(NULL); for(tmp = objectMapping; tmp != NULL && strcmp(tmp->url, url); tmp = tmp->next); if(tmp) { _XmHTMLDebug(17, ("cache.c, getURLObjectFromCache, found mapping:\n" "\tFrom: %s\n\tTo : %s\n", url, tmp->cache_entry->file)); return(tmp->cache_entry->object); } _XmHTMLDebug(17, ("cache.c, getURLObjectFromCache, no mapping found " "for URL:\n\t%s\n", url)); return(NULL); } /***** * Name: getObjectFromCache * Return Type: XtPointer * Description: get an object from the objectcache, given it's location * In: * file: file name of a cached object. * Returns: * the cached object if it exists or NULL of it doesn't. *****/ XtPointer getObjectFromCache(String file, String url) { CacheObject *tmp; /* no filename given, try matching with url */ if(file == NULL) return(getURLObjectFromCache(url)); /* filename given, try with filename */ for(tmp = objectCache; tmp != NULL && strcmp(tmp->file, file); tmp = tmp->next); if(tmp) { _XmHTMLDebug(17, ("cache.c: getObjectFromCache, cache entry found for " "object:\n\t%s\n", file)); return(tmp->object); } _XmHTMLDebug(17, ("cache.c: getObjectFromCache, NO cache entry found for " "object:\n\t%s\n", file)); return(NULL); } /***** * Name: storeObjectInCache * Return Type: void * Description: insert a new entry in the object cache * In: * object: object to be cached; * file: location on local file system; primary object identifier; * url: url referencing object to be cached; secondary object * indentifier. * Returns: * nothing. *****/ void storeObjectInCache(XtPointer object, String file, String url) { CacheObject *tmp; /* check if this file is already stored */ for(tmp = objectCache; tmp != NULL && strcmp(tmp->file, file); tmp = tmp->next); /* entry already exists, check if we need to update the mapping */ if(tmp != NULL) { _XmHTMLDebug(17, ("cache.c, storeObjectInCache, incrementing reference" " count for object\n\t%s to %i\n", url, tmp->ref_count+1)); /* increment reference counter for this object */ tmp->ref_count++; /* add a new mapping for this object as well */ addObjectMapping(tmp, url); } else /* add a new cache item */ objectCache = newEntry(objectCache, NULL, object, file, url); } /***** * Name: replaceObjectInCache * Return Type: void * Description: replace an entry in the object cache with the newly given data * In: * old_object: object to be replaced; * new_object: new data to be stored at the old object's location. * Returns: * nothing. *****/ void replaceObjectInCache(XtPointer old_object, XtPointer new_object) { CacheObject *tmp; /* locate original object and replace with new object data */ for(tmp = objectCache; tmp != NULL && tmp->object != old_object; tmp = tmp->next); /* found it, replace with new data */ if(tmp) { _XmHTMLDebug(17, ("cache.c, replaceObjectInCache: replacing object " "data for cached object:\n\t%s\n", tmp->file)); /* update cache size */ currentCacheSize -= tmp->size; /* store new object */ tmp->object = new_object; /* update object */ if(sizeProc) { tmp->size = sizeProc(new_object, clientData); currentCacheSize += tmp->size; } } else fprintf(stderr, "Warning:\n cache replacement: could not locate " "given object in object cache.\n\n"); } /***** * Name: removeURLObjectFromCache * Return Type: void * Description: removes the mapping for the given url. * In: * url: url to be invalidated. * Returns: * nothing. * Note: * This routine does not actually delete a mapping but decrements the * reference counter for the given url and the cache object it's pointing to. *****/ void removeURLObjectFromCache(String url) { ObjectMapping *tmp; for(tmp = objectMapping; tmp != NULL && strcmp(tmp->url, url); tmp = tmp->next); if(tmp) { tmp->ref_count--; tmp->cache_entry->map_count--; _XmHTMLDebug(17, ("cache.c: removeURLObjectFromCache, decremented " "reference count for mapping:\n\tFrom: %s (ref_count: %i)\n" "\tTo : %s (ref_count: %i)\n", tmp->url, tmp->ref_count, tmp->cache_entry->file, tmp->cache_entry->map_count)); } else fprintf(stderr, "Warning:\n cache URL removal: missing object " "entry in object cache.\n (object: %s)\n\n", url); } /***** * Name: removeObjectFromCache * Return Type: void * Description: decrement reference counter for the given file * In: * file: object to be removed * Returns: * nothing. *****/ void removeObjectFromCache(String file) { CacheObject *tmp; for(tmp = objectCache; tmp != NULL && strcmp(tmp->file, file); tmp = tmp->next); if(tmp) { tmp->ref_count--; _XmHTMLDebug(17, ("cache.c: removeObjectFromCache, decremented " "reference count for object:\n\t%s (ref_count: %i)\n", tmp->file, tmp->ref_count)); } else fprintf(stderr, "Warning:\n cache removal: missing object entry in " "object cache:\n %s\n\n", file); } /***** * Name: cleanObjectCache * Return Type: void * Description: removes unused objects (zero reference count) from the object * cache * In: * nothing * Returns: * nothing *****/ void cleanObjectCache(void) { CacheObject *tmp; ObjectMapping *tmp_map; if(cleanProc == NULL) { fprintf(stderr, "Warning:\n cache cleaning: no clean_proc " "defined!\n The cache list will be updated but stored objects " "are NOT freed.\n\n"); } /* * Cross check object mapping reference counts against cache reference * counts before really freeing an entry. */ for(tmp_map = objectMapping; tmp_map != NULL; tmp_map = tmp_map->next) { /* object reference count is stronger than object mapping count */ if(tmp_map->cache_entry->ref_count <= 0 && tmp_map->ref_count > 0) { /* objects with 0 reference count are always kicked out */ fprintf(stderr, "Warning:\n cache object mapping\n From: %s" "\n To : %s\n has a positive mapping count (%i) while " "object reference count is zero.\n\n", tmp_map->url, tmp_map->cache_entry->file, tmp_map->ref_count); tmp_map->ref_count = 0; } if(tmp_map->ref_count <= 0) { if(tmp_map->cache_entry->ref_count != 0) { fprintf(stderr, "Error:\n cache cleaning: reference count " "for cached object\n %s is non-zero (%i).\n\n", tmp_map->cache_entry->file, tmp_map->cache_entry->ref_count); tmp_map->ref_count = tmp_map->cache_entry->ref_count; } else { ObjectMapping *save; save = (tmp_map->prev ? tmp_map->prev : tmp_map->next); _XmHTMLDebug(17, ("cache.c, cleanObjectCache: removing object " "mapping:\n\tFrom :%s\n\tTo :%s\n", tmp_map->url, tmp_map->cache_entry->file)); /* discard this mapping */ discardMapEntry(tmp_map); /* and free it */ free(tmp_map->url); free(tmp_map); tmp_map = save; /* sanity if last item in list is removed */ if(tmp_map == NULL) break; } } } for(tmp = objectCache; tmp != NULL; tmp = tmp->next) { /* zero reference count, remove it */ if(tmp->ref_count <= 0) { CacheObject *save; save = (tmp->prev ? tmp->prev : tmp->next); _XmHTMLDebug(17, ("cache.c, cleanObjectCache: removing cache " "object:\n\t%s\n", tmp->file)); /* discard this cache entry from the objectCache */ discardCacheEntry(tmp); /* call clean function to release cached data */ if(cleanProc) cleanProc(tmp->object, clientData); tmp->ref_count = 0; tmp->map_count = 0; /* and wipe the entry */ free(tmp->file); free(tmp); tmp = save; /* sanity if last item in list is removed */ if(tmp == NULL) break; } } } /***** * Name: destroyObjectCache * Return Type: void * Description: wipes the object cache completely clean. * cache * In: * nothing * Returns: * nothing *****/ void destroyObjectCache(void) { CacheObject *tmp; ObjectMapping *tmp_map; if(cleanProc == NULL) { fprintf(stderr, "Warning:\n cache destruction: no clean_proc " "defined!\n The cache list will be updated but stored objects " "are NOT freed.\n\n"); } /* * Cross check object mapping reference counts against cache reference * counts before really freeing an entry. */ while(objectMapping != NULL) { tmp_map = objectMapping->next; free(objectMapping->url); free(objectMapping); objectMapping = tmp_map; } while(objectCache != NULL) { tmp = objectCache->next; /* call clean function to release cached data */ if(cleanProc) cleanProc(objectCache->object, clientData); /* and wipe the entry */ free(objectCache->file); free(objectCache); objectCache = tmp; } } /***** * Name: pruneObjectCache * Return Type: void * Description: removes all mappings with a reference count of zero and * all objects which are no longer being referred. * In: * nothing; * Returns: * nothing; *****/ void pruneObjectCache(void) { CacheObject *tmp; ObjectMapping *tmp_map; if(cleanProc == NULL) { fprintf(stderr, "Warning:\n object cache pruning: no clean_proc " "defined!\n The cache list will be updated but stored objects " "are NOT freed.\n\n"); } /* remove all unused mappings */ for(tmp_map = objectMapping; tmp_map != NULL; tmp_map = tmp_map->next) { if(tmp_map->ref_count <= 0) { ObjectMapping *save; save = (tmp_map->prev ? tmp_map->prev : tmp_map->next); _XmHTMLDebug(17, ("cache.c, pruneObjectCache: removing object " "mapping:\n\tFrom :%s\n\tTo :%s\n", tmp_map->url, tmp_map->cache_entry->file)); /* discard this mapping */ discardMapEntry(tmp_map); /* and free it */ free(tmp_map->url); free(tmp_map); tmp_map = save; /* sanity if last item in list is removed */ if(tmp_map == NULL) break; } } /* remove all unused objects */ for(tmp = objectCache; tmp != NULL; tmp = tmp->next) { /* zero reference count, remove it */ if(tmp->map_count == 0) { CacheObject *save; save = (tmp->prev ? tmp->prev : tmp->next); _XmHTMLDebug(17, ("cache.c, pruneObjectCache: removing cache " "object:\n\t%s\n", tmp->file)); /* discard this cache entry from the objectCache */ discardCacheEntry(tmp); /* call clean function to release cached data */ if(cleanProc) cleanProc(tmp->object, clientData); tmp->ref_count = 0; tmp->map_count = 0; /* and wipe the entry */ free(tmp->file); free(tmp); tmp = save; /* sanity if last item in list is removed */ if(tmp == NULL) break; } } } /***** * Name: destroyCache * Return Type: void * Description: completely obliterates the cache * In: * nothing * Returns: * nothing *****/ void destroyCache(void) { CacheObject *tmp; ObjectMapping *tmp_map; if(cleanProc == NULL) { fprintf(stderr, "Warning:\n cache destruction: no clean_proc " "defined!\n The cache list will be destroyed but stored " "objects are NOT freed.\n\n"); } /* first clean out the cache mappings */ while(objectMapping != NULL) { tmp_map = objectMapping->next; free(objectMapping->url); free(objectMapping); objectMapping = tmp_map; } /* now clean out the cache itself */ while(objectCache != NULL) { tmp = objectCache->next; /* call clean function to release cached data */ if(cleanProc) cleanProc(tmp->object, clientData); /* and wipe the entry */ free(tmp->file); free(tmp); objectCache = tmp; } /* reset everything to default */ objectMapping = (ObjectMapping*)NULL; objectCache = (CacheObject*)NULL; cleanProc = (cleanObjectProc)NULL; sizeProc = (sizeObjectProc)NULL; clientData = (XtPointer)NULL; cacheSize = DEFAULT_CACHE_SIZE; currentCacheSize = 0; } /***** * Name: getCacheInfo * Return Type: void * Description: returns cache usage info * In: * *size: occupied size of image cache; * *nobjects: number of objects stored in cache. * Returns: * nothing but the arguments are updated. *****/ void getCacheInfo(int *size, int *nobjects) { CacheObject *tmp; int i = 0; for(tmp = objectCache; tmp != NULL; tmp = tmp->next, i++); *nobjects = i; *size = currentCacheSize; } /***** * Name: initCache * Return Type: void * Description: initialize the object caching routines. * In: * size: maximum cache size; * clean_proc: procedure to call for actual object destruction; * size_proc: procedure to call for determining the size of a cached object; * client_d..: data to be used as the second arg. when calling the above to * functions. This arg is unused internally. * Returns: * nothing * Note: * cacheSize is unused for now *****/ void initCache(int size, cleanObjectProc clean_proc, sizeObjectProc size_proc, XtPointer client_data) { /* uses default cache size if size is not set */ if(size) cacheSize = size; currentCacheSize = 0; cleanProc = clean_proc; sizeProc = size_proc; clientData = client_data; } /***** * Special debugging functions: * _listCachedObjects(void) * lists all entries in the object cache * _listObjectMappings(void) * lists all url->file mappings in the objectMapping. *****/ #ifdef DEBUG void _listCachedObjects(void) { CacheObject *tmp; int i = 0; printf("Object id : Reference Count\n"); for(tmp = objectCache; tmp != NULL; tmp = tmp->next, i++) printf("%i: %s, %i\n", i, tmp->file, tmp->ref_count); printf("Total objects cached: %i\n", i); } void _listObjectMappings(void) { ObjectMapping *tmp; int i = 0; printf("Mapping id : : \n"); for(tmp = objectMapping; tmp != NULL; tmp = tmp->next, i++) printf("%i:\n\tFrom : %s (%i)\n\tTo : %s (%i)\n", i, tmp->url, tmp->ref_count, tmp->cache_entry->file, tmp->cache_entry->ref_count); printf("Total mappings: %i\n", i); } #endif /* DEBUG */