1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-message-cache.c: Class for a Camel cache.
4 * Authors: Michael Zucchi <notzed@ximian.com>
6 * Copyright (C) 2001 Ximian, Inc. (www.ximian.com)
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of version 2 of the GNU Lesser General Public
10 * License as published by the Free Software Foundation.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
27 #include <sys/types.h>
37 #include <glib/gstdio.h>
38 #include <glib/gi18n-lib.h>
40 #include <libedataserver/e-data-server-util.h>
41 #include "camel-data-cache.h"
42 #include "camel-exception.h"
43 #include "camel-stream-fs.h"
44 #include "camel-stream-mem.h"
45 #include "camel-file-utils.h"
47 extern int camel_verbose_debug;
48 #define dd(x) (camel_verbose_debug?(x):0)
51 /* how many 'bits' of hash are used to key the toplevel directory */
52 #define CAMEL_DATA_CACHE_BITS (6)
53 #define CAMEL_DATA_CACHE_MASK ((1<<CAMEL_DATA_CACHE_BITS)-1)
55 /* timeout before a cache dir is checked again for expired entries,
56 once an hour should be enough */
57 #define CAMEL_DATA_CACHE_CYCLE_TIME (60*60)
59 struct _CamelDataCachePrivate {
60 CamelObjectBag *busy_bag;
63 time_t expire_last[1<<CAMEL_DATA_CACHE_BITS];
66 static CamelObject *camel_data_cache_parent;
68 static void data_cache_class_init(CamelDataCacheClass *klass)
70 camel_data_cache_parent = (CamelObject *)camel_object_get_type ();
73 klass->add = data_cache_add;
74 klass->get = data_cache_get;
75 klass->close = data_cache_close;
76 klass->remove = data_cache_remove;
77 klass->clear = data_cache_clear;
81 static void data_cache_init(CamelDataCache *cdc, CamelDataCacheClass *klass)
83 struct _CamelDataCachePrivate *p;
85 p = cdc->priv = g_malloc0(sizeof(*cdc->priv));
86 p->busy_bag = camel_object_bag_new(g_str_hash, g_str_equal, (CamelCopyFunc)g_strdup, g_free);
89 static void data_cache_finalise(CamelDataCache *cdc)
91 struct _CamelDataCachePrivate *p;
94 camel_object_bag_destroy(p->busy_bag);
101 camel_data_cache_get_type(void)
103 static CamelType camel_data_cache_type = CAMEL_INVALID_TYPE;
105 if (camel_data_cache_type == CAMEL_INVALID_TYPE) {
106 camel_data_cache_type = camel_type_register(
107 CAMEL_OBJECT_TYPE, "CamelDataCache",
108 sizeof (CamelDataCache),
109 sizeof (CamelDataCacheClass),
110 (CamelObjectClassInitFunc) data_cache_class_init,
112 (CamelObjectInitFunc) data_cache_init,
113 (CamelObjectFinalizeFunc) data_cache_finalise);
116 return camel_data_cache_type;
120 * camel_data_cache_new:
121 * @path: Base path of cache, subdirectories will be created here.
122 * @flags: Open flags, none defined.
125 * Create a new data cache.
127 * Return value: A new cache object, or NULL if the base path cannot
131 camel_data_cache_new(const char *path, guint32 flags, CamelException *ex)
135 if (g_mkdir_with_parents (path, 0700) == -1) {
136 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
137 _("Unable to create cache path"));
141 cdc = (CamelDataCache *)camel_object_new(CAMEL_DATA_CACHE_TYPE);
143 cdc->path = g_strdup(path);
145 cdc->expire_age = -1;
146 cdc->expire_access = -1;
152 * camel_data_cache_set_expire_age:
153 * @cdc: A #CamelDataCache
154 * @when: Timeout for age expiry, or -1 to disable.
156 * Set the cache expiration policy for aged entries.
158 * Items in the cache older than @when seconds may be
159 * flushed at any time. Items are expired in a lazy
160 * manner, so it is indeterminate when the items will
161 * physically be removed.
163 * Note you can set both an age and an access limit. The
164 * age acts as a hard limit on cache entries.
167 camel_data_cache_set_expire_age(CamelDataCache *cdc, time_t when)
169 cdc->expire_age = when;
173 * camel_data_cache_set_expire_access:
174 * @cdc: A #CamelDataCache
175 * @when: Timeout for access, or -1 to disable access expiry.
177 * Set the cache expiration policy for access times.
179 * Items in the cache which haven't been accessed for @when
180 * seconds may be expired at any time. Items are expired in a lazy
181 * manner, so it is indeterminate when the items will
182 * physically be removed.
184 * Note you can set both an age and an access limit. The
185 * age acts as a hard limit on cache entries.
188 camel_data_cache_set_expire_access(CamelDataCache *cdc, time_t when)
190 cdc->expire_access = when;
194 data_cache_expire(CamelDataCache *cdc, const char *path, const char *keep, time_t now)
202 dir = g_dir_open(path, 0, NULL);
206 s = g_string_new("");
207 while ( (dname = g_dir_read_name(dir)) ) {
208 if (strcmp(dname, keep) == 0)
211 g_string_printf (s, "%s/%s", path, dname);
212 dd(printf("Checking '%s' for expiry\n", s->str));
213 if (g_stat(s->str, &st) == 0
214 && S_ISREG(st.st_mode)
215 && ((cdc->expire_age != -1 && st.st_mtime + cdc->expire_age < now)
216 || (cdc->expire_access != -1 && st.st_atime + cdc->expire_access < now))) {
217 dd(printf("Has expired! Removing!\n"));
219 stream = camel_object_bag_get(cdc->priv->busy_bag, s->str);
221 camel_object_bag_remove(cdc->priv->busy_bag, stream);
222 camel_object_unref(stream);
226 g_string_free(s, TRUE);
230 /* Since we have to stat the directory anyway, we use this opportunity to
231 lazily expire old data.
232 If it is this directories 'turn', and we haven't done it for CYCLE_TIME seconds,
233 then we perform an expiry run */
235 data_cache_path(CamelDataCache *cdc, int create, const char *path, const char *key)
237 char *dir, *real, *tmp;
240 hash = g_str_hash(key);
241 hash = (hash>>5)&CAMEL_DATA_CACHE_MASK;
242 dir = alloca(strlen(cdc->path) + strlen(path) + 8);
243 sprintf(dir, "%s/%s/%02x", cdc->path, path, hash);
246 if (g_access(dir, F_OK) == -1) {
248 if (access (dir, F_OK) == -1) {
251 g_mkdir_with_parents (dir, 0700);
252 } else if (cdc->priv->expire_inc == hash
253 && (cdc->expire_age != -1 || cdc->expire_access != -1)) {
256 dd(printf("Checking expire cycle time on dir '%s'\n", dir));
258 /* This has a race, but at worst we re-run an expire cycle which is safe */
260 if (cdc->priv->expire_last[hash] + CAMEL_DATA_CACHE_CYCLE_TIME < now) {
261 cdc->priv->expire_last[hash] = now;
262 data_cache_expire(cdc, dir, key, now);
264 cdc->priv->expire_inc = (cdc->priv->expire_inc + 1) & CAMEL_DATA_CACHE_MASK;
267 tmp = camel_file_util_safe_filename(key);
268 real = g_strdup_printf("%s/%s", dir, tmp);
275 * camel_data_cache_add:
276 * @cdc: A #CamelDataCache
277 * @path: Relative path of item to add.
278 * @key: Key of item to add.
281 * Add a new item to the cache.
283 * The key and the path combine to form a unique key used to store
286 * Potentially, expiry processing will be performed while this call
289 * Return value: A CamelStream (file) opened in read-write mode.
290 * The caller must unref this when finished.
293 camel_data_cache_add(CamelDataCache *cdc, const char *path, const char *key, CamelException *ex)
298 real = data_cache_path(cdc, TRUE, path, key);
299 /* need to loop 'cause otherwise we can call bag_add/bag_abort
300 * after bag_reserve returned a pointer, which is an invalid
303 stream = camel_object_bag_reserve(cdc->priv->busy_bag, real);
306 camel_object_bag_remove(cdc->priv->busy_bag, stream);
307 camel_object_unref(stream);
309 } while (stream != NULL);
311 stream = camel_stream_fs_new_with_name(real, O_RDWR|O_CREAT|O_TRUNC, 0600);
313 camel_object_bag_add(cdc->priv->busy_bag, real, stream);
315 camel_object_bag_abort(cdc->priv->busy_bag, real);
323 * camel_data_cache_get:
324 * @cdc: A #CamelDataCache
325 * @path: Path to the (sub) cache the item exists in.
326 * @key: Key for the cache item.
329 * Lookup an item in the cache. If the item exists, a stream
330 * is returned for the item. The stream may be shared by
331 * multiple callers, so ensure the stream is in a valid state
332 * through external locking.
334 * Return value: A cache item, or NULL if the cache item does not exist.
337 camel_data_cache_get(CamelDataCache *cdc, const char *path, const char *key, CamelException *ex)
342 real = data_cache_path(cdc, FALSE, path, key);
343 stream = camel_object_bag_reserve(cdc->priv->busy_bag, real);
345 stream = camel_stream_fs_new_with_name(real, O_RDWR, 0600);
347 camel_object_bag_add(cdc->priv->busy_bag, real, stream);
349 camel_object_bag_abort(cdc->priv->busy_bag, real);
357 * camel_data_cache_remove:
358 * @cdc: A #CamelDataCache
363 * Remove/expire a cache item.
368 camel_data_cache_remove(CamelDataCache *cdc, const char *path, const char *key, CamelException *ex)
374 real = data_cache_path(cdc, FALSE, path, key);
375 stream = camel_object_bag_get(cdc->priv->busy_bag, real);
377 camel_object_bag_remove(cdc->priv->busy_bag, stream);
378 camel_object_unref(stream);
381 /* maybe we were a mem stream */
382 if (g_unlink (real) == -1 && errno != ENOENT) {
383 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
384 _("Could not remove cache entry: %s: %s"),
385 real, g_strerror (errno));
397 * camel_data_cache_rename:
403 * Rename a cache path. All cache items accessed from the old path
404 * are accessible using the new path.
406 * CURRENTLY UNIMPLEMENTED
408 * Return value: -1 on error.
410 int camel_data_cache_rename(CamelDataCache *cache,
411 const char *old, const char *new, CamelException *ex)
413 /* blah dont care yet */
418 * camel_data_cache_clear:
420 * @path: Path to clear, or NULL to clear all items in
424 * Clear all items in a given cache path or all items in the cache.
426 * CURRENTLY_UNIMPLEMENTED
428 * Return value: -1 on error.
431 camel_data_cache_clear(CamelDataCache *cache, const char *path, CamelException *ex)