r898: Applied Bernard Jungen's latest patch:
[rox-filer.git] / ROX-Filer / src / fscache.c
blobd7bcab76fcffaf39cc996dfca6710c5f3de560ba
1 /*
2 * $Id$
4 * FSCache - a glib-style object for caching files
5 * Copyright (C) 2001, the ROX-Filer team.
7 * A cache object holds stat details about files in a hash table, along
8 * with user-specified data. When you want to read in a file try to
9 * get the data via the cache - if the file is cached AND has not been
10 * modified since it was last loaded the cached copy is returned, else the
11 * file is reloaded.
13 * The actual data need not be the raw file contents - a user specified
14 * function loads the file and associates data with the file in the cache.
16 * This program is free software; you can redistribute it and/or modify it
17 * under the terms of the GNU General Public License as published by the Free
18 * Software Foundation; either version 2 of the License, or (at your option)
19 * any later version.
21 * This program is distributed in the hope that it will be useful, but WITHOUT
22 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
23 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
24 * more details.
26 * You should have received a copy of the GNU General Public License along with
27 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
28 * Place, Suite 330, Boston, MA 02111-1307 USA
31 #include "config.h"
33 #include "fscache.h"
35 #define UPTODATE(data, info) \
36 (data->m_time == info.st_mtime \
37 && data->length == info.st_size \
38 && data->mode == info.st_mode) \
41 /* Static prototypes */
43 static guint hash_key(gconstpointer key);
44 static gint cmp_stats(gconstpointer a, gconstpointer b);
45 static void destroy_hash_entry(gpointer key, gpointer data, gpointer user_data);
46 static gboolean purge_hash_entry(gpointer key, gpointer data,
47 gpointer user_data);
50 struct PurgeInfo
52 GFSCache *cache;
53 gint age;
54 time_t now;
57 /****************************************************************
58 * EXTERNAL INTERFACE *
59 ****************************************************************/
62 /* Create a new GFSCache object and return a pointer to it.
64 * When someone tries to lookup a file which is not in the cache,
65 * load() is called.
66 * It should load the file and return a pointer to an object for the file.
67 * The object should have a ref count of 1.
69 * ref() and unref() should modify the reference counter of the object.
70 * When the counter reaches zero, destroy the object.
72 * getref() returns the current value. This can be used to stop objects
73 * being purged from the cache while they are being used elsewhere, which
74 * is rather wasteful. If NULL then this check isn't done.
76 * update() will be called to update an object which is cached, but
77 * out of date. If NULL, the object will be unref'd and load() used
78 * to make a new one.
80 * 'user_data' will be passed to all of the above functions.
82 GFSCache *g_fscache_new(GFSLoadFunc load,
83 GFSRefFunc ref,
84 GFSRefFunc unref,
85 GFSGetRefFunc getref,
86 GFSUpdateFunc update,
87 gpointer user_data)
89 GFSCache *cache;
91 cache = g_new(GFSCache, 1);
92 cache->inode_to_stats = g_hash_table_new(hash_key, cmp_stats);
93 cache->load = load;
94 cache->ref = ref;
95 cache->unref = unref;
96 cache->getref = getref;
97 cache->update = update;
98 cache->user_data = user_data;
100 return cache;
103 void g_fscache_destroy(GFSCache *cache)
105 g_return_if_fail(cache != NULL);
107 g_hash_table_foreach(cache->inode_to_stats, destroy_hash_entry, cache);
108 g_hash_table_destroy(cache->inode_to_stats);
110 g_free(cache);
113 /* Call the ref() user function for this object */
114 void g_fscache_data_ref(GFSCache *cache, gpointer data)
116 g_return_if_fail(cache != NULL);
118 if (cache->ref)
119 cache->ref(data, cache->user_data);
122 /* Call the unref() user function for this object */
123 void g_fscache_data_unref(GFSCache *cache, gpointer data)
125 g_return_if_fail(cache != NULL);
127 if (cache->unref)
128 cache->unref(data, cache->user_data);
131 /* Find the data for this file in the cache, loading it into
132 * the cache if it isn't there already.
134 * Remember to g_fscache_data_unref() the returned value when
135 * you're done with it.
137 * Returns NULL on failure.
139 gpointer g_fscache_lookup(GFSCache *cache, char *pathname)
141 return g_fscache_lookup_full(cache, pathname, FSCACHE_LOOKUP_CREATE);
144 /* As g_fscache_lookup, but 'load' flags control what happens if the data
145 * is out-of-date.
146 * TRUE => Data is updated / reloaded
147 * FALSE => Returns NULL
149 gpointer g_fscache_lookup_full(GFSCache *cache, char *pathname,
150 FSCacheLookup lookup_type)
152 struct stat info;
153 GFSCacheKey key;
154 GFSCacheData *data;
156 g_return_val_if_fail(cache != NULL, NULL);
157 g_return_val_if_fail(pathname != NULL, NULL);
159 if (mc_stat(pathname, &info))
160 return NULL;
162 key.device = info.st_dev;
163 key.inode = info.st_ino;
165 data = g_hash_table_lookup(cache->inode_to_stats, &key);
167 if (data)
169 /* We've cached this file already */
171 if (lookup_type == FSCACHE_LOOKUP_PEEK)
172 goto out; /* Never update on peeks */
174 /* Is it up-to-date? */
176 if (UPTODATE(data, info))
177 goto out;
179 if (lookup_type == FSCACHE_LOOKUP_ONLY_NEW)
180 return NULL;
182 /* Out-of-date */
183 if (cache->update)
184 cache->update(data->data, pathname, cache->user_data);
185 else
187 if (cache->unref)
188 cache->unref(data->data, cache->user_data);
189 data->data = NULL;
192 else
194 GFSCacheKey *new_key;
196 if (lookup_type != FSCACHE_LOOKUP_CREATE)
197 return NULL;
199 new_key = g_memdup(&key, sizeof(key));
201 data = g_new(GFSCacheData, 1);
202 data->data = NULL;
204 g_hash_table_insert(cache->inode_to_stats, new_key, data);
207 data->m_time = info.st_mtime;
208 data->length = info.st_size;
209 data->mode = info.st_mode;
211 if (data->data == NULL)
213 /* Create the object for the file (ie, not an update) */
214 if (cache->load)
215 data->data = cache->load(pathname, cache->user_data);
217 out:
218 if (cache->ref)
219 cache->ref(data->data, cache->user_data);
220 data->last_lookup = time(NULL);
221 return data->data;
224 /* Call the update() function on this item if it's in the cache
225 * AND it's out-of-date.
227 void g_fscache_may_update(GFSCache *cache, char *pathname)
229 GFSCacheKey key;
230 GFSCacheData *data;
231 struct stat info;
233 g_return_if_fail(cache != NULL);
234 g_return_if_fail(pathname != NULL);
235 g_return_if_fail(cache->update != NULL);
237 if (mc_stat(pathname, &info))
238 return;
240 key.device = info.st_dev;
241 key.inode = info.st_ino;
243 data = g_hash_table_lookup(cache->inode_to_stats, &key);
245 if (data && !UPTODATE(data, info))
247 cache->update(data->data, pathname, cache->user_data);
248 data->m_time = info.st_mtime;
249 data->length = info.st_size;
250 data->mode = info.st_mode;
254 /* Call the update() function on this item iff it's in the cache. */
255 void g_fscache_update(GFSCache *cache, char *pathname)
257 GFSCacheKey key;
258 GFSCacheData *data;
259 struct stat info;
261 g_return_if_fail(cache != NULL);
262 g_return_if_fail(pathname != NULL);
263 g_return_if_fail(cache->update != NULL);
265 if (mc_stat(pathname, &info))
266 return;
268 key.device = info.st_dev;
269 key.inode = info.st_ino;
271 data = g_hash_table_lookup(cache->inode_to_stats, &key);
273 if (data)
275 cache->update(data->data, pathname, cache->user_data);
276 data->m_time = info.st_mtime;
277 data->length = info.st_size;
278 data->mode = info.st_mode;
282 /* Remove all cache entries last accessed more than 'age' seconds
283 * ago.
285 void g_fscache_purge(GFSCache *cache, gint age)
287 struct PurgeInfo info;
289 g_return_if_fail(cache != NULL);
291 info.age = age;
292 info.cache = cache;
293 info.now = time(NULL);
295 g_hash_table_foreach_remove(cache->inode_to_stats, purge_hash_entry,
296 (gpointer) &info);
300 /****************************************************************
301 * INTERNAL FUNCTIONS *
302 ****************************************************************/
305 /* Generate a hash number for some stats */
306 static guint hash_key(gconstpointer key)
308 GFSCacheKey *stats = (GFSCacheKey *) key;
310 return stats->inode;
313 /* See if two stats blocks represent the same file */
314 static gint cmp_stats(gconstpointer a, gconstpointer b)
316 GFSCacheKey *c = (GFSCacheKey *) a;
317 GFSCacheKey *d = (GFSCacheKey *) b;
319 return c->device == d->device && c->inode == d->inode;
322 static void destroy_hash_entry(gpointer key, gpointer data, gpointer user_data)
324 GFSCache *cache = (GFSCache *) user_data;
325 GFSCacheData *cache_data = (GFSCacheData *) data;
327 if (cache->unref)
328 cache->unref(cache_data->data, cache->user_data);
330 g_free(key);
331 g_free(data);
334 static gboolean purge_hash_entry(gpointer key, gpointer data,
335 gpointer user_data)
337 struct PurgeInfo *info = (struct PurgeInfo *) user_data;
338 GFSCacheData *cache_data = (GFSCacheData *) data;
339 GFSCache *cache = info->cache;
341 /* It's wasteful to remove an entry if someone else is using it */
342 if (cache->getref &&
343 cache->getref(cache_data->data, cache->user_data) > 1)
344 return FALSE;
346 if (cache_data->last_lookup <= info->now
347 && cache_data->last_lookup >= info->now - info->age)
348 return FALSE;
350 if (cache->unref)
351 cache->unref(cache_data->data, cache->user_data);
353 g_free(key);
354 g_free(data);
356 return TRUE;