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) 1999-2008 Novell, Inc. (www.novell.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>
33 #include <glib/gstdio.h>
34 #include <glib/gi18n-lib.h>
36 #include "camel-data-cache.h"
37 #include "camel-object-bag.h"
38 #include "camel-stream-fs.h"
39 #include "camel-stream-mem.h"
40 #include "camel-file-utils.h"
44 #define CAMEL_DATA_CACHE_GET_PRIVATE(obj) \
45 (G_TYPE_INSTANCE_GET_PRIVATE \
46 ((obj), CAMEL_TYPE_DATA_CACHE, CamelDataCachePrivate))
48 /* how many 'bits' of hash are used to key the toplevel directory */
49 #define CAMEL_DATA_CACHE_BITS (6)
50 #define CAMEL_DATA_CACHE_MASK ((1 << CAMEL_DATA_CACHE_BITS)-1)
52 /* timeout before a cache dir is checked again for expired entries,
53 * once an hour should be enough */
54 #define CAMEL_DATA_CACHE_CYCLE_TIME (60*60)
56 struct _CamelDataCachePrivate {
57 CamelObjectBag *busy_bag;
64 time_t expire_last[1 << CAMEL_DATA_CACHE_BITS];
72 G_DEFINE_TYPE (CamelDataCache, camel_data_cache, CAMEL_TYPE_OBJECT)
75 data_cache_set_property (GObject *object,
80 switch (property_id) {
82 camel_data_cache_set_path (
83 CAMEL_DATA_CACHE (object),
84 g_value_get_string (value));
88 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
92 data_cache_get_property (GObject *object,
97 switch (property_id) {
100 value, camel_data_cache_get_path (
101 CAMEL_DATA_CACHE (object)));
105 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
109 data_cache_finalize (GObject *object)
111 CamelDataCachePrivate *priv;
113 priv = CAMEL_DATA_CACHE_GET_PRIVATE (object);
115 camel_object_bag_destroy (priv->busy_bag);
118 /* Chain up to parent's finalize() method. */
119 G_OBJECT_CLASS (camel_data_cache_parent_class)->finalize (object);
123 camel_data_cache_class_init (CamelDataCacheClass *class)
125 GObjectClass *object_class;
127 g_type_class_add_private (class, sizeof (CamelDataCachePrivate));
129 object_class = G_OBJECT_CLASS (class);
130 object_class->set_property = data_cache_set_property;
131 object_class->get_property = data_cache_get_property;
132 object_class->finalize = data_cache_finalize;
134 g_object_class_install_property (
137 g_param_spec_string (
147 camel_data_cache_init (CamelDataCache *data_cache)
149 CamelObjectBag *busy_bag;
151 busy_bag = camel_object_bag_new (
152 g_str_hash, g_str_equal,
153 (CamelCopyFunc) g_strdup,
156 data_cache->priv = CAMEL_DATA_CACHE_GET_PRIVATE (data_cache);
157 data_cache->priv->busy_bag = busy_bag;
158 data_cache->priv->expire_age = -1;
159 data_cache->priv->expire_access = -1;
163 * camel_data_cache_new:
164 * @path: Base path of cache, subdirectories will be created here.
165 * @error: return location for a #GError, or %NULL
167 * Create a new data cache.
169 * Returns: A new cache object, or NULL if the base path cannot
173 camel_data_cache_new (const gchar *path,
176 g_return_val_if_fail (path != NULL, NULL);
178 if (g_mkdir_with_parents (path, 0700) == -1) {
180 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
181 _("Unable to create cache path"));
185 return g_object_new (CAMEL_TYPE_DATA_CACHE, "path", path, NULL);
189 * camel_data_cache_get_path:
190 * @cdc: a #CamelDataCache
192 * Returns the path to the data cache.
194 * Returns: the path to the data cache
199 camel_data_cache_get_path (CamelDataCache *cdc)
201 g_return_val_if_fail (CAMEL_IS_DATA_CACHE (cdc), NULL);
203 return cdc->priv->path;
207 * camel_data_cache_set_path:
208 * @cdc: a #CamelDataCache
209 * @path: path to the data cache
211 * Sets the path to the data cache.
216 camel_data_cache_set_path (CamelDataCache *cdc,
219 g_return_if_fail (CAMEL_IS_DATA_CACHE (cdc));
220 g_return_if_fail (path != NULL);
222 if (g_strcmp0 (cdc->priv->path, path) == 0)
225 g_free (cdc->priv->path);
226 cdc->priv->path = g_strdup (path);
228 g_object_notify (G_OBJECT (cdc), "path");
232 * camel_data_cache_set_expire_age:
233 * @cdc: A #CamelDataCache
234 * @when: Timeout for age expiry, or -1 to disable.
236 * Set the cache expiration policy for aged entries.
238 * Items in the cache older than @when seconds may be
239 * flushed at any time. Items are expired in a lazy
240 * manner, so it is indeterminate when the items will
241 * physically be removed.
243 * Note you can set both an age and an access limit. The
244 * age acts as a hard limit on cache entries.
247 camel_data_cache_set_expire_age (CamelDataCache *cdc,
250 cdc->priv->expire_age = when;
254 * camel_data_cache_set_expire_access:
255 * @cdc: A #CamelDataCache
256 * @when: Timeout for access, or -1 to disable access expiry.
258 * Set the cache expiration policy for access times.
260 * Items in the cache which haven't been accessed for @when
261 * seconds may be expired at any time. Items are expired in a lazy
262 * manner, so it is indeterminate when the items will
263 * physically be removed.
265 * Note you can set both an age and an access limit. The
266 * age acts as a hard limit on cache entries.
269 camel_data_cache_set_expire_access (CamelDataCache *cdc,
272 cdc->priv->expire_access = when;
276 data_cache_expire (CamelDataCache *cdc,
287 dir = g_dir_open (path, 0, NULL);
291 while ((dname = g_dir_read_name (dir))) {
294 if (keep && strcmp (dname, keep) == 0)
297 dpath = g_build_filename (path, dname, NULL);
299 if (g_stat (dpath, &st) == 0
300 && S_ISREG (st.st_mode)
302 || (cdc->priv->expire_age != -1 && st.st_mtime + cdc->priv->expire_age < now)
303 || (cdc->priv->expire_access != -1 && st.st_atime + cdc->priv->expire_access < now))) {
305 stream = camel_object_bag_get (cdc->priv->busy_bag, dpath);
307 camel_object_bag_remove (cdc->priv->busy_bag, stream);
308 g_object_unref (stream);
317 /* Since we have to stat the directory anyway, we use this opportunity to
318 * lazily expire old data.
319 * If it is this directories 'turn', and we haven't done it for CYCLE_TIME seconds,
320 * then we perform an expiry run */
322 data_cache_path (CamelDataCache *cdc,
327 gchar *dir, *real, *tmp;
330 hash = g_str_hash (key);
331 hash = (hash >> 5) &CAMEL_DATA_CACHE_MASK;
332 dir = alloca (strlen (cdc->priv->path) + strlen (path) + 8);
333 sprintf (dir, "%s/%s/%02x", cdc->priv->path, path, hash);
335 if (g_access (dir, F_OK) == -1) {
337 g_mkdir_with_parents (dir, 0700);
338 } else if (cdc->priv->expire_age != -1 || cdc->priv->expire_access != -1) {
341 /* This has a race, but at worst we re-run an expire cycle which is safe */
343 if (cdc->priv->expire_last[hash] + CAMEL_DATA_CACHE_CYCLE_TIME < now) {
344 cdc->priv->expire_last[hash] = now;
345 data_cache_expire (cdc, dir, key, now, FALSE);
349 tmp = camel_file_util_safe_filename (key);
350 real = g_strdup_printf ("%s/%s", dir, tmp);
357 * camel_data_cache_add:
358 * @cdc: A #CamelDataCache
359 * @path: Relative path of item to add.
360 * @key: Key of item to add.
361 * @error: return location for a #GError, or %NULL
363 * Add a new item to the cache.
365 * The key and the path combine to form a unique key used to store
368 * Potentially, expiry processing will be performed while this call
371 * Returns: A CamelStream (file) opened in read-write mode.
372 * The caller must unref this when finished.
375 camel_data_cache_add (CamelDataCache *cdc,
383 real = data_cache_path (cdc, TRUE, path, key);
384 /* need to loop 'cause otherwise we can call bag_add/bag_abort
385 * after bag_reserve returned a pointer, which is an invalid
388 stream = camel_object_bag_reserve (cdc->priv->busy_bag, real);
391 camel_object_bag_remove (cdc->priv->busy_bag, stream);
392 g_object_unref (stream);
394 } while (stream != NULL);
396 stream = camel_stream_fs_new_with_name (
397 real, O_RDWR | O_CREAT | O_TRUNC, 0600, error);
399 camel_object_bag_add (cdc->priv->busy_bag, real, stream);
401 camel_object_bag_abort (cdc->priv->busy_bag, real);
409 * camel_data_cache_get:
410 * @cdc: A #CamelDataCache
411 * @path: Path to the (sub) cache the item exists in.
412 * @key: Key for the cache item.
413 * @error: return location for a #GError, or %NULL
415 * Lookup an item in the cache. If the item exists, a stream
416 * is returned for the item. The stream may be shared by
417 * multiple callers, so ensure the stream is in a valid state
418 * through external locking.
420 * Returns: A cache item, or NULL if the cache item does not exist.
423 camel_data_cache_get (CamelDataCache *cdc,
431 real = data_cache_path (cdc, FALSE, path, key);
432 stream = camel_object_bag_reserve (cdc->priv->busy_bag, real);
433 if (stream == NULL) {
436 /* An empty cache file is useless. Return an error. */
437 if (g_stat (real, &st) == 0 && st.st_size == 0) {
439 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
440 "%s: %s", _("Empty cache file"), real);
442 stream = camel_stream_fs_new_with_name (
443 real, O_RDWR, 0600, error);
447 camel_object_bag_add (cdc->priv->busy_bag, real, stream);
449 camel_object_bag_abort (cdc->priv->busy_bag, real);
457 * camel_data_cache_get_filename:
458 * @cdc: A #CamelDataCache
459 * @path: Path to the (sub) cache the item exists in.
460 * @key: Key for the cache item.
462 * Lookup the filename for an item in the cache
464 * Returns: The filename for a cache item
469 camel_data_cache_get_filename (CamelDataCache *cdc,
473 return data_cache_path (cdc, FALSE, path, key);
477 * camel_data_cache_remove:
478 * @cdc: A #CamelDataCache
481 * @error: return location for a #GError, or %NULL
483 * Remove/expire a cache item.
488 camel_data_cache_remove (CamelDataCache *cdc,
497 real = data_cache_path (cdc, FALSE, path, key);
498 stream = camel_object_bag_get (cdc->priv->busy_bag, real);
500 camel_object_bag_remove (cdc->priv->busy_bag, stream);
501 g_object_unref (stream);
504 /* maybe we were a mem stream */
505 if (g_unlink (real) == -1 && errno != ENOENT) {
508 g_io_error_from_errno (errno),
509 _("Could not remove cache entry: %s: %s"),
510 real, g_strerror (errno));
522 * camel_data_cache_clear:
523 * @cdc: a #CamelDataCache
524 * @path: Path to the (sub) cache the item exists in.
526 * Clear cache's content in @path.
531 camel_data_cache_clear (CamelDataCache *cdc,
539 g_return_if_fail (cdc != NULL);
540 g_return_if_fail (path != NULL);
542 base_dir = g_build_filename (cdc->priv->path, path, NULL);
544 dir = g_dir_open (base_dir, 0, NULL);
550 while ((dname = g_dir_read_name (dir))) {
553 dpath = g_build_filename (base_dir, dname, NULL);
555 if (g_stat (dpath, &st) == 0
556 && S_ISDIR (st.st_mode)
557 && !g_str_equal (dname, ".")
558 && !g_str_equal (dname, "..")) {
559 data_cache_expire (cdc, dpath, NULL, -1, TRUE);