Bug 710787 - Replace most uses of sprintf() with g_snprintf()
[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         gsize name_len;
194         CamelFolderInfo *fi = NULL;
195         struct stat st;
196         CamelFolder *folder;
197         gchar from[80];
198         FILE *fp;
199
200         d (printf ("checking dir '%s' part '%s' for mbox content\n", root, path));
201
202         /* look for folders matching the right structure, recursively */
203         if (path) {
204                 name_len = strlen (root) + strlen (path) + 2;
205                 name = alloca (name_len);
206                 g_snprintf (name, name_len, "%s/%s", root, path);
207         } else
208                 name = (gchar *) root;  /* XXX casting away const */
209
210         if (g_stat (name, &st) == -1) {
211                 g_set_error (
212                         error, G_IO_ERROR,
213                         g_io_error_from_errno (errno),
214                         _("Could not scan folder '%s': %s"),
215                         name, g_strerror (errno));
216         } else if (S_ISREG (st.st_mode)) {
217                 /* incase we start scanning from a file.  messy duplication :-/ */
218                 if (path) {
219                         fi = spool_new_fi (
220                                 store, parent, fip, path,
221                                 CAMEL_FOLDER_NOINFERIORS |
222                                 CAMEL_FOLDER_NOCHILDREN);
223                         spool_fill_fi (store, fi, flags, cancellable);
224                 }
225                 return 0;
226         }
227
228         dir = opendir (name);
229         if (dir == NULL) {
230                 g_set_error (
231                         error, G_IO_ERROR,
232                         g_io_error_from_errno (errno),
233                         _("Could not scan folder '%s': %s"),
234                         name, g_strerror (errno));
235                 return -1;
236         }
237
238         if (path != NULL) {
239                 fi = spool_new_fi (
240                         store, parent, fip, path,
241                         CAMEL_FOLDER_NOSELECT);
242                 fip = &fi->child;
243                 parent = fi;
244         }
245
246         while ((d = readdir (dir))) {
247                 if (strcmp (d->d_name, ".") == 0
248                     || strcmp (d->d_name, "..") == 0)
249                         continue;
250
251                 tmp = g_strdup_printf ("%s/%s", name, d->d_name);
252                 if (g_stat (tmp, &st) == 0) {
253                         if (path)
254                                 fname = g_strdup_printf (
255                                         "%s/%s", path, d->d_name);
256                         else
257                                 fname = g_strdup (d->d_name);
258
259                         if (S_ISREG (st.st_mode)) {
260                                 gint isfolder = FALSE;
261
262                                 /* first, see if we already have it open */
263                                 folder = camel_object_bag_peek (
264                                         store->folders, fname);
265                                 if (folder == NULL) {
266                                         fp = fopen (tmp, "r");
267                                         if (fp != NULL) {
268                                                 isfolder = (st.st_size == 0
269                                                             || (fgets (from, sizeof (from), fp) != NULL
270                                                                 && strncmp (from, "From ", 5) == 0));
271                                                 fclose (fp);
272                                         }
273                                 }
274
275                                 if (folder != NULL || isfolder) {
276                                         fi = spool_new_fi (
277                                                 store, parent, fip, fname,
278                                                 CAMEL_FOLDER_NOINFERIORS |
279                                                 CAMEL_FOLDER_NOCHILDREN);
280                                         spool_fill_fi (
281                                                 store, fi, flags, cancellable);
282                                 }
283                                 if (folder)
284                                         g_object_unref (folder);
285
286                         } else if (S_ISDIR (st.st_mode)) {
287                                 struct _inode in = { st.st_dev, st.st_ino };
288
289                                 /* see if we've visited already */
290                                 if (g_hash_table_lookup (visited, &in) == NULL) {
291                                         struct _inode *inew = g_malloc (sizeof (*inew));
292
293                                         *inew = in;
294                                         g_hash_table_insert (visited, inew, inew);
295
296                                         if (scan_dir (store, visited, root, fname, flags, parent, fip, cancellable, error) == -1) {
297                                                 g_free (tmp);
298                                                 g_free (fname);
299                                                 closedir (dir);
300                                                 return -1;
301                                         }
302                                 }
303                         }
304                         g_free (fname);
305
306                 }
307                 g_free (tmp);
308         }
309         closedir (dir);
310
311         return 0;
312 }
313
314 static guint
315 inode_hash (gconstpointer d)
316 {
317         const struct _inode *v = d;
318
319         return v->inode ^ v->dnode;
320 }
321
322 static gboolean
323 inode_equal (gconstpointer a,
324              gconstpointer b)
325 {
326         const struct _inode *v1 = a, *v2 = b;
327
328         return v1->inode == v2->inode && v1->dnode == v2->dnode;
329 }
330
331 static void
332 inode_free (gpointer k,
333             gpointer v,
334             gpointer d)
335 {
336         g_free (k);
337 }
338
339 static CamelFolderInfo *
340 get_folder_info_elm (CamelStore *store,
341                      const gchar *top,
342                      guint32 flags,
343                      GCancellable *cancellable,
344                      GError **error)
345 {
346         CamelLocalSettings *local_settings;
347         CamelSettings *settings;
348         CamelService *service;
349         CamelFolderInfo *fi = NULL;
350         GHashTable *visited;
351         gchar *path;
352
353         service = CAMEL_SERVICE (store);
354
355         settings = camel_service_ref_settings (service);
356
357         local_settings = CAMEL_LOCAL_SETTINGS (settings);
358         path = camel_local_settings_dup_path (local_settings);
359
360         g_object_unref (settings);
361
362         visited = g_hash_table_new (inode_hash, inode_equal);
363
364         if (scan_dir (
365                 store, visited, path, top, flags,
366                 NULL, &fi, cancellable, error) == -1 && fi != NULL) {
367                 camel_folder_info_free (fi);
368                 fi = NULL;
369         }
370
371         g_hash_table_foreach (visited, inode_free, NULL);
372         g_hash_table_destroy (visited);
373
374         g_free (path);
375
376         return fi;
377 }
378
379 static CamelFolderInfo *
380 get_folder_info_mbox (CamelStore *store,
381                       const gchar *top,
382                       guint32 flags,
383                       GCancellable *cancellable,
384                       GError **error)
385 {
386         CamelFolderInfo *fi = NULL, *fip = NULL;
387
388         if (top == NULL || strcmp (top, "INBOX") == 0) {
389                 fi = spool_new_fi (
390                         store, NULL, &fip, "INBOX",
391                         CAMEL_FOLDER_NOINFERIORS |
392                         CAMEL_FOLDER_NOCHILDREN |
393                         CAMEL_FOLDER_SYSTEM);
394                 g_free (fi->display_name);
395                 fi->display_name = g_strdup (_("Inbox"));
396                 spool_fill_fi (store, fi, flags, cancellable);
397         }
398
399         return fi;
400 }
401
402 static gchar *
403 spool_store_get_name (CamelService *service,
404                       gboolean brief)
405 {
406         CamelLocalSettings *local_settings;
407         CamelSpoolStore *spool_store;
408         CamelSettings *settings;
409         gchar *name;
410         gchar *path;
411
412         spool_store = CAMEL_SPOOL_STORE (service);
413
414         settings = camel_service_ref_settings (service);
415
416         local_settings = CAMEL_LOCAL_SETTINGS (settings);
417         path = camel_local_settings_dup_path (local_settings);
418
419         g_object_unref (settings);
420
421         if (brief)
422                 return path;
423
424         switch (spool_store_get_type (spool_store, NULL)) {
425                 case CAMEL_SPOOL_STORE_MBOX:
426                         name = g_strdup_printf (
427                                 _("Spool mail file %s"), path);
428                         break;
429                 case CAMEL_SPOOL_STORE_ELM:
430                         name = g_strdup_printf (
431                                 _("Spool folder tree %s"), path);
432                         break;
433                 default:
434                         name = g_strdup (_("Invalid spool"));
435                         break;
436         }
437
438         g_free (path);
439
440         return name;
441 }
442
443 static CamelFolder *
444 spool_store_get_folder_sync (CamelStore *store,
445                              const gchar *folder_name,
446                              CamelStoreGetFolderFlags flags,
447                              GCancellable *cancellable,
448                              GError **error)
449 {
450         CamelLocalSettings *local_settings;
451         CamelSpoolStore *spool_store;
452         CamelSettings *settings;
453         CamelService *service;
454         CamelFolder *folder = NULL;
455         camel_spool_store_t type;
456         struct stat st;
457         gchar *name;
458         gchar *path;
459
460         d (printf ("opening folder %s on path %s\n", folder_name, path));
461
462         spool_store = CAMEL_SPOOL_STORE (store);
463         type = spool_store_get_type (spool_store, error);
464
465         if (type == CAMEL_SPOOL_STORE_INVALID)
466                 return NULL;
467
468         service = CAMEL_SERVICE (store);
469
470         settings = camel_service_ref_settings (service);
471
472         local_settings = CAMEL_LOCAL_SETTINGS (settings);
473         path = camel_local_settings_dup_path (local_settings);
474
475         g_object_unref (settings);
476
477         /* we only support an 'INBOX' in mbox mode */
478         if (type == CAMEL_SPOOL_STORE_MBOX) {
479                 if (strcmp (folder_name, "INBOX") != 0) {
480                         g_set_error (
481                                 error, CAMEL_STORE_ERROR,
482                                 CAMEL_STORE_ERROR_NO_FOLDER,
483                                 _("Folder '%s/%s' does not exist."),
484                                 path, folder_name);
485                 } else {
486                         folder = camel_spool_folder_new (
487                                 store, folder_name, flags, cancellable, error);
488                 }
489         } else {
490                 name = g_build_filename (path, folder_name, NULL);
491                 if (g_stat (name, &st) == -1) {
492                         if (errno != ENOENT) {
493                                 g_set_error (
494                                         error, G_IO_ERROR,
495                                         g_io_error_from_errno (errno),
496                                         _("Could not open folder '%s':\n%s"),
497                                         folder_name, g_strerror (errno));
498                         } else if ((flags & CAMEL_STORE_FOLDER_CREATE) == 0) {
499                                 g_set_error (
500                                         error, CAMEL_STORE_ERROR,
501                                         CAMEL_STORE_ERROR_NO_FOLDER,
502                                         _("Folder '%s' does not exist."),
503                                         folder_name);
504                         } else {
505                                 gint fd = creat (name, 0600);
506                                 if (fd == -1) {
507                                         g_set_error (
508                                                 error, G_IO_ERROR,
509                                                 g_io_error_from_errno (errno),
510                                                 _("Could not create folder '%s':\n%s"),
511                                                 folder_name, g_strerror (errno));
512                                 } else {
513                                         close (fd);
514                                         folder = camel_spool_folder_new (
515                                                 store, folder_name, flags,
516                                                 cancellable, error);
517                                 }
518                         }
519                 } else if (!S_ISREG (st.st_mode)) {
520                         g_set_error (
521                                 error, CAMEL_STORE_ERROR,
522                                 CAMEL_STORE_ERROR_NO_FOLDER,
523                                 _("'%s' is not a mailbox file."), name);
524                 } else {
525                         folder = camel_spool_folder_new (
526                                 store, folder_name, flags, cancellable, error);
527                 }
528                 g_free (name);
529         }
530
531         g_free (path);
532
533         return folder;
534 }
535
536 static CamelFolderInfo *
537 spool_store_get_folder_info_sync (CamelStore *store,
538                                   const gchar *top,
539                                   CamelStoreGetFolderInfoFlags flags,
540                                   GCancellable *cancellable,
541                                   GError **error)
542 {
543         CamelSpoolStore *spool_store;
544         CamelFolderInfo *folder_info = NULL;
545
546         spool_store = CAMEL_SPOOL_STORE (store);
547
548         switch (spool_store_get_type (spool_store, error)) {
549                 case CAMEL_SPOOL_STORE_MBOX:
550                         folder_info = get_folder_info_mbox (
551                                 store, top, flags, cancellable, error);
552                         break;
553
554                 case CAMEL_SPOOL_STORE_ELM:
555                         folder_info = get_folder_info_elm (
556                                 store, top, flags, cancellable, error);
557                         break;
558
559                 default:
560                         break;
561         }
562
563         return folder_info;
564 }
565
566 static CamelFolder *
567 spool_store_get_inbox_folder_sync (CamelStore *store,
568                                    GCancellable *cancellable,
569                                    GError **error)
570 {
571         CamelSpoolStore *spool_store;
572         CamelFolder *folder = NULL;
573
574         spool_store = CAMEL_SPOOL_STORE (store);
575
576         switch (spool_store_get_type (spool_store, error)) {
577                 case CAMEL_SPOOL_STORE_MBOX:
578                         folder = spool_store_get_folder_sync (
579                                 store, "INBOX", CAMEL_STORE_FOLDER_CREATE,
580                                 cancellable, error);
581                         break;
582
583                 case CAMEL_SPOOL_STORE_ELM:
584                         g_set_error (
585                                 error, CAMEL_STORE_ERROR,
586                                 CAMEL_STORE_ERROR_NO_FOLDER,
587                                 _("Store does not support an INBOX"));
588                         break;
589
590                 default:
591                         break;
592         }
593
594         return folder;
595 }
596
597 /* default implementation, only delete metadata */
598 static gboolean
599 spool_store_delete_folder_sync (CamelStore *store,
600                                 const gchar *folder_name,
601                                 GCancellable *cancellable,
602                                 GError **error)
603 {
604         g_set_error (
605                 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
606                 _("Spool folders cannot be deleted"));
607
608         return FALSE;
609 }
610
611 /* default implementation, rename all */
612 static gboolean
613 spool_store_rename_folder_sync (CamelStore *store,
614                                 const gchar *old,
615                                 const gchar *new,
616                                 GCancellable *cancellable,
617                                 GError **error)
618 {
619         g_set_error (
620                 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
621                 _("Spool folders cannot be renamed"));
622
623         return FALSE;
624 }
625
626 static gchar *
627 spool_store_get_full_path (CamelLocalStore *local_store,
628                            const gchar *full_name)
629 {
630         CamelLocalSettings *local_settings;
631         CamelSpoolStore *spool_store;
632         CamelSettings *settings;
633         CamelService *service;
634         gchar *full_path;
635         gchar *path;
636
637         service = CAMEL_SERVICE (local_store);
638
639         settings = camel_service_ref_settings (service);
640
641         local_settings = CAMEL_LOCAL_SETTINGS (settings);
642         path = camel_local_settings_dup_path (local_settings);
643
644         g_object_unref (settings);
645
646         spool_store = CAMEL_SPOOL_STORE (local_store);
647
648         switch (spool_store_get_type (spool_store, NULL)) {
649                 case CAMEL_SPOOL_STORE_MBOX:
650                         full_path = g_strdup (path);
651                         break;
652
653                 case CAMEL_SPOOL_STORE_ELM:
654                         full_path = g_build_filename (path, full_name, NULL);
655                         break;
656
657                 default:
658                         full_path = NULL;
659                         break;
660         }
661
662         g_free (path);
663
664         return full_path;
665 }
666
667 static gchar *
668 spool_store_get_meta_path (CamelLocalStore *ls,
669                            const gchar *full_name,
670                            const gchar *ext)
671 {
672         CamelService *service;
673         const gchar *user_data_dir;
674         gchar *path, *key;
675
676         service = CAMEL_SERVICE (ls);
677         user_data_dir = camel_service_get_user_data_dir (service);
678
679         key = camel_file_util_safe_filename (full_name);
680         path = g_strdup_printf ("%s/%s%s", user_data_dir, key, ext);
681         g_free (key);
682
683         return path;
684 }
685
686 static void
687 camel_spool_store_class_init (CamelSpoolStoreClass *class)
688 {
689         CamelServiceClass *service_class;
690         CamelStoreClass *store_class;
691         CamelLocalStoreClass *local_store_class;
692
693         g_type_class_add_private (class, sizeof (CamelSpoolStorePrivate));
694
695         service_class = CAMEL_SERVICE_CLASS (class);
696         service_class->settings_type = CAMEL_TYPE_SPOOL_SETTINGS;
697         service_class->get_name = spool_store_get_name;
698
699         store_class = CAMEL_STORE_CLASS (class);
700         store_class->get_folder_sync = spool_store_get_folder_sync;
701         store_class->get_folder_info_sync = spool_store_get_folder_info_sync;
702         store_class->get_inbox_folder_sync = spool_store_get_inbox_folder_sync;
703         store_class->delete_folder_sync = spool_store_delete_folder_sync;
704         store_class->rename_folder_sync = spool_store_rename_folder_sync;
705
706         local_store_class = CAMEL_LOCAL_STORE_CLASS (class);
707         local_store_class->get_full_path = spool_store_get_full_path;
708         local_store_class->get_meta_path = spool_store_get_meta_path;
709 }
710
711 static void
712 camel_spool_store_init (CamelSpoolStore *spool_store)
713 {
714         spool_store->priv = CAMEL_SPOOL_STORE_GET_PRIVATE (spool_store);
715 }