r1322: Converted MaskedPixmap to GObject.
[rox-filer.git] / ROX-Filer / src / fscache.c
blob771770cffca6f380101123e295b1a87ada07b04c
1 /*
2 * $Id$
4 * FSCache - a glib-style object for caching files
5 * Copyright (C) 2002, 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 "global.h"
35 #include "fscache.h"
37 #define UPTODATE(data, info) \
38 (data->m_time == info.st_mtime \
39 && data->length == info.st_size \
40 && data->mode == info.st_mode) \
43 /* Static prototypes */
45 static guint hash_key(gconstpointer key);
46 static gint cmp_stats(gconstpointer a, gconstpointer b);
47 static void destroy_hash_entry(gpointer key, gpointer data, gpointer user_data);
48 static gboolean purge_hash_entry(gpointer key, gpointer data,
49 gpointer user_data);
50 static GFSCacheData *lookup_internal(GFSCache *cache, const char *pathname,
51 FSCacheLookup lookup_type);
54 struct PurgeInfo
56 GFSCache *cache;
57 gint age;
58 time_t now;
61 /****************************************************************
62 * EXTERNAL INTERFACE *
63 ****************************************************************/
66 /* Create a new GFSCache object and return a pointer to it.
68 * When someone tries to lookup a file which is not in the cache,
69 * load() is called.
70 * It should load the file and return a pointer to an object for the file.
71 * The object should have a ref count of 1.
73 * ref() and unref() should modify the reference counter of the object.
74 * When the counter reaches zero, destroy the object.
76 * getref() returns the current value. This can be used to stop objects
77 * being purged from the cache while they are being used elsewhere, which
78 * is rather wasteful. If NULL then this check isn't done.
80 * update() will be called to update an object which is cached, but
81 * out of date. If NULL, the object will be unref'd and load() used
82 * to make a new one.
84 * 'user_data' will be passed to all of the above functions.
86 GFSCache *g_fscache_new(GFSLoadFunc load,
87 GFSRefFunc ref,
88 GFSRefFunc unref,
89 GFSGetRefFunc getref,
90 GFSUpdateFunc update,
91 gpointer user_data)
93 GFSCache *cache;
95 cache = g_new(GFSCache, 1);
96 cache->inode_to_stats = g_hash_table_new(hash_key, cmp_stats);
97 cache->load = load;
98 cache->ref = ref;
99 cache->unref = unref;
100 cache->getref = getref;
101 cache->update = update;
102 cache->user_data = user_data;
104 return cache;
107 void g_fscache_destroy(GFSCache *cache)
109 g_return_if_fail(cache != NULL);
111 g_hash_table_foreach(cache->inode_to_stats, destroy_hash_entry, cache);
112 g_hash_table_destroy(cache->inode_to_stats);
114 g_free(cache);
117 /* Call the ref() user function for this object */
118 void g_fscache_data_ref(GFSCache *cache, gpointer data)
120 g_return_if_fail(cache != NULL);
122 if (cache->ref && data)
123 cache->ref(data, cache->user_data);
126 /* Call the unref() user function for this object */
127 void g_fscache_data_unref(GFSCache *cache, gpointer data)
129 g_return_if_fail(cache != NULL);
131 if (cache->unref && data)
132 cache->unref(data, cache->user_data);
135 /* Find the data for this file in the cache, loading it into
136 * the cache if it isn't there already.
138 * Remember to g_fscache_data_unref() the returned value when
139 * you're done with it.
141 * Returns NULL on failure.
143 gpointer g_fscache_lookup(GFSCache *cache, const char *pathname)
145 return g_fscache_lookup_full(cache, pathname,
146 FSCACHE_LOOKUP_CREATE, NULL);
149 /* Force this already-loaded item into the cache. The cache will
150 * ref the object if it wants to keep it.
151 * If update_details is FALSE then the timestamp and size aren't recorded.
152 * Generally, you call this function with update_details = TRUE when you
153 * start loading some data and then with update_details = FALSE when you
154 * put in the loaded object.
156 void g_fscache_insert(GFSCache *cache, const char *pathname, gpointer obj,
157 gboolean update_details)
159 GFSCacheData *data;
161 data = lookup_internal(cache, pathname,
162 update_details ? FSCACHE_LOOKUP_INIT
163 : FSCACHE_LOOKUP_INSERT);
165 if (!data)
166 return;
168 if (cache->unref && data->data)
169 cache->unref(data->data, cache->user_data);
170 data->data = obj;
171 if (cache->ref && data->data)
172 cache->ref(data->data, cache->user_data);
175 /* As g_fscache_lookup, but 'lookup_type' controls what happens if the data
176 * is out-of-date.
177 * If found is not NULL, use it to indicate whether something is being
178 * returned (a NULL return could indicate that the data is cached, but
179 * the data is NULL).
180 * If returned value is not NULL, value is refd.
182 gpointer g_fscache_lookup_full(GFSCache *cache, const char *pathname,
183 FSCacheLookup lookup_type,
184 gboolean *found)
186 GFSCacheData *data;
188 g_return_val_if_fail(lookup_type != FSCACHE_LOOKUP_INIT, NULL);
190 data = lookup_internal(cache, pathname, lookup_type);
192 if (!data)
194 if (found)
195 *found = FALSE;
196 return NULL;
199 if (found)
200 *found = TRUE;
202 if (cache->ref)
203 cache->ref(data->data, cache->user_data);
205 return data->data;
208 /* Call the update() function on this item if it's in the cache
209 * AND it's out-of-date.
211 void g_fscache_may_update(GFSCache *cache, const char *pathname)
213 GFSCacheKey key;
214 GFSCacheData *data;
215 struct stat info;
217 g_return_if_fail(cache != NULL);
218 g_return_if_fail(pathname != NULL);
219 g_return_if_fail(cache->update != NULL);
221 if (mc_stat(pathname, &info))
222 return;
224 key.device = info.st_dev;
225 key.inode = info.st_ino;
227 data = g_hash_table_lookup(cache->inode_to_stats, &key);
229 if (data && !UPTODATE(data, info))
231 cache->update(data->data, pathname, cache->user_data);
232 data->m_time = info.st_mtime;
233 data->length = info.st_size;
234 data->mode = info.st_mode;
238 /* Call the update() function on this item iff it's in the cache. */
239 void g_fscache_update(GFSCache *cache, const char *pathname)
241 GFSCacheKey key;
242 GFSCacheData *data;
243 struct stat info;
245 g_return_if_fail(cache != NULL);
246 g_return_if_fail(pathname != NULL);
247 g_return_if_fail(cache->update != NULL);
249 if (mc_stat(pathname, &info))
250 return;
252 key.device = info.st_dev;
253 key.inode = info.st_ino;
255 data = g_hash_table_lookup(cache->inode_to_stats, &key);
257 if (data)
259 cache->update(data->data, pathname, cache->user_data);
260 data->m_time = info.st_mtime;
261 data->length = info.st_size;
262 data->mode = info.st_mode;
266 /* Remove all cache entries last accessed more than 'age' seconds
267 * ago.
269 void g_fscache_purge(GFSCache *cache, gint age)
271 struct PurgeInfo info;
273 g_return_if_fail(cache != NULL);
275 info.age = age;
276 info.cache = cache;
277 info.now = time(NULL);
279 g_hash_table_foreach_remove(cache->inode_to_stats, purge_hash_entry,
280 (gpointer) &info);
284 /****************************************************************
285 * INTERNAL FUNCTIONS *
286 ****************************************************************/
289 /* Generate a hash number for some stats */
290 static guint hash_key(gconstpointer key)
292 GFSCacheKey *stats = (GFSCacheKey *) key;
294 return stats->inode;
297 /* See if two stats blocks represent the same file */
298 static gint cmp_stats(gconstpointer a, gconstpointer b)
300 GFSCacheKey *c = (GFSCacheKey *) a;
301 GFSCacheKey *d = (GFSCacheKey *) b;
303 return c->device == d->device && c->inode == d->inode;
306 static void destroy_hash_entry(gpointer key, gpointer data, gpointer user_data)
308 GFSCache *cache = (GFSCache *) user_data;
309 GFSCacheData *cache_data = (GFSCacheData *) data;
311 if (cache->unref)
312 cache->unref(cache_data->data, cache->user_data);
314 g_free(key);
315 g_free(data);
318 static gboolean purge_hash_entry(gpointer key, gpointer data,
319 gpointer user_data)
321 struct PurgeInfo *info = (struct PurgeInfo *) user_data;
322 GFSCacheData *cache_data = (GFSCacheData *) data;
323 GFSCache *cache = info->cache;
325 /* It's wasteful to remove an entry if someone else is using it */
326 if (cache->getref &&
327 cache->getref(cache_data->data, cache->user_data) > 1)
328 return FALSE;
330 if (cache_data->last_lookup <= info->now
331 && cache_data->last_lookup >= info->now - info->age)
332 return FALSE;
334 if (cache->unref)
335 cache->unref(cache_data->data, cache->user_data);
337 g_free(key);
338 g_free(data);
340 return TRUE;
343 /* As for g_fscache_lookup_full, but return the GFSCacheData rather than
344 * the data it contains. Doesn't increment the refcount.
346 static GFSCacheData *lookup_internal(GFSCache *cache, const char *pathname,
347 FSCacheLookup lookup_type)
349 struct stat info;
350 GFSCacheKey key;
351 GFSCacheData *data;
353 g_return_val_if_fail(cache != NULL, NULL);
354 g_return_val_if_fail(pathname != NULL, NULL);
356 if (mc_stat(pathname, &info))
357 return NULL;
359 key.device = info.st_dev;
360 key.inode = info.st_ino;
362 data = g_hash_table_lookup(cache->inode_to_stats, &key);
364 if (data)
366 /* We've cached this file already */
368 if (lookup_type == FSCACHE_LOOKUP_PEEK ||
369 lookup_type == FSCACHE_LOOKUP_INSERT)
370 goto out; /* Never update on peeks */
372 if (lookup_type == FSCACHE_LOOKUP_INIT)
373 goto init;
375 /* Is it up-to-date? */
377 if (UPTODATE(data, info))
378 goto out;
380 if (lookup_type == FSCACHE_LOOKUP_ONLY_NEW)
381 return NULL;
383 /* Out-of-date */
384 if (cache->update)
385 cache->update(data->data, pathname, cache->user_data);
386 else
388 if (cache->unref)
389 cache->unref(data->data, cache->user_data);
390 data->data = NULL;
393 else
395 GFSCacheKey *new_key;
397 if (lookup_type != FSCACHE_LOOKUP_CREATE &&
398 lookup_type != FSCACHE_LOOKUP_INIT)
399 return NULL;
401 new_key = g_memdup(&key, sizeof(key));
403 data = g_new(GFSCacheData, 1);
404 data->data = NULL;
406 g_hash_table_insert(cache->inode_to_stats, new_key, data);
409 init:
410 data->m_time = info.st_mtime;
411 data->length = info.st_size;
412 data->mode = info.st_mode;
414 if (data->data == NULL &&
415 lookup_type != FSCACHE_LOOKUP_INIT &&
416 lookup_type != FSCACHE_LOOKUP_INSERT)
418 /* Create the object for the file (ie, not an update) */
419 if (cache->load)
420 data->data = cache->load(pathname, cache->user_data);
422 out:
423 data->last_lookup = time(NULL);
425 return data;