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 program is free software; you can redistribute it and/or
8 * modify it under the terms of version 2 of the GNU Lesser General Public
9 * License as published by the Free Software Foundation.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
33 #include <sys/types.h>
35 #include <glib/gstdio.h>
36 #include <glib/gi18n-lib.h>
38 #include "camel-spool-folder.h"
39 #include "camel-spool-settings.h"
40 #include "camel-spool-store.h"
42 #define CAMEL_SPOOL_STORE_GET_PRIVATE(obj) \
43 (G_TYPE_INSTANCE_GET_PRIVATE \
44 ((obj), CAMEL_TYPE_SPOOL_STORE, CamelSpoolStorePrivate))
48 typedef enum _camel_spool_store_t {
49 CAMEL_SPOOL_STORE_INVALID,
50 CAMEL_SPOOL_STORE_MBOX, /* a single mbox */
51 CAMEL_SPOOL_STORE_ELM /* elm/pine/etc tree of mbox files in folders */
52 } camel_spool_store_t;
54 struct _CamelSpoolStorePrivate {
55 gint placeholder; /* for future expansion */
61 CAMEL_TYPE_MBOX_STORE)
63 static camel_spool_store_t
64 spool_store_get_type (CamelSpoolStore *spool_store,
67 CamelLocalSettings *local_settings;
68 CamelSettings *settings;
69 CamelService *service;
70 camel_spool_store_t type;
74 service = CAMEL_SERVICE (spool_store);
76 settings = camel_service_ref_settings (service);
78 local_settings = CAMEL_LOCAL_SETTINGS (settings);
79 path = camel_local_settings_dup_path (local_settings);
81 g_object_unref (settings);
83 /* Check the path for validity while we have the opportunity. */
85 if (path == NULL || *path != '/') {
87 error, CAMEL_STORE_ERROR,
88 CAMEL_STORE_ERROR_NO_FOLDER,
89 _("Store root %s is not an absolute path"),
90 (path != NULL) ? path : "(null)");
91 type = CAMEL_SPOOL_STORE_INVALID;
93 } else if (g_stat (path, &st) == -1) {
96 g_io_error_from_errno (errno),
97 _("Spool '%s' cannot be opened: %s"),
98 path, g_strerror (errno));
99 type = CAMEL_SPOOL_STORE_INVALID;
101 } else if (S_ISREG (st.st_mode)) {
102 type = CAMEL_SPOOL_STORE_MBOX;
104 } else if (S_ISDIR (st.st_mode)) {
105 type = CAMEL_SPOOL_STORE_ELM;
109 error, CAMEL_STORE_ERROR,
110 CAMEL_STORE_ERROR_NO_FOLDER,
111 _("Spool '%s' is not a regular file or directory"),
113 type = CAMEL_SPOOL_STORE_INVALID;
121 /* partially copied from mbox */
123 spool_fill_fi (CamelStore *store,
126 GCancellable *cancellable)
132 folder = camel_object_bag_peek (store->folders, fi->full_name);
134 if ((flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0)
135 camel_folder_refresh_info_sync (folder, cancellable, NULL);
136 fi->unread = camel_folder_get_unread_message_count (folder);
137 fi->total = camel_folder_get_message_count (folder);
138 g_object_unref (folder);
142 static CamelFolderInfo *
143 spool_new_fi (CamelStore *store,
144 CamelFolderInfo *parent,
145 CamelFolderInfo **fip,
152 name = strrchr (full, '/');
158 fi = camel_folder_info_new ();
159 fi->full_name = g_strdup (full);
160 fi->display_name = g_strdup (name);
172 /* used to find out where we've visited already */
178 /* returns number of records found at or below this level */
180 scan_dir (CamelStore *store,
185 CamelFolderInfo *parent,
186 CamelFolderInfo **fip,
187 GCancellable *cancellable,
192 gchar *name, *tmp, *fname;
193 CamelFolderInfo *fi = NULL;
199 d(printf("checking dir '%s' part '%s' for mbox content\n", root, path));
201 /* look for folders matching the right structure, recursively */
203 name = alloca (strlen (root) + strlen (path) + 2);
204 sprintf(name, "%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_store_free_folder_info_full (store, 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_free_folder_info (CamelStore *store,
446 g_free (fi->full_name);
447 g_free (fi->display_name);
448 g_slice_free (CamelFolderInfo, fi);
453 spool_store_get_folder_sync (CamelStore *store,
454 const gchar *folder_name,
455 CamelStoreGetFolderFlags flags,
456 GCancellable *cancellable,
459 CamelLocalSettings *local_settings;
460 CamelSpoolStore *spool_store;
461 CamelSettings *settings;
462 CamelService *service;
463 CamelFolder *folder = NULL;
464 camel_spool_store_t type;
469 d(printf("opening folder %s on path %s\n", folder_name, path));
471 spool_store = CAMEL_SPOOL_STORE (store);
472 type = spool_store_get_type (spool_store, error);
474 if (type == CAMEL_SPOOL_STORE_INVALID)
477 service = CAMEL_SERVICE (store);
479 settings = camel_service_ref_settings (service);
481 local_settings = CAMEL_LOCAL_SETTINGS (settings);
482 path = camel_local_settings_dup_path (local_settings);
484 g_object_unref (settings);
486 /* we only support an 'INBOX' in mbox mode */
487 if (type == CAMEL_SPOOL_STORE_MBOX) {
488 if (strcmp(folder_name, "INBOX") != 0) {
490 error, CAMEL_STORE_ERROR,
491 CAMEL_STORE_ERROR_NO_FOLDER,
492 _("Folder '%s/%s' does not exist."),
495 folder = camel_spool_folder_new (
496 store, folder_name, flags, cancellable, error);
499 name = g_build_filename (path, folder_name, NULL);
500 if (g_stat (name, &st) == -1) {
501 if (errno != ENOENT) {
504 g_io_error_from_errno (errno),
505 _("Could not open folder '%s':\n%s"),
506 folder_name, g_strerror (errno));
507 } else if ((flags & CAMEL_STORE_FOLDER_CREATE) == 0) {
509 error, CAMEL_STORE_ERROR,
510 CAMEL_STORE_ERROR_NO_FOLDER,
511 _("Folder '%s' does not exist."),
514 gint fd = creat (name, 0600);
518 g_io_error_from_errno (errno),
519 _("Could not create folder '%s':\n%s"),
520 folder_name, g_strerror (errno));
523 folder = camel_spool_folder_new (
524 store, folder_name, flags,
528 } else if (!S_ISREG (st.st_mode)) {
530 error, CAMEL_STORE_ERROR,
531 CAMEL_STORE_ERROR_NO_FOLDER,
532 _("'%s' is not a mailbox file."), name);
534 folder = camel_spool_folder_new (
535 store, folder_name, flags, cancellable, error);
545 static CamelFolderInfo *
546 spool_store_get_folder_info_sync (CamelStore *store,
548 CamelStoreGetFolderInfoFlags flags,
549 GCancellable *cancellable,
552 CamelSpoolStore *spool_store;
553 CamelFolderInfo *folder_info = NULL;
555 spool_store = CAMEL_SPOOL_STORE (store);
557 switch (spool_store_get_type (spool_store, error)) {
558 case CAMEL_SPOOL_STORE_MBOX:
559 folder_info = get_folder_info_mbox (
560 store, top, flags, cancellable, error);
563 case CAMEL_SPOOL_STORE_ELM:
564 folder_info = get_folder_info_elm (
565 store, top, flags, cancellable, error);
576 spool_store_get_inbox_folder_sync (CamelStore *store,
577 GCancellable *cancellable,
580 CamelSpoolStore *spool_store;
581 CamelFolder *folder = NULL;
583 spool_store = CAMEL_SPOOL_STORE (store);
585 switch (spool_store_get_type (spool_store, error)) {
586 case CAMEL_SPOOL_STORE_MBOX:
587 folder = spool_store_get_folder_sync (
588 store, "INBOX", CAMEL_STORE_FOLDER_CREATE,
592 case CAMEL_SPOOL_STORE_ELM:
594 error, CAMEL_STORE_ERROR,
595 CAMEL_STORE_ERROR_NO_FOLDER,
596 _("Store does not support an INBOX"));
606 /* default implementation, only delete metadata */
608 spool_store_delete_folder_sync (CamelStore *store,
609 const gchar *folder_name,
610 GCancellable *cancellable,
614 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
615 _("Spool folders cannot be deleted"));
620 /* default implementation, rename all */
622 spool_store_rename_folder_sync (CamelStore *store,
625 GCancellable *cancellable,
629 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
630 _("Spool folders cannot be renamed"));
636 spool_store_get_full_path (CamelLocalStore *local_store,
637 const gchar *full_name)
639 CamelLocalSettings *local_settings;
640 CamelSpoolStore *spool_store;
641 CamelSettings *settings;
642 CamelService *service;
646 service = CAMEL_SERVICE (local_store);
648 settings = camel_service_ref_settings (service);
650 local_settings = CAMEL_LOCAL_SETTINGS (settings);
651 path = camel_local_settings_dup_path (local_settings);
653 g_object_unref (settings);
655 spool_store = CAMEL_SPOOL_STORE (local_store);
657 switch (spool_store_get_type (spool_store, NULL)) {
658 case CAMEL_SPOOL_STORE_MBOX:
659 full_path = g_strdup (path);
662 case CAMEL_SPOOL_STORE_ELM:
663 full_path = g_build_filename (path, full_name, NULL);
677 spool_store_get_meta_path (CamelLocalStore *ls,
678 const gchar *full_name,
681 CamelService *service;
682 const gchar *user_data_dir;
685 service = CAMEL_SERVICE (ls);
686 user_data_dir = camel_service_get_user_data_dir (service);
688 key = camel_file_util_safe_filename (full_name);
689 path = g_strdup_printf ("%s/%s%s", user_data_dir, key, ext);
696 camel_spool_store_class_init (CamelSpoolStoreClass *class)
698 CamelServiceClass *service_class;
699 CamelStoreClass *store_class;
700 CamelLocalStoreClass *local_store_class;
702 g_type_class_add_private (class, sizeof (CamelSpoolStorePrivate));
704 service_class = CAMEL_SERVICE_CLASS (class);
705 service_class->settings_type = CAMEL_TYPE_SPOOL_SETTINGS;
706 service_class->get_name = spool_store_get_name;
708 store_class = CAMEL_STORE_CLASS (class);
709 store_class->free_folder_info = spool_store_free_folder_info;
710 store_class->get_folder_sync = spool_store_get_folder_sync;
711 store_class->get_folder_info_sync = spool_store_get_folder_info_sync;
712 store_class->get_inbox_folder_sync = spool_store_get_inbox_folder_sync;
713 store_class->delete_folder_sync = spool_store_delete_folder_sync;
714 store_class->rename_folder_sync = spool_store_rename_folder_sync;
716 local_store_class = CAMEL_LOCAL_STORE_CLASS (class);
717 local_store_class->get_full_path = spool_store_get_full_path;
718 local_store_class->get_meta_path = spool_store_get_meta_path;
722 camel_spool_store_init (CamelSpoolStore *spool_store)
724 spool_store->priv = CAMEL_SPOOL_STORE_GET_PRIVATE (spool_store);