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 */
6 * Dan Winship <danw@ximian.com>
8 * Copyright (C) 2001 Ximian, Inc. (www.ximian.com)
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.
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.
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
30 #include <sys/types.h>
32 #include <glib/gi18n-lib.h>
33 #include <glib/gstdio.h>
35 #include "camel-data-wrapper.h"
36 #include "camel-exception.h"
37 #include "camel-stream-fs.h"
39 #include "camel-imap-message-cache.h"
45 static void finalize (CamelImapMessageCache *cache);
46 static void stream_finalize (CamelObject *stream, gpointer event_data, gpointer user_data);
50 camel_imap_message_cache_get_type (void)
52 static CamelType camel_imap_message_cache_type = CAMEL_INVALID_TYPE;
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),
62 (CamelObjectFinalizeFunc) finalize);
65 return camel_imap_message_cache_type;
69 free_part (gpointer key, gpointer value, gpointer data)
72 if (strchr (key, '.')) {
73 camel_object_unhook_event (value, "finalize",
74 stream_finalize, data);
75 camel_object_unref (value);
77 g_ptr_array_free (value, TRUE);
83 finalize (CamelImapMessageCache *cache)
88 g_hash_table_foreach (cache->parts, free_part, cache);
89 g_hash_table_destroy (cache->parts);
92 g_hash_table_destroy (cache->cached);
96 cache_put (CamelImapMessageCache *cache, const char *uid, const char *key,
101 gpointer okey, ostream;
104 uidval = strtoul (uid, NULL, 10);
105 if (uidval > cache->max_uid)
106 cache->max_uid = uidval;
108 subparts = g_hash_table_lookup (cache->parts, uid);
110 subparts = g_ptr_array_new ();
111 g_hash_table_insert (cache->parts, g_strdup (uid), subparts);
114 if (g_hash_table_lookup_extended (cache->parts, key, &okey, &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);
123 hash_key = g_strdup (key);
124 g_ptr_array_add (subparts, hash_key);
127 g_hash_table_insert (cache->parts, hash_key, stream);
128 g_hash_table_insert (cache->cached, stream, hash_key);
131 camel_object_hook_event (CAMEL_OBJECT (stream), "finalize",
132 stream_finalize, cache);
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
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.
146 CamelImapMessageCache *
147 camel_imap_message_cache_new (const char *path, CamelFolderSummary *summary,
150 CamelImapMessageCache *cache;
155 CamelMessageInfo *info;
156 GError *error = NULL;
158 dir = g_dir_open (path, 0, &error);
160 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
161 _("Could not open cache directory: %s"),
163 g_error_free (error);
167 cache = (CamelImapMessageCache *)camel_object_new (CAMEL_IMAP_MESSAGE_CACHE_TYPE);
168 cache->path = g_strdup (path);
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]))
176 p = strchr (dname, '.');
178 uid = g_strndup (dname, p - dname);
180 uid = g_strdup (dname);
182 info = camel_folder_summary_uid (summary, uid);
184 camel_message_info_free(info);
185 cache_put (cache, uid, dname, NULL);
187 g_ptr_array_add (deletes, g_strdup_printf ("%s/%s", cache->path, dname));
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);
197 g_ptr_array_free (deletes, TRUE);
203 * camel_imap_message_cache_max_uid:
206 * Return value: the largest (real) UID in the cache.
209 camel_imap_message_cache_max_uid (CamelImapMessageCache *cache)
211 return cache->max_uid;
215 * camel_imap_message_cache_set_path:
219 * Set the path used for the message cache.
222 camel_imap_message_cache_set_path (CamelImapMessageCache *cache, const char *path)
225 cache->path = g_strdup(path);
229 stream_finalize (CamelObject *stream, gpointer event_data, gpointer user_data)
231 CamelImapMessageCache *cache = user_data;
234 key = g_hash_table_lookup (cache->cached, stream);
237 g_hash_table_remove (cache->cached, stream);
238 g_hash_table_insert (cache->parts, key, NULL);
243 insert_setup (CamelImapMessageCache *cache, const char *uid, const char *part_spec,
244 char **path, char **key, CamelException *ex)
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.
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);
262 camel_object_unref (CAMEL_OBJECT (stream));
264 fd = g_open (*path, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0600);
266 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
267 _("Failed to cache message %s: %s"),
268 uid, g_strerror (errno));
273 return camel_stream_fs_new_with_fd (fd);
277 insert_abort (char *path, CamelStream *stream)
281 camel_object_unref (CAMEL_OBJECT (stream));
286 insert_finish (CamelImapMessageCache *cache, const char *uid, char *path,
287 char *key, CamelStream *stream)
289 camel_stream_flush (stream);
290 camel_stream_reset (stream);
291 cache_put (cache, uid, key, stream);
298 * camel_imap_message_cache_insert:
300 * @uid: UID of the message data to cache
301 * @part_spec: the IMAP part_spec of the data
303 * @len: length of @data
305 * Caches the provided data into @cache.
307 * Return value: a CamelStream containing the cached data, which the
311 camel_imap_message_cache_insert (CamelImapMessageCache *cache, const char *uid,
312 const char *part_spec, const char *data,
313 int len, CamelException *ex)
318 stream = insert_setup (cache, uid, part_spec, &path, &key, ex);
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);
329 return insert_finish (cache, uid, path, key, stream);
333 * camel_imap_message_cache_insert_stream:
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
339 * Caches the provided data into @cache.
342 camel_imap_message_cache_insert_stream (CamelImapMessageCache *cache,
343 const char *uid, const char *part_spec,
344 CamelStream *data_stream, CamelException *ex)
349 stream = insert_setup (cache, uid, part_spec, &path, &key, ex);
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);
359 insert_finish (cache, uid, path, key, stream);
360 camel_object_unref (CAMEL_OBJECT (stream));
365 * camel_imap_message_cache_insert_wrapper:
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
371 * Caches the provided data into @cache.
374 camel_imap_message_cache_insert_wrapper (CamelImapMessageCache *cache,
375 const char *uid, const char *part_spec,
376 CamelDataWrapper *wrapper, CamelException *ex)
381 stream = insert_setup (cache, uid, part_spec, &path, &key, ex);
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);
391 insert_finish (cache, uid, path, key, stream);
392 camel_object_unref (CAMEL_OBJECT (stream));
398 * camel_imap_message_cache_get:
400 * @uid: the UID of the data to get
401 * @part_spec: the part_spec of the data to get
404 * Return value: a CamelStream containing the cached data (which the
405 * caller must unref), or %NULL if that data is not cached.
408 camel_imap_message_cache_get (CamelImapMessageCache *cache, const char *uid,
409 const char *part_spec, CamelException *ex)
418 /* See comment in insert_setup() */
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);
426 camel_stream_reset (CAMEL_STREAM (stream));
427 camel_object_ref (CAMEL_OBJECT (stream));
432 stream = camel_stream_fs_new_with_name (path, O_RDONLY, 0);
434 cache_put (cache, uid, key, stream);
436 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
437 _("Failed to cache %s: %s"),
438 part_spec, g_strerror (errno));
447 * camel_imap_message_cache_remove:
449 * @uid: UID of the data to remove
451 * Removes all data associated with @uid from @cache.
454 camel_imap_message_cache_remove (CamelImapMessageCache *cache, const char *uid)
461 subparts = g_hash_table_lookup (cache->parts, uid);
464 for (i = 0; i < subparts->len; i++) {
465 key = subparts->pdata[i];
466 path = g_strdup_printf ("%s/%s", cache->path, key);
469 stream = g_hash_table_lookup (cache->parts, key);
471 camel_object_unhook_event (stream, "finalize",
472 stream_finalize, cache);
473 camel_object_unref (stream);
474 g_hash_table_remove (cache->cached, stream);
476 g_hash_table_remove (cache->parts, key);
479 g_hash_table_remove (cache->parts, uid);
480 g_ptr_array_free (subparts, TRUE);
484 add_uids (gpointer key, gpointer value, gpointer data)
486 if (!strchr (key, '.'))
487 g_ptr_array_add (data, key);
491 * camel_imap_message_cache_clear:
494 * Removes all cached data from @cache.
497 camel_imap_message_cache_clear (CamelImapMessageCache *cache)
502 uids = g_ptr_array_new ();
503 g_hash_table_foreach (cache->parts, add_uids, uids);
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);
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
518 * Copies all cached parts from @source_uid in @source to @dest_uid in
522 camel_imap_message_cache_copy (CamelImapMessageCache *source,
523 const char *source_uid,
524 CamelImapMessageCache *dest,
525 const char *dest_uid,
533 subparts = g_hash_table_lookup (source->parts, source_uid);
534 if (!subparts || !subparts->len)
537 for (i = 0; i < subparts->len; i++) {
538 part = strchr (subparts->pdata[i], '.');
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));