Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / providers / imap / camel-imap-message-cache.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-imap-message-cache.c: Class for an IMAP message cache */
3
4 /* 
5  * Author: 
6  *   Dan Winship <danw@ximian.com>
7  *
8  * Copyright (C) 2001 Ximian, Inc. (www.ximian.com)
9  *
10  * This program is free software; you can redistribute it and/or 
11  * modify it under the terms of version 2 of the GNU Lesser General Public 
12  * License as published by the Free Software Foundation.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
22  * USA
23  */
24
25 #include <config.h>
26
27 #include <ctype.h>
28 #include <errno.h>
29 #include <string.h>
30 #include <sys/types.h>
31
32 #include <glib/gi18n-lib.h>
33 #include <glib/gstdio.h>
34
35 #include "camel-data-wrapper.h"
36 #include "camel-exception.h"
37 #include "camel-stream-fs.h"
38
39 #include "camel-imap-message-cache.h"
40
41 #ifndef O_BINARY
42 #define O_BINARY 0
43 #endif
44
45 static void finalize (CamelImapMessageCache *cache);
46 static void stream_finalize (CamelObject *stream, gpointer event_data, gpointer user_data);
47
48
49 CamelType
50 camel_imap_message_cache_get_type (void)
51 {
52         static CamelType camel_imap_message_cache_type = CAMEL_INVALID_TYPE;
53         
54         if (camel_imap_message_cache_type == CAMEL_INVALID_TYPE) {
55                 camel_imap_message_cache_type = camel_type_register (
56                         CAMEL_OBJECT_TYPE, "CamelImapMessageCache",
57                         sizeof (CamelImapMessageCache),
58                         sizeof (CamelImapMessageCacheClass),
59                         NULL,
60                         NULL,
61                         NULL,
62                         (CamelObjectFinalizeFunc) finalize);
63         }
64
65         return camel_imap_message_cache_type;
66 }
67
68 static void
69 free_part (gpointer key, gpointer value, gpointer data)
70 {
71         if (value) {
72                 if (strchr (key, '.')) {
73                         camel_object_unhook_event (value, "finalize",
74                                                    stream_finalize, data);
75                         camel_object_unref (value);
76                 } else
77                         g_ptr_array_free (value, TRUE);
78         }
79         g_free (key);
80 }
81
82 static void
83 finalize (CamelImapMessageCache *cache)
84 {
85         if (cache->path)
86                 g_free (cache->path);
87         if (cache->parts) {
88                 g_hash_table_foreach (cache->parts, free_part, cache);
89                 g_hash_table_destroy (cache->parts);
90         }
91         if (cache->cached)
92                 g_hash_table_destroy (cache->cached);
93 }
94
95 static void
96 cache_put (CamelImapMessageCache *cache, const char *uid, const char *key,
97            CamelStream *stream)
98 {
99         char *hash_key;
100         GPtrArray *subparts;
101         gpointer okey, ostream;
102         guint32 uidval;
103
104         uidval = strtoul (uid, NULL, 10);
105         if (uidval > cache->max_uid)
106                 cache->max_uid = uidval;
107
108         subparts = g_hash_table_lookup (cache->parts, uid);
109         if (!subparts) {
110                 subparts = g_ptr_array_new ();
111                 g_hash_table_insert (cache->parts, g_strdup (uid), subparts);
112         }
113
114         if (g_hash_table_lookup_extended (cache->parts, key, &okey, &ostream)) {
115                 if (ostream) {
116                         camel_object_unhook_event (ostream, "finalize",
117                                                    stream_finalize, cache);
118                         g_hash_table_remove (cache->cached, ostream);
119                         camel_object_unref (ostream);
120                 }
121                 hash_key = okey;
122         } else {
123                 hash_key = g_strdup (key);
124                 g_ptr_array_add (subparts, hash_key);
125         }
126
127         g_hash_table_insert (cache->parts, hash_key, stream);
128         g_hash_table_insert (cache->cached, stream, hash_key);
129
130         if (stream) {
131                 camel_object_hook_event (CAMEL_OBJECT (stream), "finalize",
132                                          stream_finalize, cache);
133         }
134 }
135
136 /**
137  * camel_imap_message_cache_new:
138  * @path: directory to use for storage
139  * @summary: CamelFolderSummary for the folder we are caching
140  * @ex: a CamelException
141  *
142  * Return value: a new CamelImapMessageCache object using @path for
143  * storage. If cache files already exist in @path, then any that do not
144  * correspond to messages in @summary will be deleted.
145  **/
146 CamelImapMessageCache *
147 camel_imap_message_cache_new (const char *path, CamelFolderSummary *summary,
148                               CamelException *ex)
149 {
150         CamelImapMessageCache *cache;
151         GDir *dir;
152         const char *dname;
153         char *uid, *p;
154         GPtrArray *deletes;
155         CamelMessageInfo *info;
156         GError *error = NULL;
157
158         dir = g_dir_open (path, 0, &error);
159         if (!dir) {
160                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
161                                       _("Could not open cache directory: %s"),
162                                       error->message);
163                 g_error_free (error);
164                 return NULL;
165         }
166
167         cache = (CamelImapMessageCache *)camel_object_new (CAMEL_IMAP_MESSAGE_CACHE_TYPE);
168         cache->path = g_strdup (path);
169
170         cache->parts = g_hash_table_new (g_str_hash, g_str_equal);
171         cache->cached = g_hash_table_new (NULL, NULL);
172         deletes = g_ptr_array_new ();
173         while ((dname = g_dir_read_name (dir))) {
174                 if (!isdigit (dname[0]))
175                         continue;
176                 p = strchr (dname, '.');
177                 if (p)
178                         uid = g_strndup (dname, p - dname);
179                 else
180                         uid = g_strdup (dname);
181
182                 info = camel_folder_summary_uid (summary, uid);
183                 if (info) {
184                         camel_message_info_free(info);
185                         cache_put (cache, uid, dname, NULL);
186                 } else
187                         g_ptr_array_add (deletes, g_strdup_printf ("%s/%s", cache->path, dname));
188                 g_free (uid);
189         }
190         g_dir_close (dir);
191
192         while (deletes->len) {
193                 g_unlink (deletes->pdata[0]);
194                 g_free (deletes->pdata[0]);
195                 g_ptr_array_remove_index_fast (deletes, 0);
196         }
197         g_ptr_array_free (deletes, TRUE);
198
199         return cache;
200 }
201
202 /**
203  * camel_imap_message_cache_max_uid:
204  * @cache: the cache
205  *
206  * Return value: the largest (real) UID in the cache.
207  **/
208 guint32
209 camel_imap_message_cache_max_uid (CamelImapMessageCache *cache)
210 {
211         return cache->max_uid;
212 }
213
214 /**
215  * camel_imap_message_cache_set_path:
216  * @cache: 
217  * @path: 
218  * 
219  * Set the path used for the message cache.
220  **/
221 void
222 camel_imap_message_cache_set_path (CamelImapMessageCache *cache, const char *path)
223 {
224         g_free(cache->path);
225         cache->path = g_strdup(path);
226 }
227
228 static void
229 stream_finalize (CamelObject *stream, gpointer event_data, gpointer user_data)
230 {
231         CamelImapMessageCache *cache = user_data;
232         char *key;
233
234         key = g_hash_table_lookup (cache->cached, stream);
235         if (!key)
236                 return;
237         g_hash_table_remove (cache->cached, stream);
238         g_hash_table_insert (cache->parts, key, NULL);
239 }
240
241
242 static CamelStream *
243 insert_setup (CamelImapMessageCache *cache, const char *uid, const char *part_spec,
244               char **path, char **key, CamelException *ex)
245 {
246         CamelStream *stream;
247         int fd;
248         
249 #ifdef G_OS_WIN32
250         /* Trailing periods in file names are silently dropped on
251          * Win32, argh. The code in this file requires the period to
252          * be there. So in case part_spec is empty, use a tilde (just
253          * a random choice) instead.
254          */
255         if (!*part_spec)
256                 part_spec = "~";
257 #endif
258         *path = g_strdup_printf ("%s/%s.%s", cache->path, uid, part_spec);
259         *key = strrchr (*path, '/') + 1;
260         stream = g_hash_table_lookup (cache->parts, *key);
261         if (stream)
262                 camel_object_unref (CAMEL_OBJECT (stream));
263         
264         fd = g_open (*path, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0600);
265         if (fd == -1) {
266                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
267                                       _("Failed to cache message %s: %s"),
268                                       uid, g_strerror (errno));
269                 g_free (*path);
270                 return NULL;
271         }
272         
273         return camel_stream_fs_new_with_fd (fd);
274 }
275
276 static CamelStream *
277 insert_abort (char *path, CamelStream *stream)
278 {
279         g_unlink (path);
280         g_free (path);
281         camel_object_unref (CAMEL_OBJECT (stream));
282         return NULL;
283 }
284
285 static CamelStream *
286 insert_finish (CamelImapMessageCache *cache, const char *uid, char *path,
287                char *key, CamelStream *stream)
288 {
289         camel_stream_flush (stream);
290         camel_stream_reset (stream);
291         cache_put (cache, uid, key, stream);
292         g_free (path);
293
294         return stream;
295 }
296
297 /**
298  * camel_imap_message_cache_insert:
299  * @cache: the cache
300  * @uid: UID of the message data to cache
301  * @part_spec: the IMAP part_spec of the data
302  * @data: the data
303  * @len: length of @data
304  *
305  * Caches the provided data into @cache.
306  *
307  * Return value: a CamelStream containing the cached data, which the
308  * caller must unref.
309  **/
310 CamelStream *
311 camel_imap_message_cache_insert (CamelImapMessageCache *cache, const char *uid,
312                                  const char *part_spec, const char *data,
313                                  int len, CamelException *ex)
314 {
315         char *path, *key;
316         CamelStream *stream;
317         
318         stream = insert_setup (cache, uid, part_spec, &path, &key, ex);
319         if (!stream)
320                 return NULL;
321         
322         if (camel_stream_write (stream, data, len) == -1) {
323                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
324                                       _("Failed to cache message %s: %s"),
325                                       uid, g_strerror (errno));
326                 return insert_abort (path, stream);
327         }
328         
329         return insert_finish (cache, uid, path, key, stream);
330 }
331
332 /**
333  * camel_imap_message_cache_insert_stream:
334  * @cache: the cache
335  * @uid: UID of the message data to cache
336  * @part_spec: the IMAP part_spec of the data
337  * @data_stream: the stream to cache
338  *
339  * Caches the provided data into @cache.
340  **/
341 void
342 camel_imap_message_cache_insert_stream (CamelImapMessageCache *cache,
343                                         const char *uid, const char *part_spec,
344                                         CamelStream *data_stream, CamelException *ex)
345 {
346         char *path, *key;
347         CamelStream *stream;
348         
349         stream = insert_setup (cache, uid, part_spec, &path, &key, ex);
350         if (!stream)
351                 return;
352         
353         if (camel_stream_write_to_stream (data_stream, stream) == -1) {
354                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
355                                       _("Failed to cache message %s: %s"),
356                                       uid, g_strerror (errno));
357                 insert_abort (path, stream);
358         } else {
359                 insert_finish (cache, uid, path, key, stream);
360                 camel_object_unref (CAMEL_OBJECT (stream));
361         }
362 }
363
364 /**
365  * camel_imap_message_cache_insert_wrapper:
366  * @cache: the cache
367  * @uid: UID of the message data to cache
368  * @part_spec: the IMAP part_spec of the data
369  * @wrapper: the wrapper to cache
370  *
371  * Caches the provided data into @cache.
372  **/
373 void
374 camel_imap_message_cache_insert_wrapper (CamelImapMessageCache *cache,
375                                          const char *uid, const char *part_spec,
376                                          CamelDataWrapper *wrapper, CamelException *ex)
377 {
378         char *path, *key;
379         CamelStream *stream;
380
381         stream = insert_setup (cache, uid, part_spec, &path, &key, ex);
382         if (!stream)
383                 return;
384         
385         if (camel_data_wrapper_write_to_stream (wrapper, stream) == -1) {
386                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
387                                       _("Failed to cache message %s: %s"),
388                                       uid, g_strerror (errno));
389                 insert_abort (path, stream);
390         } else {
391                 insert_finish (cache, uid, path, key, stream);
392                 camel_object_unref (CAMEL_OBJECT (stream));
393         }
394 }
395
396
397 /**
398  * camel_imap_message_cache_get:
399  * @cache: the cache
400  * @uid: the UID of the data to get
401  * @part_spec: the part_spec of the data to get
402  * @ex: exception
403  *
404  * Return value: a CamelStream containing the cached data (which the
405  * caller must unref), or %NULL if that data is not cached.
406  **/
407 CamelStream *
408 camel_imap_message_cache_get (CamelImapMessageCache *cache, const char *uid,
409                               const char *part_spec, CamelException *ex)
410 {
411         CamelStream *stream;
412         char *path, *key;
413         
414         if (uid[0] == 0)
415                 return NULL;
416         
417 #ifdef G_OS_WIN32
418         /* See comment in insert_setup() */
419         if (!*part_spec)
420                 part_spec = "~";
421 #endif
422         path = g_strdup_printf ("%s/%s.%s", cache->path, uid, part_spec);
423         key = strrchr (path, '/') + 1;
424         stream = g_hash_table_lookup (cache->parts, key);
425         if (stream) {
426                 camel_stream_reset (CAMEL_STREAM (stream));
427                 camel_object_ref (CAMEL_OBJECT (stream));
428                 g_free (path);
429                 return stream;
430         }
431         
432         stream = camel_stream_fs_new_with_name (path, O_RDONLY, 0);
433         if (stream) {
434                 cache_put (cache, uid, key, stream);
435         } else {
436                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
437                                       _("Failed to cache %s: %s"),
438                                       part_spec, g_strerror (errno));
439         }
440         
441         g_free (path);
442         
443         return stream;
444 }
445
446 /**
447  * camel_imap_message_cache_remove:
448  * @cache: the cache
449  * @uid: UID of the data to remove
450  *
451  * Removes all data associated with @uid from @cache.
452  **/
453 void
454 camel_imap_message_cache_remove (CamelImapMessageCache *cache, const char *uid)
455 {
456         GPtrArray *subparts;
457         char *key, *path;
458         CamelObject *stream;
459         int i;
460
461         subparts = g_hash_table_lookup (cache->parts, uid);
462         if (!subparts)
463                 return;
464         for (i = 0; i < subparts->len; i++) {
465                 key = subparts->pdata[i];
466                 path = g_strdup_printf ("%s/%s", cache->path, key);
467                 g_unlink (path);
468                 g_free (path);
469                 stream = g_hash_table_lookup (cache->parts, key);
470                 if (stream) {
471                         camel_object_unhook_event (stream, "finalize",
472                                                    stream_finalize, cache);
473                         camel_object_unref (stream);
474                         g_hash_table_remove (cache->cached, stream);
475                 }
476                 g_hash_table_remove (cache->parts, key);
477                 g_free (key);
478         }
479         g_hash_table_remove (cache->parts, uid);
480         g_ptr_array_free (subparts, TRUE);
481 }
482
483 static void
484 add_uids (gpointer key, gpointer value, gpointer data)
485 {
486         if (!strchr (key, '.'))
487                 g_ptr_array_add (data, key);
488 }
489
490 /**
491  * camel_imap_message_cache_clear:
492  * @cache: the cache
493  *
494  * Removes all cached data from @cache.
495  **/
496 void
497 camel_imap_message_cache_clear (CamelImapMessageCache *cache)
498 {
499         GPtrArray *uids;
500         int i;
501
502         uids = g_ptr_array_new ();
503         g_hash_table_foreach (cache->parts, add_uids, uids);
504
505         for (i = 0; i < uids->len; i++)
506                 camel_imap_message_cache_remove (cache, uids->pdata[i]);
507         g_ptr_array_free (uids, TRUE);
508 }
509
510
511 /**
512  * camel_imap_message_cache_copy:
513  * @source: the source message cache
514  * @source_uid: UID of a message in @source
515  * @dest: the destination message cache
516  * @dest_uid: UID of the message in @dest
517  *
518  * Copies all cached parts from @source_uid in @source to @dest_uid in
519  * @destination.
520  **/
521 void
522 camel_imap_message_cache_copy (CamelImapMessageCache *source,
523                                const char *source_uid,
524                                CamelImapMessageCache *dest,
525                                const char *dest_uid,
526                                CamelException *ex)
527 {
528         GPtrArray *subparts;
529         CamelStream *stream;
530         char *part;
531         int i;
532         
533         subparts = g_hash_table_lookup (source->parts, source_uid);
534         if (!subparts || !subparts->len)
535                 return;
536         
537         for (i = 0; i < subparts->len; i++) {
538                 part = strchr (subparts->pdata[i], '.');
539                 if (!part++)
540                         continue;
541
542                 if ((stream = camel_imap_message_cache_get (source, source_uid, part, ex))) {
543                         camel_imap_message_cache_insert_stream (dest, dest_uid, part, stream, ex);
544                         camel_object_unref (CAMEL_OBJECT (stream));
545                 }
546         }
547 }