Fix FSF address (Tobias Mueller, #470445)
[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) 2001 Ximian Inc (www.ximian.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 #ifdef HAVE_ALLOCA_H
36 #include <alloca.h>
37 #endif
38
39 #include <glib/gi18n-lib.h>
40
41 #include <libedataserver/e-data-server-util.h>
42
43 #include "camel-exception.h"
44 #include "camel-file-utils.h"
45 #include "camel-private.h"
46 #include "camel-session.h"
47 #include "camel-url.h"
48
49 #include "camel-spool-folder.h"
50 #include "camel-spool-store.h"
51
52 #define d(x)
53
54 /* Returns the class for a CamelSpoolStore */
55 #define CSPOOLS_CLASS(so) CAMEL_SPOOL_STORE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
56 #define CF_CLASS(so) CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
57
58 static void construct (CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex);
59 static CamelFolder *get_folder(CamelStore * store, const char *folder_name, guint32 flags, CamelException * ex);
60 static char *get_name(CamelService *service, gboolean brief);
61 static CamelFolder *get_inbox (CamelStore *store, CamelException *ex);
62 static void rename_folder(CamelStore *store, const char *old_name, const char *new_name, CamelException *ex);
63 static CamelFolderInfo *get_folder_info (CamelStore *store, const char *top, guint32 flags, CamelException *ex);
64 static void free_folder_info (CamelStore *store, CamelFolderInfo *fi);
65
66 static void delete_folder(CamelStore *store, const char *folder_name, CamelException *ex);
67 static void rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex);
68
69 static char *spool_get_meta_path(CamelLocalStore *ls, const char *full_name, const char *ext);
70 static char *spool_get_full_path(CamelLocalStore *ls, const char *full_name);
71
72 static CamelStoreClass *parent_class = NULL;
73
74 static void
75 camel_spool_store_class_init (CamelSpoolStoreClass *camel_spool_store_class)
76 {
77         CamelStoreClass *camel_store_class = CAMEL_STORE_CLASS (camel_spool_store_class);
78         CamelServiceClass *camel_service_class = CAMEL_SERVICE_CLASS (camel_spool_store_class);
79         
80         parent_class = CAMEL_STORE_CLASS(camel_mbox_store_get_type());
81
82         /* virtual method overload */
83         camel_service_class->construct = construct;
84         camel_service_class->get_name = get_name;
85         camel_store_class->get_folder = get_folder;
86         camel_store_class->get_inbox = get_inbox;
87         camel_store_class->get_folder_info = get_folder_info;
88         camel_store_class->free_folder_info = free_folder_info;
89
90         camel_store_class->delete_folder = delete_folder;
91         camel_store_class->rename_folder = rename_folder;
92
93         ((CamelLocalStoreClass *)camel_store_class)->get_full_path = spool_get_full_path;
94         ((CamelLocalStoreClass *)camel_store_class)->get_meta_path = spool_get_meta_path;
95 }
96
97 CamelType
98 camel_spool_store_get_type (void)
99 {
100         static CamelType camel_spool_store_type = CAMEL_INVALID_TYPE;
101         
102         if (camel_spool_store_type == CAMEL_INVALID_TYPE)       {
103                 camel_spool_store_type = camel_type_register (camel_mbox_store_get_type(), "CamelSpoolStore",
104                                                              sizeof (CamelSpoolStore),
105                                                              sizeof (CamelSpoolStoreClass),
106                                                              (CamelObjectClassInitFunc) camel_spool_store_class_init,
107                                                              NULL,
108                                                              NULL,
109                                                              NULL);
110         }
111         
112         return camel_spool_store_type;
113 }
114
115 static void
116 construct (CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex)
117 {
118         struct stat st;
119
120         d(printf("constructing store of type %s '%s:%s'\n",
121                  camel_type_to_name(((CamelObject *)service)->s.type), url->protocol, url->path));
122
123         CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
124         if (camel_exception_is_set (ex))
125                 return;
126
127         if (service->url->path[0] != '/') {
128                 camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
129                                      _("Store root %s is not an absolute path"), service->url->path);
130                 return;
131         }
132
133         if (stat(service->url->path, &st) == -1) {
134                 camel_exception_setv (ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
135                                       _("Spool `%s' cannot be opened: %s"),
136                                       service->url->path, g_strerror (errno));
137                 return;
138         }
139
140         if (S_ISREG(st.st_mode))
141                 ((CamelSpoolStore *)service)->type = CAMEL_SPOOL_STORE_MBOX;
142         else if (S_ISDIR(st.st_mode))
143                 /* we could check here for slight variations */
144                 ((CamelSpoolStore *)service)->type = CAMEL_SPOOL_STORE_ELM;
145         else {
146                 camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
147                                      _("Spool `%s' is not a regular file or directory"),
148                                      service->url->path);
149                 return;
150         }
151 }
152
153 static CamelFolder *
154 get_folder(CamelStore * store, const char *folder_name, guint32 flags, CamelException * ex)
155 {
156         CamelFolder *folder = NULL;
157         struct stat st;
158         char *name;
159
160         d(printf("opening folder %s on path %s\n", folder_name, path));
161
162         /* we only support an 'INBOX' in mbox mode */
163         if (((CamelSpoolStore *)store)->type == CAMEL_SPOOL_STORE_MBOX) {
164                 if (strcmp(folder_name, "INBOX") != 0) {
165                         camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
166                                              _("Folder `%s/%s' does not exist."),
167                                              ((CamelService *)store)->url->path, folder_name);
168                 } else {
169                         folder = camel_spool_folder_new(store, folder_name, flags, ex);
170                 }
171         } else {
172                 name = g_strdup_printf("%s%s", CAMEL_LOCAL_STORE(store)->toplevel_dir, folder_name);
173                 if (stat(name, &st) == -1) {
174                         if (errno != ENOENT) {
175                                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
176                                                       _("Could not open folder `%s':\n%s"),
177                                                       folder_name, g_strerror (errno));
178                         } else if ((flags & CAMEL_STORE_FOLDER_CREATE) == 0) {
179                                 camel_exception_setv (ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
180                                                       _("Folder `%s' does not exist."),
181                                                       folder_name);
182                         } else {
183                                 if (creat (name, 0600) == -1) {
184                                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
185                                                               _("Could not create folder `%s':\n%s"),
186                                                               folder_name, g_strerror (errno));
187                                 } else {
188                                         folder = camel_spool_folder_new(store, folder_name, flags, ex);
189                                 }
190                         }
191                 } else if (!S_ISREG(st.st_mode)) {
192                         camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
193                                              _("`%s' is not a mailbox file."), name);
194                 } else {
195                         folder = camel_spool_folder_new(store, folder_name, flags, ex);
196                 }
197                 g_free(name);
198         }
199
200         return folder;
201 }
202
203 static CamelFolder *
204 get_inbox(CamelStore *store, CamelException *ex)
205 {
206         if (((CamelSpoolStore *)store)->type == CAMEL_SPOOL_STORE_MBOX)
207                 return get_folder (store, "INBOX", CAMEL_STORE_FOLDER_CREATE, ex);
208         else {
209                 camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
210                                      _("Store does not support an INBOX"));
211                 return NULL;
212         }
213 }
214
215 static char *
216 get_name (CamelService *service, gboolean brief)
217 {
218         if (brief)
219                 return g_strdup(service->url->path);
220         else
221                 return g_strdup_printf(((CamelSpoolStore *)service)->type == CAMEL_SPOOL_STORE_MBOX?
222                                        _("Spool mail file %s"):_("Spool folder tree %s"), service->url->path);
223 }
224
225 /* default implementation, rename all */
226 static void
227 rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex)
228 {
229         camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
230                              _("Spool folders cannot be renamed"));
231 }
232
233 /* default implementation, only delete metadata */
234 static void
235 delete_folder(CamelStore *store, const char *folder_name, CamelException *ex)
236 {
237         camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
238                              _("Spool folders cannot be deleted"));
239 }
240
241 static void free_folder_info (CamelStore *store, CamelFolderInfo *fi)
242 {
243         if (fi) {
244                 g_free(fi->uri);
245                 g_free(fi->name);
246                 g_free(fi->full_name);
247                 g_free(fi);
248         }
249 }
250
251 /* partially copied from mbox */
252 static void
253 spool_fill_fi(CamelStore *store, CamelFolderInfo *fi, guint32 flags)
254 {
255         CamelFolder *folder;
256
257         fi->unread = -1;
258         fi->total = -1;
259         folder = camel_object_bag_get(store->folders, fi->full_name);
260         if (folder) {
261                 if ((flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0)
262                         camel_folder_refresh_info(folder, NULL);
263                 fi->unread = camel_folder_get_unread_message_count(folder);
264                 fi->total = camel_folder_get_message_count(folder);
265                 camel_object_unref(folder);
266         }
267 }
268
269 static CamelFolderInfo *
270 spool_new_fi(CamelStore *store, CamelFolderInfo *parent, CamelFolderInfo **fip, const char *full, guint32 flags)
271 {
272         CamelFolderInfo *fi;
273         const char *name;
274         CamelURL *url;
275
276         name = strrchr(full, '/');
277         if (name)
278                 name++;
279         else
280                 name = full;
281
282         fi = g_malloc0(sizeof(*fi));
283         url = camel_url_copy(((CamelService *)store)->url);
284         camel_url_set_fragment(url, full);
285         fi->uri = camel_url_to_string(url, 0);
286         camel_url_free(url);
287         fi->full_name = g_strdup(full);
288         fi->name = g_strdup(name);
289         fi->unread = -1;
290         fi->total = -1;
291         fi->flags = flags;
292
293         fi->parent = parent;
294         fi->next = *fip;
295         *fip = fi;
296
297         d(printf("Adding spoold info: '%s' '%s' '%s' '%s'\n", fi->path, fi->name, fi->full_name, fi->url));
298
299         return fi;
300 }
301
302 /* used to find out where we've visited already */
303 struct _inode {
304         dev_t dnode;
305         ino_t inode;
306 };
307
308 /* returns number of records found at or below this level */
309 static int scan_dir(CamelStore *store, GHashTable *visited, char *root, const char *path, guint32 flags, CamelFolderInfo *parent, CamelFolderInfo **fip, CamelException *ex)
310 {
311         DIR *dir;
312         struct dirent *d;
313         char *name, *tmp, *fname;
314         CamelFolderInfo *fi = NULL;
315         struct stat st;
316         CamelFolder *folder;
317         char from[80];
318         FILE *fp;
319
320         d(printf("checking dir '%s' part '%s' for mbox content\n", root, path));
321
322         /* look for folders matching the right structure, recursively */
323         if (path) {
324                 name = alloca(strlen(root) + strlen(path) + 2);
325                 sprintf(name, "%s/%s", root, path);
326         } else
327                 name = root;
328
329         if (stat(name, &st) == -1) {
330                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
331                                       _("Could not scan folder `%s': %s"),
332                                       name, g_strerror (errno));
333         } else if (S_ISREG(st.st_mode)) {
334                 /* incase we start scanning from a file.  messy duplication :-/ */
335                 if (path) {
336                         fi = spool_new_fi(store, parent, fip, path, CAMEL_FOLDER_NOINFERIORS|CAMEL_FOLDER_NOCHILDREN);
337                         spool_fill_fi(store, fi, flags);
338                 }
339                 return 0;
340         }
341
342         dir = opendir(name);
343         if (dir == NULL) {
344                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
345                                       _("Could not scan folder `%s': %s"),
346                                       name, g_strerror (errno));
347                 return -1;
348         }
349
350         if (path != NULL) {
351                 fi = spool_new_fi(store, parent, fip, path, CAMEL_FOLDER_NOSELECT);     
352                 fip = &fi->child;
353                 parent = fi;
354         }
355         
356         while ( (d = readdir(dir)) ) {
357                 if (strcmp(d->d_name, ".") == 0
358                     || strcmp(d->d_name, "..") == 0)
359                         continue;
360                 
361                 tmp = g_strdup_printf("%s/%s", name, d->d_name);
362                 if (stat(tmp, &st) == 0) {
363                         if (path)
364                                 fname = g_strdup_printf("%s/%s", path, d->d_name);
365                         else
366                                 fname = g_strdup(d->d_name);
367
368                         if (S_ISREG(st.st_mode)) {
369                                 int isfolder = FALSE;
370
371                                 /* first, see if we already have it open */
372                                 folder = camel_object_bag_get(store->folders, fname);
373                                 if (folder == NULL) {
374                                         fp = fopen(tmp, "r");
375                                         if (fp != NULL) {
376                                                 isfolder = (st.st_size == 0
377                                                             || (fgets(from, sizeof(from), fp) != NULL
378                                                                 && strncmp(from, "From ", 5) == 0));
379                                                 fclose(fp);
380                                         }
381                                 }
382
383                                 if (folder != NULL || isfolder) {
384                                         fi = spool_new_fi(store, parent, fip, fname, CAMEL_FOLDER_NOINFERIORS|CAMEL_FOLDER_NOCHILDREN);
385                                         spool_fill_fi(store, fi, flags);
386                                 }
387                                 if (folder)
388                                         camel_object_unref(folder);
389
390                         } else if (S_ISDIR(st.st_mode)) {
391                                 struct _inode in = { st.st_dev, st.st_ino };
392                         
393                                 /* see if we've visited already */
394                                 if (g_hash_table_lookup(visited, &in) == NULL) {
395                                         struct _inode *inew = g_malloc(sizeof(*inew));
396                                 
397                                         *inew = in;
398                                         g_hash_table_insert(visited, inew, inew);
399
400                                         if (scan_dir(store, visited, root, fname, flags, parent, fip, ex) == -1) {
401                                                 g_free(tmp);
402                                                 g_free(fname);
403                                                 closedir(dir);
404                                                 return -1;
405                                         }
406                                 }
407                         }
408                         g_free(fname);
409
410                 }
411                 g_free(tmp);
412         }
413         closedir(dir);
414
415         return 0;
416 }
417
418 static guint inode_hash(const void *d)
419 {
420         const struct _inode *v = d;
421
422         return v->inode ^ v->dnode;
423 }
424
425 static gboolean inode_equal(const void *a, const void *b)
426 {
427         const struct _inode *v1 = a, *v2 = b;
428         
429         return v1->inode == v2->inode && v1->dnode == v2->dnode;
430 }
431
432 static void inode_free(void *k, void *v, void *d)
433 {
434         g_free(k);
435 }
436
437 static CamelFolderInfo *
438 get_folder_info_elm(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
439 {
440         CamelFolderInfo *fi = NULL;
441         GHashTable *visited;
442
443         visited = g_hash_table_new(inode_hash, inode_equal);
444
445         if (scan_dir(store, visited, ((CamelService *)store)->url->path, top, flags, NULL, &fi, ex) == -1 && fi != NULL) {
446                 camel_store_free_folder_info_full(store, fi);
447                 fi = NULL;
448         }
449
450         g_hash_table_foreach(visited, inode_free, NULL);
451         g_hash_table_destroy(visited);
452
453         return fi;
454 }
455
456 static CamelFolderInfo *
457 get_folder_info_mbox(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
458 {
459         CamelFolderInfo *fi = NULL, *fip = NULL;
460
461         if (top == NULL || strcmp(top, "INBOX") == 0) {
462                 fi = spool_new_fi(store, NULL, &fip, "INBOX", CAMEL_FOLDER_NOINFERIORS|CAMEL_FOLDER_NOCHILDREN|CAMEL_FOLDER_SYSTEM);
463                 g_free(fi->name);
464                 fi->name = g_strdup(_("Inbox"));
465                 spool_fill_fi(store, fi, flags);
466         }
467
468         return fi;
469 }
470
471 static CamelFolderInfo *
472 get_folder_info(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
473 {
474         if (((CamelSpoolStore *)store)->type == CAMEL_SPOOL_STORE_MBOX)
475                 return get_folder_info_mbox(store, top, flags, ex);
476         else
477                 return get_folder_info_elm(store, top, flags, ex);
478 }
479
480 static char *
481 spool_get_full_path(CamelLocalStore *ls, const char *full_name)
482 {
483         if (((CamelSpoolStore *)ls)->type == CAMEL_SPOOL_STORE_MBOX)
484                 /* a trailing / is always present on toplevel_dir from CamelLocalStore */
485                 return g_strndup(ls->toplevel_dir, strlen(ls->toplevel_dir)-1);
486         else
487                 return g_strdup_printf("%s/%s", ls->toplevel_dir, full_name);
488 }
489
490 static char *
491 spool_get_meta_path(CamelLocalStore *ls, const char *full_name, const char *ext)
492 {
493         char *root = camel_session_get_storage_path(((CamelService *)ls)->session, (CamelService *)ls, NULL);
494         char *path, *key;
495
496         if (root == NULL)
497                 return NULL;
498
499         g_mkdir_with_parents(root, 0777);
500         key = camel_file_util_safe_filename(full_name);
501         path = g_strdup_printf("%s/%s%s", root, key, ext);
502         g_free(key);
503         g_free(root);
504
505         return path;
506 }