Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / camel-data-cache.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-message-cache.c: Class for a Camel cache.
3  *
4  * Authors: Michael Zucchi <notzed@ximian.com>
5  *
6  * Copyright (C) 2001 Ximian, Inc. (www.ximian.com)
7  *
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.
11  *
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.
16  *
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
20  * USA
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <sys/types.h>
28 #include <ctype.h>
29 #include <errno.h>
30 #include <string.h>
31 #include <stdlib.h>
32 #ifdef HAVE_ALLOCA_H
33 #include <alloca.h>
34 #endif
35
36 #include <glib.h>
37 #include <glib/gstdio.h>
38 #include <glib/gi18n-lib.h>
39
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"
46
47 extern int camel_verbose_debug;
48 #define dd(x) (camel_verbose_debug?(x):0)
49 #define d(x)
50
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)
54
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)
58
59 struct _CamelDataCachePrivate {
60         CamelObjectBag *busy_bag;
61
62         int expire_inc;
63         time_t expire_last[1<<CAMEL_DATA_CACHE_BITS];
64 };
65
66 static CamelObject *camel_data_cache_parent;
67
68 static void data_cache_class_init(CamelDataCacheClass *klass)
69 {
70         camel_data_cache_parent = (CamelObject *)camel_object_get_type ();
71
72 #if 0
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;
78 #endif
79 }
80
81 static void data_cache_init(CamelDataCache *cdc, CamelDataCacheClass *klass)
82 {
83         struct _CamelDataCachePrivate *p;
84
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);
87 }
88
89 static void data_cache_finalise(CamelDataCache *cdc)
90 {
91         struct _CamelDataCachePrivate *p;
92
93         p = cdc->priv;
94         camel_object_bag_destroy(p->busy_bag);
95         g_free(p);
96         
97         g_free (cdc->path);
98 }
99
100 CamelType
101 camel_data_cache_get_type(void)
102 {
103         static CamelType camel_data_cache_type = CAMEL_INVALID_TYPE;
104         
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,
111                         NULL,
112                         (CamelObjectInitFunc) data_cache_init,
113                         (CamelObjectFinalizeFunc) data_cache_finalise);
114         }
115
116         return camel_data_cache_type;
117 }
118
119 /**
120  * camel_data_cache_new:
121  * @path: Base path of cache, subdirectories will be created here.
122  * @flags: Open flags, none defined.
123  * @ex: 
124  * 
125  * Create a new data cache.
126  * 
127  * Return value: A new cache object, or NULL if the base path cannot
128  * be written to.
129  **/
130 CamelDataCache *
131 camel_data_cache_new(const char *path, guint32 flags, CamelException *ex)
132 {
133         CamelDataCache *cdc;
134
135         if (g_mkdir_with_parents (path, 0700) == -1) {
136                 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
137                                      _("Unable to create cache path"));
138                 return NULL;
139         }
140
141         cdc = (CamelDataCache *)camel_object_new(CAMEL_DATA_CACHE_TYPE);
142
143         cdc->path = g_strdup(path);
144         cdc->flags = flags;
145         cdc->expire_age = -1;
146         cdc->expire_access = -1;
147
148         return cdc;
149 }
150
151 /**
152  * camel_data_cache_set_expire_age:
153  * @cdc: A #CamelDataCache
154  * @when: Timeout for age expiry, or -1 to disable.
155  * 
156  * Set the cache expiration policy for aged entries.
157  * 
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.
162  *
163  * Note you can set both an age and an access limit.  The
164  * age acts as a hard limit on cache entries.
165  **/
166 void
167 camel_data_cache_set_expire_age(CamelDataCache *cdc, time_t when)
168 {
169         cdc->expire_age = when;
170 }
171
172 /**
173  * camel_data_cache_set_expire_access:
174  * @cdc: A #CamelDataCache
175  * @when: Timeout for access, or -1 to disable access expiry.
176  * 
177  * Set the cache expiration policy for access times.
178  *
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.
183  *
184  * Note you can set both an age and an access limit.  The
185  * age acts as a hard limit on cache entries.
186  **/
187 void
188 camel_data_cache_set_expire_access(CamelDataCache *cdc, time_t when)
189 {
190         cdc->expire_access = when;
191 }
192
193 static void
194 data_cache_expire(CamelDataCache *cdc, const char *path, const char *keep, time_t now)
195 {
196         GDir *dir;
197         const char *dname;
198         GString *s;
199         struct stat st;
200         CamelStream *stream;
201
202         dir = g_dir_open(path, 0, NULL);
203         if (dir == NULL)
204                 return;
205
206         s = g_string_new("");
207         while ( (dname = g_dir_read_name(dir)) ) {
208                 if (strcmp(dname, keep) == 0)
209                         continue;
210                 
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"));
218                         g_unlink(s->str);
219                         stream = camel_object_bag_get(cdc->priv->busy_bag, s->str);
220                         if (stream) {
221                                 camel_object_bag_remove(cdc->priv->busy_bag, stream);
222                                 camel_object_unref(stream);
223                         }
224                 }
225         }
226         g_string_free(s, TRUE);
227         g_dir_close(dir);
228 }
229
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 */
234 static char *
235 data_cache_path(CamelDataCache *cdc, int create, const char *path, const char *key)
236 {
237         char *dir, *real, *tmp;
238         guint32 hash;
239
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);
244         
245 #ifdef G_OS_WIN32 
246         if (g_access(dir, F_OK) == -1) {
247 #else
248         if (access (dir, F_OK) == -1) {
249 #endif
250                 if (create)
251                         g_mkdir_with_parents (dir, 0700);
252         } else if (cdc->priv->expire_inc == hash
253                    && (cdc->expire_age != -1 || cdc->expire_access != -1)) {
254                 time_t now;
255
256                 dd(printf("Checking expire cycle time on dir '%s'\n", dir));
257
258                 /* This has a race, but at worst we re-run an expire cycle which is safe */
259                 now = time(0);
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);
263                 }
264                 cdc->priv->expire_inc = (cdc->priv->expire_inc + 1) & CAMEL_DATA_CACHE_MASK;
265         }
266
267         tmp = camel_file_util_safe_filename(key);
268         real = g_strdup_printf("%s/%s", dir, tmp);
269         g_free(tmp);
270
271         return real;
272 }
273
274 /**
275  * camel_data_cache_add:
276  * @cdc: A #CamelDataCache
277  * @path: Relative path of item to add.
278  * @key: Key of item to add.
279  * @ex: 
280  * 
281  * Add a new item to the cache.
282  *
283  * The key and the path combine to form a unique key used to store
284  * the item.
285  * 
286  * Potentially, expiry processing will be performed while this call
287  * is executing.
288  *
289  * Return value: A CamelStream (file) opened in read-write mode.
290  * The caller must unref this when finished.
291  **/
292 CamelStream *
293 camel_data_cache_add(CamelDataCache *cdc, const char *path, const char *key, CamelException *ex)
294 {
295         char *real;
296         CamelStream *stream;
297
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
301          * sequence. */
302         do {
303                 stream = camel_object_bag_reserve(cdc->priv->busy_bag, real);
304                 if (stream) {
305                         g_unlink(real);
306                         camel_object_bag_remove(cdc->priv->busy_bag, stream);
307                         camel_object_unref(stream);
308                 }
309         } while (stream != NULL);
310
311         stream = camel_stream_fs_new_with_name(real, O_RDWR|O_CREAT|O_TRUNC, 0600);
312         if (stream)
313                 camel_object_bag_add(cdc->priv->busy_bag, real, stream);
314         else
315                 camel_object_bag_abort(cdc->priv->busy_bag, real);
316
317         g_free(real);
318
319         return stream;
320 }
321
322 /**
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.
327  * @ex: 
328  * 
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.
333  * 
334  * Return value: A cache item, or NULL if the cache item does not exist.
335  **/
336 CamelStream *
337 camel_data_cache_get(CamelDataCache *cdc, const char *path, const char *key, CamelException *ex)
338 {
339         char *real;
340         CamelStream *stream;
341
342         real = data_cache_path(cdc, FALSE, path, key);
343         stream = camel_object_bag_reserve(cdc->priv->busy_bag, real);
344         if (!stream) {
345                 stream = camel_stream_fs_new_with_name(real, O_RDWR, 0600);
346                 if (stream)
347                         camel_object_bag_add(cdc->priv->busy_bag, real, stream);
348                 else
349                         camel_object_bag_abort(cdc->priv->busy_bag, real);
350         }
351         g_free(real);
352
353         return stream;
354 }
355
356 /**
357  * camel_data_cache_remove:
358  * @cdc: A #CamelDataCache
359  * @path: 
360  * @key: 
361  * @ex: 
362  * 
363  * Remove/expire a cache item.
364  * 
365  * Return value: 
366  **/
367 int
368 camel_data_cache_remove(CamelDataCache *cdc, const char *path, const char *key, CamelException *ex)
369 {
370         CamelStream *stream;
371         char *real;
372         int ret;
373
374         real = data_cache_path(cdc, FALSE, path, key);
375         stream = camel_object_bag_get(cdc->priv->busy_bag, real);
376         if (stream) {
377                 camel_object_bag_remove(cdc->priv->busy_bag, stream);
378                 camel_object_unref(stream);
379         }
380
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));
386                 ret = -1;
387         } else {
388                 ret = 0;
389         }
390
391         g_free(real);
392
393         return ret;
394 }
395
396 /**
397  * camel_data_cache_rename:
398  * @cache: 
399  * @old: 
400  * @new: 
401  * @ex: 
402  * 
403  * Rename a cache path.  All cache items accessed from the old path
404  * are accessible using the new path.
405  *
406  * CURRENTLY UNIMPLEMENTED
407  * 
408  * Return value: -1 on error.
409  **/
410 int camel_data_cache_rename(CamelDataCache *cache,
411                             const char *old, const char *new, CamelException *ex)
412 {
413         /* blah dont care yet */
414         return -1;
415 }
416
417 /**
418  * camel_data_cache_clear:
419  * @cache: 
420  * @path: Path to clear, or NULL to clear all items in
421  * all paths.
422  * @ex: 
423  * 
424  * Clear all items in a given cache path or all items in the cache.
425  * 
426  * CURRENTLY_UNIMPLEMENTED
427  *
428  * Return value: -1 on error.
429  **/
430 int
431 camel_data_cache_clear(CamelDataCache *cache, const char *path, CamelException *ex)
432 {
433         /* nor for this? */
434         return -1;
435 }