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
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)
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
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
35 typedef struct _GFSCacheKey GFSCacheKey
;
36 typedef struct _GFSCacheData GFSCacheData
;
40 GHashTable
*inode_to_stats
;
54 GObject
*data
; /* The object from the file */
57 /* Details of the file last time we checked it */
58 time_t m_time
, c_time
;
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
,
77 static GFSCacheData
*lookup_internal(GFSCache
*cache
, const char *pathname
,
78 FSCacheLookup lookup_type
);
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,
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
104 * 'user_data' will be passed to all of the above functions.
106 GFSCache
*g_fscache_new(GFSLoadFunc load
,
107 GFSUpdateFunc update
,
112 cache
= g_new(GFSCache
, 1);
113 cache
->inode_to_stats
= g_hash_table_new(hash_key
, cmp_stats
);
115 cache
->update
= update
;
116 cache
->user_data
= user_data
;
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
);
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
)
157 data
= lookup_internal(cache
, pathname
,
158 update_details
? FSCACHE_LOOKUP_INIT
159 : FSCACHE_LOOKUP_INSERT
);
167 g_object_unref(data
->data
);
171 /* As g_fscache_lookup, but 'lookup_type' controls what happens if the data
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
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
,
184 g_return_val_if_fail(lookup_type
!= FSCACHE_LOOKUP_INIT
, NULL
);
186 data
= lookup_internal(cache
, pathname
, lookup_type
);
199 g_object_ref(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
)
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
))
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
)
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
))
249 key
.device
= info
.st_dev
;
250 key
.inode
= info
.st_ino
;
252 data
= g_hash_table_lookup(cache
->inode_to_stats
, &key
);
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
267 void g_fscache_purge(GFSCache
*cache
, gint age
)
269 struct PurgeInfo info
;
271 g_return_if_fail(cache
!= NULL
);
275 info
.now
= time(NULL
);
277 g_hash_table_foreach_remove(cache
->inode_to_stats
, purge_hash_entry
,
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
;
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
);
315 static gboolean
purge_hash_entry(gpointer key
, gpointer 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)
325 if (cache_data
->last_lookup
<= info
->now
326 && cache_data
->last_lookup
>= info
->now
- info
->age
)
329 if (cache_data
->data
)
330 g_object_unref(cache_data
->data
);
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
)
348 g_return_val_if_fail(cache
!= NULL
, NULL
);
349 g_return_val_if_fail(pathname
!= NULL
, NULL
);
351 if (mc_stat(pathname
, &info
))
354 key
.device
= info
.st_dev
;
355 key
.inode
= info
.st_ino
;
357 data
= g_hash_table_lookup(cache
->inode_to_stats
, &key
);
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
)
370 /* Is it up-to-date? */
372 if (UPTODATE(data
, info
))
375 if (lookup_type
== FSCACHE_LOOKUP_ONLY_NEW
)
380 cache
->update(data
->data
, pathname
, cache
->user_data
);
384 g_object_unref(data
->data
);
390 GFSCacheKey
*new_key
;
392 if (lookup_type
!= FSCACHE_LOOKUP_CREATE
&&
393 lookup_type
!= FSCACHE_LOOKUP_INIT
)
396 new_key
= g_memdup(&key
, sizeof(key
));
398 data
= g_new(GFSCacheData
, 1);
401 g_hash_table_insert(cache
->inode_to_stats
, new_key
, data
);
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) */
416 data
->data
= cache
->load(pathname
, cache
->user_data
);
419 data
->last_lookup
= time(NULL
);