r3768: Updated years.
[rox-filer.git] / ROX-Filer / src / fscache.c
blob784bc13a1c7b84836f5f19bdbd0a7d2f258de678
1 /*
2 * $Id$
4 * FSCache - a glib-style object for caching files
5 * Copyright (C) 2005, 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 typedef struct _GFSCacheKey GFSCacheKey;
38 typedef struct _GFSCacheData GFSCacheData;
40 struct _GFSCache
42 GHashTable *inode_to_stats;
43 GFSLoadFunc load;
44 GFSUpdateFunc update;
45 gpointer user_data;
48 struct _GFSCacheKey
50 dev_t device;
51 ino_t inode;
54 struct _GFSCacheData
56 GObject *data; /* The object from the file */
57 time_t last_lookup;
59 /* Details of the file last time we checked it */
60 time_t m_time, c_time;
61 off_t length;
62 mode_t mode;
65 #define UPTODATE(data, info) \
66 (data->m_time == info.st_mtime \
67 && data->c_time == info.st_ctime \
68 && data->length == info.st_size \
69 && data->mode == info.st_mode) \
72 /* Static prototypes */
74 static guint hash_key(gconstpointer key);
75 static gint cmp_stats(gconstpointer a, gconstpointer b);
76 static void destroy_hash_entry(gpointer key, gpointer data, gpointer user_data);
77 static gboolean purge_hash_entry(gpointer key, gpointer data,
78 gpointer user_data);
79 static GFSCacheData *lookup_internal(GFSCache *cache, const char *pathname,
80 FSCacheLookup lookup_type);
83 struct PurgeInfo
85 GFSCache *cache;
86 gint age;
87 time_t now;
90 /****************************************************************
91 * EXTERNAL INTERFACE *
92 ****************************************************************/
95 /* Create a new GFSCache object and return a pointer to it.
97 * When someone tries to lookup a file which is not in the cache,
98 * load() is called.
99 * It should load the file and return a pointer to an object for the file.
100 * The object should have a ref count of 1.
102 * ref() and unref() should modify the reference counter of the object.
103 * When the counter reaches zero, destroy the object.
105 * getref() returns the current value. This can be used to stop objects
106 * being purged from the cache while they are being used elsewhere, which
107 * is rather wasteful. If NULL then this check isn't done.
109 * update() will be called to update an object which is cached, but
110 * out of date. If NULL, the object will be unref'd and load() used
111 * to make a new one.
113 * 'user_data' will be passed to all of the above functions.
115 GFSCache *g_fscache_new(GFSLoadFunc load,
116 GFSUpdateFunc update,
117 gpointer user_data)
119 GFSCache *cache;
121 cache = g_new(GFSCache, 1);
122 cache->inode_to_stats = g_hash_table_new(hash_key, cmp_stats);
123 cache->load = load;
124 cache->update = update;
125 cache->user_data = user_data;
127 return cache;
130 void g_fscache_destroy(GFSCache *cache)
132 g_return_if_fail(cache != NULL);
134 g_hash_table_foreach(cache->inode_to_stats, destroy_hash_entry, NULL);
135 g_hash_table_destroy(cache->inode_to_stats);
137 g_free(cache);
140 /* Find the data for this file in the cache, loading it into
141 * the cache if it isn't there already.
143 * Remember to g_object_unref() the returned value when
144 * you're done with it.
146 * Returns NULL on failure.
148 gpointer g_fscache_lookup(GFSCache *cache, const char *pathname)
150 return g_fscache_lookup_full(cache, pathname,
151 FSCACHE_LOOKUP_CREATE, NULL);
154 /* Force this already-loaded item into the cache. The cache will
155 * ref the object if it wants to keep it.
156 * If update_details is FALSE then the timestamp and size aren't recorded.
157 * Generally, you call this function with update_details = TRUE when you
158 * start loading some data and then with update_details = FALSE when you
159 * put in the loaded object.
161 void g_fscache_insert(GFSCache *cache, const char *pathname, gpointer obj,
162 gboolean update_details)
164 GFSCacheData *data;
166 data = lookup_internal(cache, pathname,
167 update_details ? FSCACHE_LOOKUP_INIT
168 : FSCACHE_LOOKUP_INSERT);
170 if (!data)
171 return;
173 if (obj)
174 g_object_ref(obj);
175 if (data->data)
176 g_object_unref(data->data);
177 data->data = obj;
180 /* As g_fscache_lookup, but 'lookup_type' controls what happens if the data
181 * is out-of-date.
182 * If found is not NULL, use it to indicate whether something is being
183 * returned (a NULL return could indicate that the data is cached, but
184 * the data is NULL).
185 * If returned value is not NULL, value is refd.
187 gpointer g_fscache_lookup_full(GFSCache *cache, const char *pathname,
188 FSCacheLookup lookup_type,
189 gboolean *found)
191 GFSCacheData *data;
193 g_return_val_if_fail(lookup_type != FSCACHE_LOOKUP_INIT, NULL);
195 data = lookup_internal(cache, pathname, lookup_type);
197 if (!data)
199 if (found)
200 *found = FALSE;
201 return NULL;
204 if (found)
205 *found = TRUE;
207 if (data->data)
208 g_object_ref(data->data);
210 return data->data;
213 /* Call the update() function on this item if it's in the cache
214 * AND it's out-of-date.
216 void g_fscache_may_update(GFSCache *cache, const char *pathname)
218 GFSCacheKey key;
219 GFSCacheData *data;
220 struct stat info;
222 g_return_if_fail(cache != NULL);
223 g_return_if_fail(pathname != NULL);
224 g_return_if_fail(cache->update != NULL);
226 if (mc_stat(pathname, &info))
227 return;
229 key.device = info.st_dev;
230 key.inode = info.st_ino;
232 data = g_hash_table_lookup(cache->inode_to_stats, &key);
234 if (data && !UPTODATE(data, info))
236 cache->update(data->data, pathname, cache->user_data);
237 data->m_time = info.st_mtime;
238 data->c_time = info.st_ctime;
239 data->length = info.st_size;
240 data->mode = info.st_mode;
244 /* Call the update() function on this item iff it's in the cache. */
245 void g_fscache_update(GFSCache *cache, const char *pathname)
247 GFSCacheKey key;
248 GFSCacheData *data;
249 struct stat info;
251 g_return_if_fail(cache != NULL);
252 g_return_if_fail(pathname != NULL);
253 g_return_if_fail(cache->update != NULL);
255 if (mc_stat(pathname, &info))
256 return;
258 key.device = info.st_dev;
259 key.inode = info.st_ino;
261 data = g_hash_table_lookup(cache->inode_to_stats, &key);
263 if (data)
265 cache->update(data->data, pathname, cache->user_data);
266 data->m_time = info.st_mtime;
267 data->c_time = info.st_ctime;
268 data->length = info.st_size;
269 data->mode = info.st_mode;
273 /* Remove all cache entries last accessed more than 'age' seconds
274 * ago.
276 void g_fscache_purge(GFSCache *cache, gint age)
278 struct PurgeInfo info;
280 g_return_if_fail(cache != NULL);
282 info.age = age;
283 info.cache = cache;
284 info.now = time(NULL);
286 g_hash_table_foreach_remove(cache->inode_to_stats, purge_hash_entry,
287 (gpointer) &info);
291 /****************************************************************
292 * INTERNAL FUNCTIONS *
293 ****************************************************************/
296 /* Generate a hash number for some stats */
297 static guint hash_key(gconstpointer key)
299 GFSCacheKey *stats = (GFSCacheKey *) key;
301 return stats->inode;
304 /* See if two stats blocks represent the same file */
305 static gint cmp_stats(gconstpointer a, gconstpointer b)
307 GFSCacheKey *c = (GFSCacheKey *) a;
308 GFSCacheKey *d = (GFSCacheKey *) b;
310 return c->device == d->device && c->inode == d->inode;
313 static void destroy_hash_entry(gpointer key, gpointer data, gpointer user_data)
315 GFSCacheData *cache_data = (GFSCacheData *) data;
317 if (cache_data->data)
318 g_object_unref(cache_data->data);
320 g_free(key);
321 g_free(data);
324 static gboolean purge_hash_entry(gpointer key, gpointer data,
325 gpointer user_data)
327 struct PurgeInfo *info = (struct PurgeInfo *) user_data;
328 GFSCacheData *cache_data = (GFSCacheData *) data;
330 /* It's wasteful to remove an entry if someone else is using it */
331 if (cache_data->data && cache_data->data->ref_count > 1)
332 return FALSE;
334 if (cache_data->last_lookup <= info->now
335 && cache_data->last_lookup >= info->now - info->age)
336 return FALSE;
338 if (cache_data->data)
339 g_object_unref(cache_data->data);
341 g_free(key);
342 g_free(data);
344 return TRUE;
347 /* As for g_fscache_lookup_full, but return the GFSCacheData rather than
348 * the data it contains. Doesn't increment the refcount.
350 static GFSCacheData *lookup_internal(GFSCache *cache, const char *pathname,
351 FSCacheLookup lookup_type)
353 struct stat info;
354 GFSCacheKey key;
355 GFSCacheData *data;
357 g_return_val_if_fail(cache != NULL, NULL);
358 g_return_val_if_fail(pathname != NULL, NULL);
360 if (mc_stat(pathname, &info))
361 return NULL;
363 key.device = info.st_dev;
364 key.inode = info.st_ino;
366 data = g_hash_table_lookup(cache->inode_to_stats, &key);
368 if (data)
370 /* We've cached this file already */
372 if (lookup_type == FSCACHE_LOOKUP_PEEK ||
373 lookup_type == FSCACHE_LOOKUP_INSERT)
374 goto out; /* Never update on peeks */
376 if (lookup_type == FSCACHE_LOOKUP_INIT)
377 goto init;
379 /* Is it up-to-date? */
381 if (UPTODATE(data, info))
382 goto out;
384 if (lookup_type == FSCACHE_LOOKUP_ONLY_NEW)
385 return NULL;
387 /* Out-of-date */
388 if (cache->update)
389 cache->update(data->data, pathname, cache->user_data);
390 else
392 if (data->data)
393 g_object_unref(data->data);
394 data->data = NULL;
397 else
399 GFSCacheKey *new_key;
401 if (lookup_type != FSCACHE_LOOKUP_CREATE &&
402 lookup_type != FSCACHE_LOOKUP_INIT)
403 return NULL;
405 new_key = g_memdup(&key, sizeof(key));
407 data = g_new(GFSCacheData, 1);
408 data->data = NULL;
410 g_hash_table_insert(cache->inode_to_stats, new_key, data);
413 init:
414 data->m_time = info.st_mtime;
415 data->c_time = info.st_ctime;
416 data->length = info.st_size;
417 data->mode = info.st_mode;
419 if (data->data == NULL &&
420 lookup_type != FSCACHE_LOOKUP_INIT &&
421 lookup_type != FSCACHE_LOOKUP_INSERT)
423 /* Create the object for the file (ie, not an update) */
424 if (cache->load)
425 data->data = cache->load(pathname, cache->user_data);
427 out:
428 data->last_lookup = time(NULL);
430 return data;