1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 * Authors: Michael Zucchi <notzed@ximian.com>
5 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
7 * This library is free software you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation.
11 * This library is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, see <http://www.gnu.org/licenses/>.
31 #include <sys/types.h>
33 #include <glib/gstdio.h>
34 #include <glib/gi18n-lib.h>
36 #include "camel-spool-folder.h"
37 #include "camel-spool-settings.h"
38 #include "camel-spool-store.h"
40 #define CAMEL_SPOOL_STORE_GET_PRIVATE(obj) \
41 (G_TYPE_INSTANCE_GET_PRIVATE \
42 ((obj), CAMEL_TYPE_SPOOL_STORE, CamelSpoolStorePrivate))
46 typedef enum _camel_spool_store_t {
47 CAMEL_SPOOL_STORE_INVALID,
48 CAMEL_SPOOL_STORE_MBOX, /* a single mbox */
49 CAMEL_SPOOL_STORE_ELM /* elm/pine/etc tree of mbox files in folders */
50 } camel_spool_store_t;
52 struct _CamelSpoolStorePrivate {
53 gint placeholder; /* for future expansion */
59 CAMEL_TYPE_MBOX_STORE)
61 static camel_spool_store_t
62 spool_store_get_type (CamelSpoolStore *spool_store,
65 CamelLocalSettings *local_settings;
66 CamelSettings *settings;
67 CamelService *service;
68 camel_spool_store_t type;
72 service = CAMEL_SERVICE (spool_store);
74 settings = camel_service_ref_settings (service);
76 local_settings = CAMEL_LOCAL_SETTINGS (settings);
77 path = camel_local_settings_dup_path (local_settings);
79 g_object_unref (settings);
81 /* Check the path for validity while we have the opportunity. */
83 if (path == NULL || *path != '/') {
85 error, CAMEL_STORE_ERROR,
86 CAMEL_STORE_ERROR_NO_FOLDER,
87 _("Store root %s is not an absolute path"),
88 (path != NULL) ? path : "(null)");
89 type = CAMEL_SPOOL_STORE_INVALID;
91 } else if (g_stat (path, &st) == -1) {
94 g_io_error_from_errno (errno),
95 _("Spool '%s' cannot be opened: %s"),
96 path, g_strerror (errno));
97 type = CAMEL_SPOOL_STORE_INVALID;
99 } else if (S_ISREG (st.st_mode)) {
100 type = CAMEL_SPOOL_STORE_MBOX;
102 } else if (S_ISDIR (st.st_mode)) {
103 type = CAMEL_SPOOL_STORE_ELM;
107 error, CAMEL_STORE_ERROR,
108 CAMEL_STORE_ERROR_NO_FOLDER,
109 _("Spool '%s' is not a regular file or directory"),
111 type = CAMEL_SPOOL_STORE_INVALID;
119 /* partially copied from mbox */
121 spool_fill_fi (CamelStore *store,
124 GCancellable *cancellable)
130 folder = camel_object_bag_peek (store->folders, fi->full_name);
132 if ((flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0)
133 camel_folder_refresh_info_sync (folder, cancellable, NULL);
134 fi->unread = camel_folder_get_unread_message_count (folder);
135 fi->total = camel_folder_get_message_count (folder);
136 g_object_unref (folder);
140 static CamelFolderInfo *
141 spool_new_fi (CamelStore *store,
142 CamelFolderInfo *parent,
143 CamelFolderInfo **fip,
150 name = strrchr (full, '/');
156 fi = camel_folder_info_new ();
157 fi->full_name = g_strdup (full);
158 fi->display_name = g_strdup (name);
170 /* used to find out where we've visited already */
176 /* returns number of records found at or below this level */
178 scan_dir (CamelStore *store,
183 CamelFolderInfo *parent,
184 CamelFolderInfo **fip,
185 GCancellable *cancellable,
190 gchar *name, *tmp, *fname;
192 CamelFolderInfo *fi = NULL;
198 d (printf ("checking dir '%s' part '%s' for mbox content\n", root, path));
200 /* look for folders matching the right structure, recursively */
202 name_len = strlen (root) + strlen (path) + 2;
203 name = alloca (name_len);
204 g_snprintf (name, name_len, "%s/%s", root, path);
206 name = (gchar *) root; /* XXX casting away const */
208 if (g_stat (name, &st) == -1) {
211 g_io_error_from_errno (errno),
212 _("Could not scan folder '%s': %s"),
213 name, g_strerror (errno));
214 } else if (S_ISREG (st.st_mode)) {
215 /* incase we start scanning from a file. messy duplication :-/ */
218 store, parent, fip, path,
219 CAMEL_FOLDER_NOINFERIORS |
220 CAMEL_FOLDER_NOCHILDREN);
221 spool_fill_fi (store, fi, flags, cancellable);
226 dir = opendir (name);
230 g_io_error_from_errno (errno),
231 _("Could not scan folder '%s': %s"),
232 name, g_strerror (errno));
238 store, parent, fip, path,
239 CAMEL_FOLDER_NOSELECT);
244 while ((d = readdir (dir))) {
245 if (strcmp (d->d_name, ".") == 0
246 || strcmp (d->d_name, "..") == 0)
249 tmp = g_strdup_printf ("%s/%s", name, d->d_name);
250 if (g_stat (tmp, &st) == 0) {
252 fname = g_strdup_printf (
253 "%s/%s", path, d->d_name);
255 fname = g_strdup (d->d_name);
257 if (S_ISREG (st.st_mode)) {
258 gint isfolder = FALSE;
260 /* first, see if we already have it open */
261 folder = camel_object_bag_peek (
262 store->folders, fname);
263 if (folder == NULL) {
264 fp = fopen (tmp, "r");
266 isfolder = (st.st_size == 0
267 || (fgets (from, sizeof (from), fp) != NULL
268 && strncmp (from, "From ", 5) == 0));
273 if (folder != NULL || isfolder) {
275 store, parent, fip, fname,
276 CAMEL_FOLDER_NOINFERIORS |
277 CAMEL_FOLDER_NOCHILDREN);
279 store, fi, flags, cancellable);
282 g_object_unref (folder);
284 } else if (S_ISDIR (st.st_mode)) {
285 struct _inode in = { st.st_dev, st.st_ino };
287 /* see if we've visited already */
288 if (g_hash_table_lookup (visited, &in) == NULL) {
289 struct _inode *inew = g_malloc (sizeof (*inew));
292 g_hash_table_insert (visited, inew, inew);
294 if (scan_dir (store, visited, root, fname, flags, parent, fip, cancellable, error) == -1) {
313 inode_hash (gconstpointer d)
315 const struct _inode *v = d;
317 return v->inode ^ v->dnode;
321 inode_equal (gconstpointer a,
324 const struct _inode *v1 = a, *v2 = b;
326 return v1->inode == v2->inode && v1->dnode == v2->dnode;
330 inode_free (gpointer k,
337 static CamelFolderInfo *
338 get_folder_info_elm (CamelStore *store,
341 GCancellable *cancellable,
344 CamelLocalSettings *local_settings;
345 CamelSettings *settings;
346 CamelService *service;
347 CamelFolderInfo *fi = NULL;
351 service = CAMEL_SERVICE (store);
353 settings = camel_service_ref_settings (service);
355 local_settings = CAMEL_LOCAL_SETTINGS (settings);
356 path = camel_local_settings_dup_path (local_settings);
358 g_object_unref (settings);
360 visited = g_hash_table_new (inode_hash, inode_equal);
363 store, visited, path, top, flags,
364 NULL, &fi, cancellable, error) == -1 && fi != NULL) {
365 camel_folder_info_free (fi);
369 g_hash_table_foreach (visited, inode_free, NULL);
370 g_hash_table_destroy (visited);
377 static CamelFolderInfo *
378 get_folder_info_mbox (CamelStore *store,
381 GCancellable *cancellable,
384 CamelFolderInfo *fi = NULL, *fip = NULL;
386 if (top == NULL || strcmp (top, "INBOX") == 0) {
388 store, NULL, &fip, "INBOX",
389 CAMEL_FOLDER_NOINFERIORS |
390 CAMEL_FOLDER_NOCHILDREN |
391 CAMEL_FOLDER_SYSTEM);
392 g_free (fi->display_name);
393 fi->display_name = g_strdup (_("Inbox"));
394 spool_fill_fi (store, fi, flags, cancellable);
401 spool_store_get_name (CamelService *service,
404 CamelLocalSettings *local_settings;
405 CamelSpoolStore *spool_store;
406 CamelSettings *settings;
410 spool_store = CAMEL_SPOOL_STORE (service);
412 settings = camel_service_ref_settings (service);
414 local_settings = CAMEL_LOCAL_SETTINGS (settings);
415 path = camel_local_settings_dup_path (local_settings);
417 g_object_unref (settings);
422 switch (spool_store_get_type (spool_store, NULL)) {
423 case CAMEL_SPOOL_STORE_MBOX:
424 name = g_strdup_printf (
425 _("Spool mail file %s"), path);
427 case CAMEL_SPOOL_STORE_ELM:
428 name = g_strdup_printf (
429 _("Spool folder tree %s"), path);
432 name = g_strdup (_("Invalid spool"));
442 spool_store_get_folder_sync (CamelStore *store,
443 const gchar *folder_name,
444 CamelStoreGetFolderFlags flags,
445 GCancellable *cancellable,
448 CamelLocalSettings *local_settings;
449 CamelSpoolStore *spool_store;
450 CamelSettings *settings;
451 CamelService *service;
452 CamelFolder *folder = NULL;
453 camel_spool_store_t type;
458 d (printf ("opening folder %s on path %s\n", folder_name, path));
460 spool_store = CAMEL_SPOOL_STORE (store);
461 type = spool_store_get_type (spool_store, error);
463 if (type == CAMEL_SPOOL_STORE_INVALID)
466 service = CAMEL_SERVICE (store);
468 settings = camel_service_ref_settings (service);
470 local_settings = CAMEL_LOCAL_SETTINGS (settings);
471 path = camel_local_settings_dup_path (local_settings);
473 g_object_unref (settings);
475 /* we only support an 'INBOX' in mbox mode */
476 if (type == CAMEL_SPOOL_STORE_MBOX) {
477 if (strcmp (folder_name, "INBOX") != 0) {
479 error, CAMEL_STORE_ERROR,
480 CAMEL_STORE_ERROR_NO_FOLDER,
481 _("Folder '%s/%s' does not exist."),
484 folder = camel_spool_folder_new (
485 store, folder_name, flags, cancellable, error);
488 name = g_build_filename (path, folder_name, NULL);
489 if (g_stat (name, &st) == -1) {
490 if (errno != ENOENT) {
493 g_io_error_from_errno (errno),
494 _("Could not open folder '%s':\n%s"),
495 folder_name, g_strerror (errno));
496 } else if ((flags & CAMEL_STORE_FOLDER_CREATE) == 0) {
498 error, CAMEL_STORE_ERROR,
499 CAMEL_STORE_ERROR_NO_FOLDER,
500 _("Folder '%s' does not exist."),
503 gint fd = creat (name, 0600);
507 g_io_error_from_errno (errno),
508 _("Could not create folder '%s':\n%s"),
509 folder_name, g_strerror (errno));
512 folder = camel_spool_folder_new (
513 store, folder_name, flags,
517 } else if (!S_ISREG (st.st_mode)) {
519 error, CAMEL_STORE_ERROR,
520 CAMEL_STORE_ERROR_NO_FOLDER,
521 _("'%s' is not a mailbox file."), name);
523 folder = camel_spool_folder_new (
524 store, folder_name, flags, cancellable, error);
534 static CamelFolderInfo *
535 spool_store_get_folder_info_sync (CamelStore *store,
537 CamelStoreGetFolderInfoFlags flags,
538 GCancellable *cancellable,
541 CamelSpoolStore *spool_store;
542 CamelFolderInfo *folder_info = NULL;
544 spool_store = CAMEL_SPOOL_STORE (store);
546 switch (spool_store_get_type (spool_store, error)) {
547 case CAMEL_SPOOL_STORE_MBOX:
548 folder_info = get_folder_info_mbox (
549 store, top, flags, cancellable, error);
552 case CAMEL_SPOOL_STORE_ELM:
553 folder_info = get_folder_info_elm (
554 store, top, flags, cancellable, error);
565 spool_store_get_inbox_folder_sync (CamelStore *store,
566 GCancellable *cancellable,
569 CamelSpoolStore *spool_store;
570 CamelFolder *folder = NULL;
572 spool_store = CAMEL_SPOOL_STORE (store);
574 switch (spool_store_get_type (spool_store, error)) {
575 case CAMEL_SPOOL_STORE_MBOX:
576 folder = spool_store_get_folder_sync (
577 store, "INBOX", CAMEL_STORE_FOLDER_CREATE,
581 case CAMEL_SPOOL_STORE_ELM:
583 error, CAMEL_STORE_ERROR,
584 CAMEL_STORE_ERROR_NO_FOLDER,
585 _("Store does not support an INBOX"));
595 /* default implementation, only delete metadata */
597 spool_store_delete_folder_sync (CamelStore *store,
598 const gchar *folder_name,
599 GCancellable *cancellable,
603 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
604 _("Spool folders cannot be deleted"));
609 /* default implementation, rename all */
611 spool_store_rename_folder_sync (CamelStore *store,
614 GCancellable *cancellable,
618 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
619 _("Spool folders cannot be renamed"));
625 spool_store_get_full_path (CamelLocalStore *local_store,
626 const gchar *full_name)
628 CamelLocalSettings *local_settings;
629 CamelSpoolStore *spool_store;
630 CamelSettings *settings;
631 CamelService *service;
635 service = CAMEL_SERVICE (local_store);
637 settings = camel_service_ref_settings (service);
639 local_settings = CAMEL_LOCAL_SETTINGS (settings);
640 path = camel_local_settings_dup_path (local_settings);
642 g_object_unref (settings);
644 spool_store = CAMEL_SPOOL_STORE (local_store);
646 switch (spool_store_get_type (spool_store, NULL)) {
647 case CAMEL_SPOOL_STORE_MBOX:
648 full_path = g_strdup (path);
651 case CAMEL_SPOOL_STORE_ELM:
652 full_path = g_build_filename (path, full_name, NULL);
666 spool_store_get_meta_path (CamelLocalStore *ls,
667 const gchar *full_name,
670 CamelService *service;
671 const gchar *user_data_dir;
674 service = CAMEL_SERVICE (ls);
675 user_data_dir = camel_service_get_user_data_dir (service);
677 key = camel_file_util_safe_filename (full_name);
678 path = g_strdup_printf ("%s/%s%s", user_data_dir, key, ext);
685 camel_spool_store_class_init (CamelSpoolStoreClass *class)
687 CamelServiceClass *service_class;
688 CamelStoreClass *store_class;
689 CamelLocalStoreClass *local_store_class;
691 g_type_class_add_private (class, sizeof (CamelSpoolStorePrivate));
693 service_class = CAMEL_SERVICE_CLASS (class);
694 service_class->settings_type = CAMEL_TYPE_SPOOL_SETTINGS;
695 service_class->get_name = spool_store_get_name;
697 store_class = CAMEL_STORE_CLASS (class);
698 store_class->get_folder_sync = spool_store_get_folder_sync;
699 store_class->get_folder_info_sync = spool_store_get_folder_info_sync;
700 store_class->get_inbox_folder_sync = spool_store_get_inbox_folder_sync;
701 store_class->delete_folder_sync = spool_store_delete_folder_sync;
702 store_class->rename_folder_sync = spool_store_rename_folder_sync;
704 local_store_class = CAMEL_LOCAL_STORE_CLASS (class);
705 local_store_class->get_full_path = spool_store_get_full_path;
706 local_store_class->get_meta_path = spool_store_get_meta_path;
710 camel_spool_store_init (CamelSpoolStore *spool_store)
712 spool_store->priv = CAMEL_SPOOL_STORE_GET_PRIVATE (spool_store);