Rename camel_service_get_settings().
[platform/upstream/evolution-data-server.git] / camel / providers / local / camel-spool-store.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2  *
3  * Authors: Michael Zucchi <notzed@ximian.com>
4  *
5  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
6  *
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.
10  *
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.
15  *
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
19  * USA
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <dirent.h>
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <sys/stat.h>
33 #include <sys/types.h>
34
35 #include <glib/gstdio.h>
36 #include <glib/gi18n-lib.h>
37
38 #include "camel-spool-folder.h"
39 #include "camel-spool-settings.h"
40 #include "camel-spool-store.h"
41
42 #define CAMEL_SPOOL_STORE_GET_PRIVATE(obj) \
43         (G_TYPE_INSTANCE_GET_PRIVATE \
44         ((obj), CAMEL_TYPE_SPOOL_STORE, CamelSpoolStorePrivate))
45
46 #define d(x)
47
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;
53
54 struct _CamelSpoolStorePrivate {
55         gint placeholder;  /* for future expansion */
56 };
57
58 G_DEFINE_TYPE (
59         CamelSpoolStore,
60         camel_spool_store,
61         CAMEL_TYPE_MBOX_STORE)
62
63 static camel_spool_store_t
64 spool_store_get_type (CamelSpoolStore *spool_store,
65                       GError **error)
66 {
67         CamelLocalSettings *local_settings;
68         CamelSettings *settings;
69         CamelService *service;
70         camel_spool_store_t type;
71         struct stat st;
72         gchar *path;
73
74         service = CAMEL_SERVICE (spool_store);
75
76         settings = camel_service_ref_settings (service);
77
78         local_settings = CAMEL_LOCAL_SETTINGS (settings);
79         path = camel_local_settings_dup_path (local_settings);
80
81         g_object_unref (settings);
82
83         /* Check the path for validity while we have the opportunity. */
84
85         if (path == NULL || *path != '/') {
86                 g_set_error (
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;
92
93         } else if (g_stat (path, &st) == -1) {
94                 g_set_error (
95                         error, G_IO_ERROR,
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;
100
101         } else if (S_ISREG (st.st_mode)) {
102                 type = CAMEL_SPOOL_STORE_MBOX;
103
104         } else if (S_ISDIR (st.st_mode)) {
105                 type = CAMEL_SPOOL_STORE_ELM;
106
107         } else {
108                 g_set_error (
109                         error, CAMEL_STORE_ERROR,
110                         CAMEL_STORE_ERROR_NO_FOLDER,
111                         _("Spool '%s' is not a regular file or directory"),
112                         path);
113                 type = CAMEL_SPOOL_STORE_INVALID;
114         }
115
116         g_free (path);
117
118         return type;
119 }
120
121 /* partially copied from mbox */
122 static void
123 spool_fill_fi (CamelStore *store,
124                CamelFolderInfo *fi,
125                guint32 flags,
126                GCancellable *cancellable)
127 {
128         CamelFolder *folder;
129
130         fi->unread = -1;
131         fi->total = -1;
132         folder = camel_object_bag_peek (store->folders, fi->full_name);
133         if (folder) {
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);
139         }
140 }
141
142 static CamelFolderInfo *
143 spool_new_fi (CamelStore *store,
144               CamelFolderInfo *parent,
145               CamelFolderInfo **fip,
146               const gchar *full,
147               guint32 flags)
148 {
149         CamelFolderInfo *fi;
150         const gchar *name;
151
152         name = strrchr (full, '/');
153         if (name)
154                 name++;
155         else
156                 name = full;
157
158         fi = camel_folder_info_new ();
159         fi->full_name = g_strdup (full);
160         fi->display_name = g_strdup (name);
161         fi->unread = -1;
162         fi->total = -1;
163         fi->flags = flags;
164
165         fi->parent = parent;
166         fi->next = *fip;
167         *fip = fi;
168
169         return fi;
170 }
171
172 /* used to find out where we've visited already */
173 struct _inode {
174         dev_t dnode;
175         ino_t inode;
176 };
177
178 /* returns number of records found at or below this level */
179 static gint
180 scan_dir (CamelStore *store,
181           GHashTable *visited,
182           const gchar *root,
183           const gchar *path,
184           guint32 flags,
185           CamelFolderInfo *parent,
186           CamelFolderInfo **fip,
187           GCancellable *cancellable,
188           GError **error)
189 {
190         DIR *dir;
191         struct dirent *d;
192         gchar *name, *tmp, *fname;
193         CamelFolderInfo *fi = NULL;
194         struct stat st;
195         CamelFolder *folder;
196         gchar from[80];
197         FILE *fp;
198
199         d(printf("checking dir '%s' part '%s' for mbox content\n", root, path));
200
201         /* look for folders matching the right structure, recursively */
202         if (path) {
203                 name = alloca (strlen (root) + strlen (path) + 2);
204                 sprintf(name, "%s/%s", root, path);
205         } else
206                 name = (gchar *) root;  /* XXX casting away const */
207
208         if (g_stat (name, &st) == -1) {
209                 g_set_error (
210                         error, G_IO_ERROR,
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 :-/ */
216                 if (path) {
217                         fi = spool_new_fi (
218                                 store, parent, fip, path,
219                                 CAMEL_FOLDER_NOINFERIORS |
220                                 CAMEL_FOLDER_NOCHILDREN);
221                         spool_fill_fi (store, fi, flags, cancellable);
222                 }
223                 return 0;
224         }
225
226         dir = opendir (name);
227         if (dir == NULL) {
228                 g_set_error (
229                         error, G_IO_ERROR,
230                         g_io_error_from_errno (errno),
231                         _("Could not scan folder '%s': %s"),
232                         name, g_strerror (errno));
233                 return -1;
234         }
235
236         if (path != NULL) {
237                 fi = spool_new_fi (
238                         store, parent, fip, path,
239                         CAMEL_FOLDER_NOSELECT);
240                 fip = &fi->child;
241                 parent = fi;
242         }
243
244         while ((d = readdir (dir))) {
245                 if (strcmp(d->d_name, ".") == 0
246                     || strcmp(d->d_name, "..") == 0)
247                         continue;
248
249                 tmp = g_strdup_printf("%s/%s", name, d->d_name);
250                 if (g_stat (tmp, &st) == 0) {
251                         if (path)
252                                 fname = g_strdup_printf (
253                                         "%s/%s", path, d->d_name);
254                         else
255                                 fname = g_strdup (d->d_name);
256
257                         if (S_ISREG (st.st_mode)) {
258                                 gint isfolder = FALSE;
259
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");
265                                         if (fp != NULL) {
266                                                 isfolder = (st.st_size == 0
267                                                             || (fgets (from, sizeof (from), fp) != NULL
268                                                                 && strncmp(from, "From ", 5) == 0));
269                                                 fclose (fp);
270                                         }
271                                 }
272
273                                 if (folder != NULL || isfolder) {
274                                         fi = spool_new_fi (
275                                                 store, parent, fip, fname,
276                                                 CAMEL_FOLDER_NOINFERIORS |
277                                                 CAMEL_FOLDER_NOCHILDREN);
278                                         spool_fill_fi (
279                                                 store, fi, flags, cancellable);
280                                 }
281                                 if (folder)
282                                         g_object_unref (folder);
283
284                         } else if (S_ISDIR (st.st_mode)) {
285                                 struct _inode in = { st.st_dev, st.st_ino };
286
287                                 /* see if we've visited already */
288                                 if (g_hash_table_lookup (visited, &in) == NULL) {
289                                         struct _inode *inew = g_malloc (sizeof (*inew));
290
291                                         *inew = in;
292                                         g_hash_table_insert (visited, inew, inew);
293
294                                         if (scan_dir (store, visited, root, fname, flags, parent, fip, cancellable, error) == -1) {
295                                                 g_free (tmp);
296                                                 g_free (fname);
297                                                 closedir (dir);
298                                                 return -1;
299                                         }
300                                 }
301                         }
302                         g_free (fname);
303
304                 }
305                 g_free (tmp);
306         }
307         closedir (dir);
308
309         return 0;
310 }
311
312 static guint
313 inode_hash (gconstpointer d)
314 {
315         const struct _inode *v = d;
316
317         return v->inode ^ v->dnode;
318 }
319
320 static gboolean
321 inode_equal (gconstpointer a,
322              gconstpointer b)
323 {
324         const struct _inode *v1 = a, *v2 = b;
325
326         return v1->inode == v2->inode && v1->dnode == v2->dnode;
327 }
328
329 static void
330 inode_free (gpointer k,
331             gpointer v,
332             gpointer d)
333 {
334         g_free (k);
335 }
336
337 static CamelFolderInfo *
338 get_folder_info_elm (CamelStore *store,
339                      const gchar *top,
340                      guint32 flags,
341                      GCancellable *cancellable,
342                      GError **error)
343 {
344         CamelLocalSettings *local_settings;
345         CamelSettings *settings;
346         CamelService *service;
347         CamelFolderInfo *fi = NULL;
348         GHashTable *visited;
349         gchar *path;
350
351         service = CAMEL_SERVICE (store);
352
353         settings = camel_service_ref_settings (service);
354
355         local_settings = CAMEL_LOCAL_SETTINGS (settings);
356         path = camel_local_settings_dup_path (local_settings);
357
358         g_object_unref (settings);
359
360         visited = g_hash_table_new (inode_hash, inode_equal);
361
362         if (scan_dir (
363                 store, visited, path, top, flags,
364                 NULL, &fi, cancellable, error) == -1 && fi != NULL) {
365                 camel_store_free_folder_info_full (store, fi);
366                 fi = NULL;
367         }
368
369         g_hash_table_foreach (visited, inode_free, NULL);
370         g_hash_table_destroy (visited);
371
372         g_free (path);
373
374         return fi;
375 }
376
377 static CamelFolderInfo *
378 get_folder_info_mbox (CamelStore *store,
379                       const gchar *top,
380                       guint32 flags,
381                       GCancellable *cancellable,
382                       GError **error)
383 {
384         CamelFolderInfo *fi = NULL, *fip = NULL;
385
386         if (top == NULL || strcmp (top, "INBOX") == 0) {
387                 fi = spool_new_fi (
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);
395         }
396
397         return fi;
398 }
399
400 static gchar *
401 spool_store_get_name (CamelService *service,
402                       gboolean brief)
403 {
404         CamelLocalSettings *local_settings;
405         CamelSpoolStore *spool_store;
406         CamelSettings *settings;
407         gchar *name;
408         gchar *path;
409
410         spool_store = CAMEL_SPOOL_STORE (service);
411
412         settings = camel_service_ref_settings (service);
413
414         local_settings = CAMEL_LOCAL_SETTINGS (settings);
415         path = camel_local_settings_dup_path (local_settings);
416
417         g_object_unref (settings);
418
419         if (brief)
420                 return path;
421
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);
426                         break;
427                 case CAMEL_SPOOL_STORE_ELM:
428                         name = g_strdup_printf (
429                                 _("Spool folder tree %s"), path);
430                         break;
431                 default:
432                         name = g_strdup (_("Invalid spool"));
433                         break;
434         }
435
436         g_free (path);
437
438         return name;
439 }
440
441 static void
442 spool_store_free_folder_info (CamelStore *store,
443                               CamelFolderInfo *fi)
444 {
445         if (fi) {
446                 g_free (fi->full_name);
447                 g_free (fi->display_name);
448                 g_slice_free (CamelFolderInfo, fi);
449         }
450 }
451
452 static CamelFolder *
453 spool_store_get_folder_sync (CamelStore *store,
454                              const gchar *folder_name,
455                              CamelStoreGetFolderFlags flags,
456                              GCancellable *cancellable,
457                              GError **error)
458 {
459         CamelLocalSettings *local_settings;
460         CamelSpoolStore *spool_store;
461         CamelSettings *settings;
462         CamelService *service;
463         CamelFolder *folder = NULL;
464         camel_spool_store_t type;
465         struct stat st;
466         gchar *name;
467         gchar *path;
468
469         d(printf("opening folder %s on path %s\n", folder_name, path));
470
471         spool_store = CAMEL_SPOOL_STORE (store);
472         type = spool_store_get_type (spool_store, error);
473
474         if (type == CAMEL_SPOOL_STORE_INVALID)
475                 return NULL;
476
477         service = CAMEL_SERVICE (store);
478
479         settings = camel_service_ref_settings (service);
480
481         local_settings = CAMEL_LOCAL_SETTINGS (settings);
482         path = camel_local_settings_dup_path (local_settings);
483
484         g_object_unref (settings);
485
486         /* we only support an 'INBOX' in mbox mode */
487         if (type == CAMEL_SPOOL_STORE_MBOX) {
488                 if (strcmp(folder_name, "INBOX") != 0) {
489                         g_set_error (
490                                 error, CAMEL_STORE_ERROR,
491                                 CAMEL_STORE_ERROR_NO_FOLDER,
492                                 _("Folder '%s/%s' does not exist."),
493                                 path, folder_name);
494                 } else {
495                         folder = camel_spool_folder_new (
496                                 store, folder_name, flags, cancellable, error);
497                 }
498         } else {
499                 name = g_build_filename (path, folder_name, NULL);
500                 if (g_stat (name, &st) == -1) {
501                         if (errno != ENOENT) {
502                                 g_set_error (
503                                         error, G_IO_ERROR,
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) {
508                                 g_set_error (
509                                         error, CAMEL_STORE_ERROR,
510                                         CAMEL_STORE_ERROR_NO_FOLDER,
511                                         _("Folder '%s' does not exist."),
512                                         folder_name);
513                         } else {
514                                 gint fd = creat (name, 0600);
515                                 if (fd == -1) {
516                                         g_set_error (
517                                                 error, G_IO_ERROR,
518                                                 g_io_error_from_errno (errno),
519                                                 _("Could not create folder '%s':\n%s"),
520                                                 folder_name, g_strerror (errno));
521                                 } else {
522                                         close (fd);
523                                         folder = camel_spool_folder_new (
524                                                 store, folder_name, flags,
525                                                 cancellable, error);
526                                 }
527                         }
528                 } else if (!S_ISREG (st.st_mode)) {
529                         g_set_error (
530                                 error, CAMEL_STORE_ERROR,
531                                 CAMEL_STORE_ERROR_NO_FOLDER,
532                                 _("'%s' is not a mailbox file."), name);
533                 } else {
534                         folder = camel_spool_folder_new (
535                                 store, folder_name, flags, cancellable, error);
536                 }
537                 g_free (name);
538         }
539
540         g_free (path);
541
542         return folder;
543 }
544
545 static CamelFolderInfo *
546 spool_store_get_folder_info_sync (CamelStore *store,
547                                   const gchar *top,
548                                   CamelStoreGetFolderInfoFlags flags,
549                                   GCancellable *cancellable,
550                                   GError **error)
551 {
552         CamelSpoolStore *spool_store;
553         CamelFolderInfo *folder_info = NULL;
554
555         spool_store = CAMEL_SPOOL_STORE (store);
556
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);
561                         break;
562
563                 case CAMEL_SPOOL_STORE_ELM:
564                         folder_info = get_folder_info_elm (
565                                 store, top, flags, cancellable, error);
566                         break;
567
568                 default:
569                         break;
570         }
571
572         return folder_info;
573 }
574
575 static CamelFolder *
576 spool_store_get_inbox_folder_sync (CamelStore *store,
577                                    GCancellable *cancellable,
578                                    GError **error)
579 {
580         CamelSpoolStore *spool_store;
581         CamelFolder *folder = NULL;
582
583         spool_store = CAMEL_SPOOL_STORE (store);
584
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,
589                                 cancellable, error);
590                         break;
591
592                 case CAMEL_SPOOL_STORE_ELM:
593                         g_set_error (
594                                 error, CAMEL_STORE_ERROR,
595                                 CAMEL_STORE_ERROR_NO_FOLDER,
596                                 _("Store does not support an INBOX"));
597                         break;
598
599                 default:
600                         break;
601         }
602
603         return folder;
604 }
605
606 /* default implementation, only delete metadata */
607 static gboolean
608 spool_store_delete_folder_sync (CamelStore *store,
609                                 const gchar *folder_name,
610                                 GCancellable *cancellable,
611                                 GError **error)
612 {
613         g_set_error (
614                 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
615                 _("Spool folders cannot be deleted"));
616
617         return FALSE;
618 }
619
620 /* default implementation, rename all */
621 static gboolean
622 spool_store_rename_folder_sync (CamelStore *store,
623                                 const gchar *old,
624                                 const gchar *new,
625                                 GCancellable *cancellable,
626                                 GError **error)
627 {
628         g_set_error (
629                 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
630                 _("Spool folders cannot be renamed"));
631
632         return FALSE;
633 }
634
635 static gchar *
636 spool_store_get_full_path (CamelLocalStore *local_store,
637                            const gchar *full_name)
638 {
639         CamelLocalSettings *local_settings;
640         CamelSpoolStore *spool_store;
641         CamelSettings *settings;
642         CamelService *service;
643         gchar *full_path;
644         gchar *path;
645
646         service = CAMEL_SERVICE (local_store);
647
648         settings = camel_service_ref_settings (service);
649
650         local_settings = CAMEL_LOCAL_SETTINGS (settings);
651         path = camel_local_settings_dup_path (local_settings);
652
653         g_object_unref (settings);
654
655         spool_store = CAMEL_SPOOL_STORE (local_store);
656
657         switch (spool_store_get_type (spool_store, NULL)) {
658                 case CAMEL_SPOOL_STORE_MBOX:
659                         full_path = g_strdup (path);
660                         break;
661
662                 case CAMEL_SPOOL_STORE_ELM:
663                         full_path = g_build_filename (path, full_name, NULL);
664                         break;
665
666                 default:
667                         full_path = NULL;
668                         break;
669         }
670
671         g_free (path);
672
673         return full_path;
674 }
675
676 static gchar *
677 spool_store_get_meta_path (CamelLocalStore *ls,
678                            const gchar *full_name,
679                            const gchar *ext)
680 {
681         CamelService *service;
682         const gchar *user_data_dir;
683         gchar *path, *key;
684
685         service = CAMEL_SERVICE (ls);
686         user_data_dir = camel_service_get_user_data_dir (service);
687
688         key = camel_file_util_safe_filename (full_name);
689         path = g_strdup_printf ("%s/%s%s", user_data_dir, key, ext);
690         g_free (key);
691
692         return path;
693 }
694
695 static void
696 camel_spool_store_class_init (CamelSpoolStoreClass *class)
697 {
698         CamelServiceClass *service_class;
699         CamelStoreClass *store_class;
700         CamelLocalStoreClass *local_store_class;
701
702         g_type_class_add_private (class, sizeof (CamelSpoolStorePrivate));
703
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;
707
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;
715
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;
719 }
720
721 static void
722 camel_spool_store_init (CamelSpoolStore *spool_store)
723 {
724         spool_store->priv = CAMEL_SPOOL_STORE_GET_PRIVATE (spool_store);
725 }