updated changelog
[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 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.
10  *
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
14  *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, see <http://www.gnu.org/licenses/>.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #include <dirent.h>
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <unistd.h>
30 #include <sys/stat.h>
31 #include <sys/types.h>
32
33 #include <glib/gstdio.h>
34 #include <glib/gi18n-lib.h>
35
36 #include "camel-spool-folder.h"
37 #include "camel-spool-settings.h"
38 #include "camel-spool-store.h"
39
40 #define CAMEL_SPOOL_STORE_GET_PRIVATE(obj) \
41         (G_TYPE_INSTANCE_GET_PRIVATE \
42         ((obj), CAMEL_TYPE_SPOOL_STORE, CamelSpoolStorePrivate))
43
44 #define d(x)
45
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;
51
52 struct _CamelSpoolStorePrivate {
53         gint placeholder;  /* for future expansion */
54 };
55
56 G_DEFINE_TYPE (
57         CamelSpoolStore,
58         camel_spool_store,
59         CAMEL_TYPE_MBOX_STORE)
60
61 static camel_spool_store_t
62 spool_store_get_type (CamelSpoolStore *spool_store,
63                       GError **error)
64 {
65         CamelLocalSettings *local_settings;
66         CamelSettings *settings;
67         CamelService *service;
68         camel_spool_store_t type;
69         struct stat st;
70         gchar *path;
71
72         service = CAMEL_SERVICE (spool_store);
73
74         settings = camel_service_ref_settings (service);
75
76         local_settings = CAMEL_LOCAL_SETTINGS (settings);
77         path = camel_local_settings_dup_path (local_settings);
78
79         g_object_unref (settings);
80
81         /* Check the path for validity while we have the opportunity. */
82
83         if (path == NULL || *path != '/') {
84                 g_set_error (
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;
90
91         } else if (g_stat (path, &st) == -1) {
92                 g_set_error (
93                         error, G_IO_ERROR,
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;
98
99         } else if (S_ISREG (st.st_mode)) {
100                 type = CAMEL_SPOOL_STORE_MBOX;
101
102         } else if (S_ISDIR (st.st_mode)) {
103                 type = CAMEL_SPOOL_STORE_ELM;
104
105         } else {
106                 g_set_error (
107                         error, CAMEL_STORE_ERROR,
108                         CAMEL_STORE_ERROR_NO_FOLDER,
109                         _("Spool '%s' is not a regular file or directory"),
110                         path);
111                 type = CAMEL_SPOOL_STORE_INVALID;
112         }
113
114         g_free (path);
115
116         return type;
117 }
118
119 /* partially copied from mbox */
120 static void
121 spool_fill_fi (CamelStore *store,
122                CamelFolderInfo *fi,
123                guint32 flags,
124                GCancellable *cancellable)
125 {
126         CamelFolder *folder;
127
128         fi->unread = -1;
129         fi->total = -1;
130         folder = camel_object_bag_peek (store->folders, fi->full_name);
131         if (folder) {
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);
137         }
138 }
139
140 static CamelFolderInfo *
141 spool_new_fi (CamelStore *store,
142               CamelFolderInfo *parent,
143               CamelFolderInfo **fip,
144               const gchar *full,
145               guint32 flags)
146 {
147         CamelFolderInfo *fi;
148         const gchar *name;
149
150         name = strrchr (full, '/');
151         if (name)
152                 name++;
153         else
154                 name = full;
155
156         fi = camel_folder_info_new ();
157         fi->full_name = g_strdup (full);
158         fi->display_name = g_strdup (name);
159         fi->unread = -1;
160         fi->total = -1;
161         fi->flags = flags;
162
163         fi->parent = parent;
164         fi->next = *fip;
165         *fip = fi;
166
167         return fi;
168 }
169
170 /* used to find out where we've visited already */
171 struct _inode {
172         dev_t dnode;
173         ino_t inode;
174 };
175
176 /* returns number of records found at or below this level */
177 static gint
178 scan_dir (CamelStore *store,
179           GHashTable *visited,
180           const gchar *root,
181           const gchar *path,
182           guint32 flags,
183           CamelFolderInfo *parent,
184           CamelFolderInfo **fip,
185           GCancellable *cancellable,
186           GError **error)
187 {
188         DIR *dir;
189         struct dirent *d;
190         gchar *name, *tmp, *fname;
191         gsize name_len;
192         CamelFolderInfo *fi = NULL;
193         struct stat st;
194         CamelFolder *folder;
195         gchar from[80];
196         FILE *fp;
197
198         d (printf ("checking dir '%s' part '%s' for mbox content\n", root, path));
199
200         /* look for folders matching the right structure, recursively */
201         if (path) {
202                 name_len = strlen (root) + strlen (path) + 2;
203                 name = alloca (name_len);
204                 g_snprintf (name, name_len, "%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_folder_info_free (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 CamelFolder *
442 spool_store_get_folder_sync (CamelStore *store,
443                              const gchar *folder_name,
444                              CamelStoreGetFolderFlags flags,
445                              GCancellable *cancellable,
446                              GError **error)
447 {
448         CamelLocalSettings *local_settings;
449         CamelSpoolStore *spool_store;
450         CamelSettings *settings;
451         CamelService *service;
452         CamelFolder *folder = NULL;
453         camel_spool_store_t type;
454         struct stat st;
455         gchar *name;
456         gchar *path;
457
458         d (printf ("opening folder %s on path %s\n", folder_name, path));
459
460         spool_store = CAMEL_SPOOL_STORE (store);
461         type = spool_store_get_type (spool_store, error);
462
463         if (type == CAMEL_SPOOL_STORE_INVALID)
464                 return NULL;
465
466         service = CAMEL_SERVICE (store);
467
468         settings = camel_service_ref_settings (service);
469
470         local_settings = CAMEL_LOCAL_SETTINGS (settings);
471         path = camel_local_settings_dup_path (local_settings);
472
473         g_object_unref (settings);
474
475         /* we only support an 'INBOX' in mbox mode */
476         if (type == CAMEL_SPOOL_STORE_MBOX) {
477                 if (strcmp (folder_name, "INBOX") != 0) {
478                         g_set_error (
479                                 error, CAMEL_STORE_ERROR,
480                                 CAMEL_STORE_ERROR_NO_FOLDER,
481                                 _("Folder '%s/%s' does not exist."),
482                                 path, folder_name);
483                 } else {
484                         folder = camel_spool_folder_new (
485                                 store, folder_name, flags, cancellable, error);
486                 }
487         } else {
488                 name = g_build_filename (path, folder_name, NULL);
489                 if (g_stat (name, &st) == -1) {
490                         if (errno != ENOENT) {
491                                 g_set_error (
492                                         error, G_IO_ERROR,
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) {
497                                 g_set_error (
498                                         error, CAMEL_STORE_ERROR,
499                                         CAMEL_STORE_ERROR_NO_FOLDER,
500                                         _("Folder '%s' does not exist."),
501                                         folder_name);
502                         } else {
503                                 gint fd = creat (name, 0600);
504                                 if (fd == -1) {
505                                         g_set_error (
506                                                 error, G_IO_ERROR,
507                                                 g_io_error_from_errno (errno),
508                                                 _("Could not create folder '%s':\n%s"),
509                                                 folder_name, g_strerror (errno));
510                                 } else {
511                                         close (fd);
512                                         folder = camel_spool_folder_new (
513                                                 store, folder_name, flags,
514                                                 cancellable, error);
515                                 }
516                         }
517                 } else if (!S_ISREG (st.st_mode)) {
518                         g_set_error (
519                                 error, CAMEL_STORE_ERROR,
520                                 CAMEL_STORE_ERROR_NO_FOLDER,
521                                 _("'%s' is not a mailbox file."), name);
522                 } else {
523                         folder = camel_spool_folder_new (
524                                 store, folder_name, flags, cancellable, error);
525                 }
526                 g_free (name);
527         }
528
529         g_free (path);
530
531         return folder;
532 }
533
534 static CamelFolderInfo *
535 spool_store_get_folder_info_sync (CamelStore *store,
536                                   const gchar *top,
537                                   CamelStoreGetFolderInfoFlags flags,
538                                   GCancellable *cancellable,
539                                   GError **error)
540 {
541         CamelSpoolStore *spool_store;
542         CamelFolderInfo *folder_info = NULL;
543
544         spool_store = CAMEL_SPOOL_STORE (store);
545
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);
550                         break;
551
552                 case CAMEL_SPOOL_STORE_ELM:
553                         folder_info = get_folder_info_elm (
554                                 store, top, flags, cancellable, error);
555                         break;
556
557                 default:
558                         break;
559         }
560
561         return folder_info;
562 }
563
564 static CamelFolder *
565 spool_store_get_inbox_folder_sync (CamelStore *store,
566                                    GCancellable *cancellable,
567                                    GError **error)
568 {
569         CamelSpoolStore *spool_store;
570         CamelFolder *folder = NULL;
571
572         spool_store = CAMEL_SPOOL_STORE (store);
573
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,
578                                 cancellable, error);
579                         break;
580
581                 case CAMEL_SPOOL_STORE_ELM:
582                         g_set_error (
583                                 error, CAMEL_STORE_ERROR,
584                                 CAMEL_STORE_ERROR_NO_FOLDER,
585                                 _("Store does not support an INBOX"));
586                         break;
587
588                 default:
589                         break;
590         }
591
592         return folder;
593 }
594
595 /* default implementation, only delete metadata */
596 static gboolean
597 spool_store_delete_folder_sync (CamelStore *store,
598                                 const gchar *folder_name,
599                                 GCancellable *cancellable,
600                                 GError **error)
601 {
602         g_set_error (
603                 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
604                 _("Spool folders cannot be deleted"));
605
606         return FALSE;
607 }
608
609 /* default implementation, rename all */
610 static gboolean
611 spool_store_rename_folder_sync (CamelStore *store,
612                                 const gchar *old,
613                                 const gchar *new,
614                                 GCancellable *cancellable,
615                                 GError **error)
616 {
617         g_set_error (
618                 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
619                 _("Spool folders cannot be renamed"));
620
621         return FALSE;
622 }
623
624 static gchar *
625 spool_store_get_full_path (CamelLocalStore *local_store,
626                            const gchar *full_name)
627 {
628         CamelLocalSettings *local_settings;
629         CamelSpoolStore *spool_store;
630         CamelSettings *settings;
631         CamelService *service;
632         gchar *full_path;
633         gchar *path;
634
635         service = CAMEL_SERVICE (local_store);
636
637         settings = camel_service_ref_settings (service);
638
639         local_settings = CAMEL_LOCAL_SETTINGS (settings);
640         path = camel_local_settings_dup_path (local_settings);
641
642         g_object_unref (settings);
643
644         spool_store = CAMEL_SPOOL_STORE (local_store);
645
646         switch (spool_store_get_type (spool_store, NULL)) {
647                 case CAMEL_SPOOL_STORE_MBOX:
648                         full_path = g_strdup (path);
649                         break;
650
651                 case CAMEL_SPOOL_STORE_ELM:
652                         full_path = g_build_filename (path, full_name, NULL);
653                         break;
654
655                 default:
656                         full_path = NULL;
657                         break;
658         }
659
660         g_free (path);
661
662         return full_path;
663 }
664
665 static gchar *
666 spool_store_get_meta_path (CamelLocalStore *ls,
667                            const gchar *full_name,
668                            const gchar *ext)
669 {
670         CamelService *service;
671         const gchar *user_data_dir;
672         gchar *path, *key;
673
674         service = CAMEL_SERVICE (ls);
675         user_data_dir = camel_service_get_user_data_dir (service);
676
677         key = camel_file_util_safe_filename (full_name);
678         path = g_strdup_printf ("%s/%s%s", user_data_dir, key, ext);
679         g_free (key);
680
681         return path;
682 }
683
684 static void
685 camel_spool_store_class_init (CamelSpoolStoreClass *class)
686 {
687         CamelServiceClass *service_class;
688         CamelStoreClass *store_class;
689         CamelLocalStoreClass *local_store_class;
690
691         g_type_class_add_private (class, sizeof (CamelSpoolStorePrivate));
692
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;
696
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;
703
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;
707 }
708
709 static void
710 camel_spool_store_init (CamelSpoolStore *spool_store)
711 {
712         spool_store->priv = CAMEL_SPOOL_STORE_GET_PRIVATE (spool_store);
713 }