Extending test-client-custom-summary to try e_book_client_get_contacts_uids()
[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) 1999-2008 Novell, Inc. (www.novell.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
33 #include <glib/gstdio.h>
34 #include <glib/gi18n-lib.h>
35
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"
41
42 #define d(x)
43
44 #define CAMEL_DATA_CACHE_GET_PRIVATE(obj) \
45         (G_TYPE_INSTANCE_GET_PRIVATE \
46         ((obj), CAMEL_TYPE_DATA_CACHE, CamelDataCachePrivate))
47
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)
51
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)
55
56 struct _CamelDataCachePrivate {
57         CamelObjectBag *busy_bag;
58
59         gchar *path;
60
61         time_t expire_age;
62         time_t expire_access;
63
64         time_t expire_last[1 << CAMEL_DATA_CACHE_BITS];
65 };
66
67 enum {
68         PROP_0,
69         PROP_PATH
70 };
71
72 G_DEFINE_TYPE (CamelDataCache, camel_data_cache, CAMEL_TYPE_OBJECT)
73
74 static void
75 data_cache_set_property (GObject *object,
76                          guint property_id,
77                          const GValue *value,
78                          GParamSpec *pspec)
79 {
80         switch (property_id) {
81                 case PROP_PATH:
82                         camel_data_cache_set_path (
83                                 CAMEL_DATA_CACHE (object),
84                                 g_value_get_string (value));
85                         return;
86         }
87
88         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
89 }
90
91 static void
92 data_cache_get_property (GObject *object,
93                          guint property_id,
94                          GValue *value,
95                          GParamSpec *pspec)
96 {
97         switch (property_id) {
98                 case PROP_PATH:
99                         g_value_set_string (
100                                 value, camel_data_cache_get_path (
101                                 CAMEL_DATA_CACHE (object)));
102                         return;
103         }
104
105         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
106 }
107
108 static void
109 data_cache_finalize (GObject *object)
110 {
111         CamelDataCachePrivate *priv;
112
113         priv = CAMEL_DATA_CACHE_GET_PRIVATE (object);
114
115         camel_object_bag_destroy (priv->busy_bag);
116         g_free (priv->path);
117
118         /* Chain up to parent's finalize() method. */
119         G_OBJECT_CLASS (camel_data_cache_parent_class)->finalize (object);
120 }
121
122 static void
123 camel_data_cache_class_init (CamelDataCacheClass *class)
124 {
125         GObjectClass *object_class;
126
127         g_type_class_add_private (class, sizeof (CamelDataCachePrivate));
128
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;
133
134         g_object_class_install_property (
135                 object_class,
136                 PROP_PATH,
137                 g_param_spec_string (
138                         "path",
139                         "Path",
140                         NULL,
141                         NULL,
142                         G_PARAM_READWRITE |
143                         G_PARAM_CONSTRUCT));
144 }
145
146 static void
147 camel_data_cache_init (CamelDataCache *data_cache)
148 {
149         CamelObjectBag *busy_bag;
150
151         busy_bag = camel_object_bag_new (
152                 g_str_hash, g_str_equal,
153                 (CamelCopyFunc) g_strdup,
154                 (GFreeFunc) g_free);
155
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;
160 }
161
162 /**
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
166  *
167  * Create a new data cache.
168  *
169  * Returns: A new cache object, or NULL if the base path cannot
170  * be written to.
171  **/
172 CamelDataCache *
173 camel_data_cache_new (const gchar *path,
174                       GError **error)
175 {
176         g_return_val_if_fail (path != NULL, NULL);
177
178         if (g_mkdir_with_parents (path, 0700) == -1) {
179                 g_set_error (
180                         error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
181                         _("Unable to create cache path"));
182                 return NULL;
183         }
184
185         return g_object_new (CAMEL_TYPE_DATA_CACHE, "path", path, NULL);
186 }
187
188 /**
189  * camel_data_cache_get_path:
190  * @cdc: a #CamelDataCache
191  *
192  * Returns the path to the data cache.
193  *
194  * Returns: the path to the data cache
195  *
196  * Since: 2.32
197  **/
198 const gchar *
199 camel_data_cache_get_path (CamelDataCache *cdc)
200 {
201         g_return_val_if_fail (CAMEL_IS_DATA_CACHE (cdc), NULL);
202
203         return cdc->priv->path;
204 }
205
206 /**
207  * camel_data_cache_set_path:
208  * @cdc: a #CamelDataCache
209  * @path: path to the data cache
210  *
211  * Sets the path to the data cache.
212  *
213  * Since: 2.32
214  **/
215 void
216 camel_data_cache_set_path (CamelDataCache *cdc,
217                            const gchar *path)
218 {
219         g_return_if_fail (CAMEL_IS_DATA_CACHE (cdc));
220         g_return_if_fail (path != NULL);
221
222         if (g_strcmp0 (cdc->priv->path, path) == 0)
223                 return;
224
225         g_free (cdc->priv->path);
226         cdc->priv->path = g_strdup (path);
227
228         g_object_notify (G_OBJECT (cdc), "path");
229 }
230
231 /**
232  * camel_data_cache_set_expire_age:
233  * @cdc: A #CamelDataCache
234  * @when: Timeout for age expiry, or -1 to disable.
235  *
236  * Set the cache expiration policy for aged entries.
237  *
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.
242  *
243  * Note you can set both an age and an access limit.  The
244  * age acts as a hard limit on cache entries.
245  **/
246 void
247 camel_data_cache_set_expire_age (CamelDataCache *cdc,
248                                  time_t when)
249 {
250         cdc->priv->expire_age = when;
251 }
252
253 /**
254  * camel_data_cache_set_expire_access:
255  * @cdc: A #CamelDataCache
256  * @when: Timeout for access, or -1 to disable access expiry.
257  *
258  * Set the cache expiration policy for access times.
259  *
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.
264  *
265  * Note you can set both an age and an access limit.  The
266  * age acts as a hard limit on cache entries.
267  **/
268 void
269 camel_data_cache_set_expire_access (CamelDataCache *cdc,
270                                     time_t when)
271 {
272         cdc->priv->expire_access = when;
273 }
274
275 static void
276 data_cache_expire (CamelDataCache *cdc,
277                    const gchar *path,
278                    const gchar *keep,
279                    time_t now,
280                    gboolean expire_all)
281 {
282         GDir *dir;
283         const gchar *dname;
284         struct stat st;
285         CamelStream *stream;
286
287         dir = g_dir_open (path, 0, NULL);
288         if (dir == NULL)
289                 return;
290
291         while ((dname = g_dir_read_name (dir))) {
292                 gchar *dpath;
293
294                 if (keep && strcmp (dname, keep) == 0)
295                         continue;
296
297                 dpath = g_build_filename (path, dname, NULL);
298
299                 if (g_stat (dpath, &st) == 0
300                     && S_ISREG (st.st_mode)
301                     && (expire_all
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))) {
304                         g_unlink (dpath);
305                         stream = camel_object_bag_get (cdc->priv->busy_bag, dpath);
306                         if (stream) {
307                                 camel_object_bag_remove (cdc->priv->busy_bag, stream);
308                                 g_object_unref (stream);
309                         }
310                 }
311
312                 g_free (dpath);
313         }
314         g_dir_close (dir);
315 }
316
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 */
321 static gchar *
322 data_cache_path (CamelDataCache *cdc,
323                  gint create,
324                  const gchar *path,
325                  const gchar *key)
326 {
327         gchar *dir, *real, *tmp;
328         guint32 hash;
329
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);
334
335         if (g_access (dir, F_OK) == -1) {
336                 if (create)
337                         g_mkdir_with_parents (dir, 0700);
338         } else if (cdc->priv->expire_age != -1 || cdc->priv->expire_access != -1) {
339                 time_t now;
340
341                 /* This has a race, but at worst we re-run an expire cycle which is safe */
342                 now = time (NULL);
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);
346                 }
347         }
348
349         tmp = camel_file_util_safe_filename (key);
350         real = g_strdup_printf ("%s/%s", dir, tmp);
351         g_free (tmp);
352
353         return real;
354 }
355
356 /**
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
362  *
363  * Add a new item to the cache.
364  *
365  * The key and the path combine to form a unique key used to store
366  * the item.
367  *
368  * Potentially, expiry processing will be performed while this call
369  * is executing.
370  *
371  * Returns: A CamelStream (file) opened in read-write mode.
372  * The caller must unref this when finished.
373  **/
374 CamelStream *
375 camel_data_cache_add (CamelDataCache *cdc,
376                       const gchar *path,
377                       const gchar *key,
378                       GError **error)
379 {
380         gchar *real;
381         CamelStream *stream;
382
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
386          * sequence. */
387         do {
388                 stream = camel_object_bag_reserve (cdc->priv->busy_bag, real);
389                 if (stream) {
390                         g_unlink (real);
391                         camel_object_bag_remove (cdc->priv->busy_bag, stream);
392                         g_object_unref (stream);
393                 }
394         } while (stream != NULL);
395
396         stream = camel_stream_fs_new_with_name (
397                 real, O_RDWR | O_CREAT | O_TRUNC, 0600, error);
398         if (stream)
399                 camel_object_bag_add (cdc->priv->busy_bag, real, stream);
400         else
401                 camel_object_bag_abort (cdc->priv->busy_bag, real);
402
403         g_free (real);
404
405         return stream;
406 }
407
408 /**
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
414  *
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.
419  *
420  * Returns: A cache item, or NULL if the cache item does not exist.
421  **/
422 CamelStream *
423 camel_data_cache_get (CamelDataCache *cdc,
424                       const gchar *path,
425                       const gchar *key,
426                       GError **error)
427 {
428         gchar *real;
429         CamelStream *stream;
430
431         real = data_cache_path (cdc, FALSE, path, key);
432         stream = camel_object_bag_reserve (cdc->priv->busy_bag, real);
433         if (stream == NULL) {
434                 struct stat st;
435
436                 /* An empty cache file is useless.  Return an error. */
437                 if (g_stat (real, &st) == 0 && st.st_size == 0) {
438                         g_set_error (
439                                 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
440                                 "%s: %s", _("Empty cache file"), real);
441                 } else {
442                         stream = camel_stream_fs_new_with_name (
443                                 real, O_RDWR, 0600, error);
444                 }
445
446                 if (stream != NULL)
447                         camel_object_bag_add (cdc->priv->busy_bag, real, stream);
448                 else
449                         camel_object_bag_abort (cdc->priv->busy_bag, real);
450         }
451         g_free (real);
452
453         return stream;
454 }
455
456 /**
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.
461  *
462  * Lookup the filename for an item in the cache
463  *
464  * Returns: The filename for a cache item
465  *
466  * Since: 2.26
467  **/
468 gchar *
469 camel_data_cache_get_filename (CamelDataCache *cdc,
470                                const gchar *path,
471                                const gchar *key)
472 {
473         return data_cache_path (cdc, FALSE, path, key);
474 }
475
476 /**
477  * camel_data_cache_remove:
478  * @cdc: A #CamelDataCache
479  * @path:
480  * @key:
481  * @error: return location for a #GError, or %NULL
482  *
483  * Remove/expire a cache item.
484  *
485  * Returns:
486  **/
487 gint
488 camel_data_cache_remove (CamelDataCache *cdc,
489                          const gchar *path,
490                          const gchar *key,
491                          GError **error)
492 {
493         CamelStream *stream;
494         gchar *real;
495         gint ret;
496
497         real = data_cache_path (cdc, FALSE, path, key);
498         stream = camel_object_bag_get (cdc->priv->busy_bag, real);
499         if (stream) {
500                 camel_object_bag_remove (cdc->priv->busy_bag, stream);
501                 g_object_unref (stream);
502         }
503
504         /* maybe we were a mem stream */
505         if (g_unlink (real) == -1 && errno != ENOENT) {
506                 g_set_error (
507                         error, G_IO_ERROR,
508                         g_io_error_from_errno (errno),
509                         _("Could not remove cache entry: %s: %s"),
510                         real, g_strerror (errno));
511                 ret = -1;
512         } else {
513                 ret = 0;
514         }
515
516         g_free (real);
517
518         return ret;
519 }
520
521 /**
522  * camel_data_cache_clear:
523  * @cdc: a #CamelDataCache
524  * @path: Path to the (sub) cache the item exists in.
525  *
526  * Clear cache's content in @path.
527  *
528  * Since: 3.2
529  **/
530 void
531 camel_data_cache_clear (CamelDataCache *cdc,
532                         const gchar *path)
533 {
534         gchar *base_dir;
535         GDir *dir;
536         const gchar *dname;
537         struct stat st;
538
539         g_return_if_fail (cdc != NULL);
540         g_return_if_fail (path != NULL);
541
542         base_dir = g_build_filename (cdc->priv->path, path, NULL);
543
544         dir = g_dir_open (base_dir, 0, NULL);
545         if (dir == NULL) {
546                 g_free (base_dir);
547                 return;
548         }
549
550         while ((dname = g_dir_read_name (dir))) {
551                 gchar *dpath;
552
553                 dpath = g_build_filename (base_dir, dname, NULL);
554
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);
560                 }
561
562                 g_free (dpath);
563         }
564
565         g_dir_close (dir);
566         g_free (base_dir);
567 }