Converted README to markdown
[rox-filer.git] / ROX-Filer / src / fscache.c
blobb4033a534c752146665e813e9f3cd4a76ad3b41d
1 /*
2 * ROX-Filer, filer for the ROX desktop project
3 * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
5 * A cache object holds stat details about files in a hash table, along
6 * with user-specified data. When you want to read in a file try to
7 * get the data via the cache - if the file is cached AND has not been
8 * modified since it was last loaded the cached copy is returned, else the
9 * file is reloaded.
11 * The actual data need not be the raw file contents - a user specified
12 * function loads the file and associates data with the file in the cache.
14 * This program is free software; you can redistribute it and/or modify it
15 * under the terms of the GNU General Public License as published by the Free
16 * Software Foundation; either version 2 of the License, or (at your option)
17 * any later version.
19 * This program is distributed in the hope that it will be useful, but WITHOUT
20 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
22 * more details.
24 * You should have received a copy of the GNU General Public License along with
25 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
26 * Place, Suite 330, Boston, MA 02111-1307 USA
29 #include "config.h"
31 #include "global.h"
33 #include "fscache.h"
35 typedef struct _GFSCacheKey GFSCacheKey;
36 typedef struct _GFSCacheData GFSCacheData;
38 struct _GFSCache
40 GHashTable *inode_to_stats;
41 GFSLoadFunc load;
42 GFSUpdateFunc update;
43 gpointer user_data;
46 struct _GFSCacheKey
48 dev_t device;
49 ino_t inode;
52 struct _GFSCacheData
54 GObject *data; /* The object from the file */
55 time_t last_lookup;
57 /* Details of the file last time we checked it */
58 time_t m_time, c_time;
59 off_t length;
60 mode_t mode;
63 #define UPTODATE(data, info) \
64 (data->m_time == info.st_mtime \
65 && data->c_time == info.st_ctime \
66 && data->length == info.st_size \
67 && data->mode == info.st_mode) \
70 /* Static prototypes */
72 static guint hash_key(gconstpointer key);
73 static gint cmp_stats(gconstpointer a, gconstpointer b);
74 static void destroy_hash_entry(gpointer key, gpointer data, gpointer user_data);
75 static gboolean purge_hash_entry(gpointer key, gpointer data,
76 gpointer user_data);
77 static GFSCacheData *lookup_internal(GFSCache *cache, const char *pathname,
78 FSCacheLookup lookup_type);
81 struct PurgeInfo
83 GFSCache *cache;
84 gint age;
85 time_t now;
88 /****************************************************************
89 * EXTERNAL INTERFACE *
90 ****************************************************************/
93 /* Create a new GFSCache object and return a pointer to it.
95 * When someone tries to lookup a file which is not in the cache,
96 * load() is called.
97 * It should load the file and return a pointer to an object for the file.
98 * The object should have a ref count of 1.
100 * update() will be called to update an object which is cached, but
101 * out of date. If NULL, the object will be unref'd and load() used
102 * to make a new one.
104 * 'user_data' will be passed to all of the above functions.
106 GFSCache *g_fscache_new(GFSLoadFunc load,
107 GFSUpdateFunc update,
108 gpointer user_data)
110 GFSCache *cache;
112 cache = g_new(GFSCache, 1);
113 cache->inode_to_stats = g_hash_table_new(hash_key, cmp_stats);
114 cache->load = load;
115 cache->update = update;
116 cache->user_data = user_data;
118 return cache;
121 void g_fscache_destroy(GFSCache *cache)
123 g_return_if_fail(cache != NULL);
125 g_hash_table_foreach(cache->inode_to_stats, destroy_hash_entry, NULL);
126 g_hash_table_destroy(cache->inode_to_stats);
128 g_free(cache);
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_object_unref() the returned value when
135 * you're done with it.
137 * Returns NULL on failure.
139 gpointer g_fscache_lookup(GFSCache *cache, const char *pathname)
141 return g_fscache_lookup_full(cache, pathname,
142 FSCACHE_LOOKUP_CREATE, NULL);
145 /* Force this already-loaded item into the cache. The cache will
146 * ref the object if it wants to keep it.
147 * If update_details is FALSE then the timestamp and size aren't recorded.
148 * Generally, you call this function with update_details = TRUE when you
149 * start loading some data and then with update_details = FALSE when you
150 * put in the loaded object.
152 void g_fscache_insert(GFSCache *cache, const char *pathname, gpointer obj,
153 gboolean update_details)
155 GFSCacheData *data;
157 data = lookup_internal(cache, pathname,
158 update_details ? FSCACHE_LOOKUP_INIT
159 : FSCACHE_LOOKUP_INSERT);
161 if (!data)
162 return;
164 if (obj)
165 g_object_ref(obj);
166 if (data->data)
167 g_object_unref(data->data);
168 data->data = obj;
171 /* As g_fscache_lookup, but 'lookup_type' controls what happens if the data
172 * is out-of-date.
173 * If found is not NULL, use it to indicate whether something is being
174 * returned (a NULL return could indicate that the data is cached, but
175 * the data is NULL).
176 * If returned value is not NULL, value is refd.
178 gpointer g_fscache_lookup_full(GFSCache *cache, const char *pathname,
179 FSCacheLookup lookup_type,
180 gboolean *found)
182 GFSCacheData *data;
184 g_return_val_if_fail(lookup_type != FSCACHE_LOOKUP_INIT, NULL);
186 data = lookup_internal(cache, pathname, lookup_type);
188 if (!data)
190 if (found)
191 *found = FALSE;
192 return NULL;
195 if (found)
196 *found = TRUE;
198 if (data->data)
199 g_object_ref(data->data);
201 return data->data;
204 /* Call the update() function on this item if it's in the cache
205 * AND it's out-of-date.
207 void g_fscache_may_update(GFSCache *cache, const char *pathname)
209 GFSCacheKey key;
210 GFSCacheData *data;
211 struct stat info;
213 g_return_if_fail(cache != NULL);
214 g_return_if_fail(pathname != NULL);
215 g_return_if_fail(cache->update != NULL);
217 if (mc_stat(pathname, &info))
218 return;
220 key.device = info.st_dev;
221 key.inode = info.st_ino;
223 data = g_hash_table_lookup(cache->inode_to_stats, &key);
225 if (data && !UPTODATE(data, info))
227 cache->update(data->data, pathname, cache->user_data);
228 data->m_time = info.st_mtime;
229 data->c_time = info.st_ctime;
230 data->length = info.st_size;
231 data->mode = info.st_mode;
235 /* Call the update() function on this item iff it's in the cache. */
236 void g_fscache_update(GFSCache *cache, const char *pathname)
238 GFSCacheKey key;
239 GFSCacheData *data;
240 struct stat info;
242 g_return_if_fail(cache != NULL);
243 g_return_if_fail(pathname != NULL);
244 g_return_if_fail(cache->update != NULL);
246 if (mc_stat(pathname, &info))
247 return;
249 key.device = info.st_dev;
250 key.inode = info.st_ino;
252 data = g_hash_table_lookup(cache->inode_to_stats, &key);
254 if (data)
256 cache->update(data->data, pathname, cache->user_data);
257 data->m_time = info.st_mtime;
258 data->c_time = info.st_ctime;
259 data->length = info.st_size;
260 data->mode = info.st_mode;
264 /* Remove all cache entries last accessed more than 'age' seconds
265 * ago.
267 void g_fscache_purge(GFSCache *cache, gint age)
269 struct PurgeInfo info;
271 g_return_if_fail(cache != NULL);
273 info.age = age;
274 info.cache = cache;
275 info.now = time(NULL);
277 g_hash_table_foreach_remove(cache->inode_to_stats, purge_hash_entry,
278 (gpointer) &info);
282 /****************************************************************
283 * INTERNAL FUNCTIONS *
284 ****************************************************************/
287 /* Generate a hash number for some stats */
288 static guint hash_key(gconstpointer key)
290 GFSCacheKey *stats = (GFSCacheKey *) key;
292 return stats->inode;
295 /* See if two stats blocks represent the same file */
296 static gint cmp_stats(gconstpointer a, gconstpointer b)
298 GFSCacheKey *c = (GFSCacheKey *) a;
299 GFSCacheKey *d = (GFSCacheKey *) b;
301 return c->device == d->device && c->inode == d->inode;
304 static void destroy_hash_entry(gpointer key, gpointer data, gpointer user_data)
306 GFSCacheData *cache_data = (GFSCacheData *) data;
308 if (cache_data->data)
309 g_object_unref(cache_data->data);
311 g_free(key);
312 g_free(data);
315 static gboolean purge_hash_entry(gpointer key, gpointer data,
316 gpointer user_data)
318 struct PurgeInfo *info = (struct PurgeInfo *) user_data;
319 GFSCacheData *cache_data = (GFSCacheData *) data;
321 /* It's wasteful to remove an entry if someone else is using it */
322 if (cache_data->data && cache_data->data->ref_count > 1)
323 return FALSE;
325 if (cache_data->last_lookup <= info->now
326 && cache_data->last_lookup >= info->now - info->age)
327 return FALSE;
329 if (cache_data->data)
330 g_object_unref(cache_data->data);
332 g_free(key);
333 g_free(data);
335 return TRUE;
338 /* As for g_fscache_lookup_full, but return the GFSCacheData rather than
339 * the data it contains. Doesn't increment the refcount.
341 static GFSCacheData *lookup_internal(GFSCache *cache, const char *pathname,
342 FSCacheLookup lookup_type)
344 struct stat info;
345 GFSCacheKey key;
346 GFSCacheData *data;
348 g_return_val_if_fail(cache != NULL, NULL);
349 g_return_val_if_fail(pathname != NULL, NULL);
351 if (mc_stat(pathname, &info))
352 return NULL;
354 key.device = info.st_dev;
355 key.inode = info.st_ino;
357 data = g_hash_table_lookup(cache->inode_to_stats, &key);
359 if (data)
361 /* We've cached this file already */
363 if (lookup_type == FSCACHE_LOOKUP_PEEK ||
364 lookup_type == FSCACHE_LOOKUP_INSERT)
365 goto out; /* Never update on peeks */
367 if (lookup_type == FSCACHE_LOOKUP_INIT)
368 goto init;
370 /* Is it up-to-date? */
372 if (UPTODATE(data, info))
373 goto out;
375 if (lookup_type == FSCACHE_LOOKUP_ONLY_NEW)
376 return NULL;
378 /* Out-of-date */
379 if (cache->update)
380 cache->update(data->data, pathname, cache->user_data);
381 else
383 if (data->data)
384 g_object_unref(data->data);
385 data->data = NULL;
388 else
390 GFSCacheKey *new_key;
392 if (lookup_type != FSCACHE_LOOKUP_CREATE &&
393 lookup_type != FSCACHE_LOOKUP_INIT)
394 return NULL;
396 new_key = g_memdup(&key, sizeof(key));
398 data = g_new(GFSCacheData, 1);
399 data->data = NULL;
401 g_hash_table_insert(cache->inode_to_stats, new_key, data);
404 init:
405 data->m_time = info.st_mtime;
406 data->c_time = info.st_ctime;
407 data->length = info.st_size;
408 data->mode = info.st_mode;
410 if (data->data == NULL &&
411 lookup_type != FSCACHE_LOOKUP_INIT &&
412 lookup_type != FSCACHE_LOOKUP_INSERT)
414 /* Create the object for the file (ie, not an update) */
415 if (cache->load)
416 data->data = cache->load(pathname, cache->user_data);
418 out:
419 data->last_lookup = time(NULL);
421 return data;