Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / providers / local / camel-local-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) 2000 Ximian, Inc.
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 <errno.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <unistd.h>
30 #include <sys/stat.h>
31
32 #include <glib.h>
33 #include <glib/gi18n-lib.h>
34 #include <glib/gstdio.h>
35
36 #include <libedataserver/e-data-server-util.h>
37
38 #include "camel/camel-exception.h"
39 #include "camel/camel-file-utils.h"
40 #include "camel/camel-private.h"
41 #include "camel/camel-text-index.h"
42 #include "camel/camel-url.h"
43 #include "camel/camel-vtrash-folder.h"
44
45 #include "camel-local-folder.h"
46 #include "camel-local-store.h"
47
48 #define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
49
50 /* Returns the class for a CamelLocalStore */
51 #define CLOCALS_CLASS(so) CAMEL_LOCAL_STORE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
52 #define CF_CLASS(so) CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
53
54 static void construct (CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex);
55 static CamelFolder *get_folder(CamelStore * store, const char *folder_name, guint32 flags, CamelException * ex);
56 static char *get_name(CamelService *service, gboolean brief);
57 static CamelFolder *local_get_inbox (CamelStore *store, CamelException *ex);
58 static CamelFolder *local_get_junk(CamelStore *store, CamelException *ex);
59 static CamelFolder *local_get_trash(CamelStore *store, CamelException *ex);
60 static CamelFolderInfo *get_folder_info (CamelStore *store, const char *top, guint32 flags, CamelException *ex);
61 static void delete_folder(CamelStore *store, const char *folder_name, CamelException *ex);
62 static void rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex);
63 static CamelFolderInfo *create_folder(CamelStore *store, const char *parent_name, const char *folder_name, CamelException *ex);
64
65 static char *local_get_full_path(CamelLocalStore *lf, const char *full_name);
66 static char *local_get_meta_path(CamelLocalStore *lf, const char *full_name, const char *ext);
67
68 static CamelStoreClass *parent_class = NULL;
69
70 static void
71 camel_local_store_class_init (CamelLocalStoreClass *camel_local_store_class)
72 {
73         CamelStoreClass *camel_store_class = CAMEL_STORE_CLASS (camel_local_store_class);
74         CamelServiceClass *camel_service_class = CAMEL_SERVICE_CLASS (camel_local_store_class);
75         
76         parent_class = CAMEL_STORE_CLASS (camel_type_get_global_classfuncs (camel_store_get_type ()));
77
78         /* virtual method overload */
79         camel_service_class->construct = construct;
80         camel_service_class->get_name = get_name;
81         camel_store_class->get_folder = get_folder;
82         camel_store_class->get_inbox = local_get_inbox;
83         camel_store_class->get_trash = local_get_trash;
84         camel_store_class->get_junk = local_get_junk;
85         camel_store_class->get_folder_info = get_folder_info;
86         camel_store_class->free_folder_info = camel_store_free_folder_info_full;
87
88         camel_store_class->create_folder = create_folder;
89         camel_store_class->delete_folder = delete_folder;
90         camel_store_class->rename_folder = rename_folder;
91
92         camel_local_store_class->get_full_path = local_get_full_path;
93         camel_local_store_class->get_meta_path = local_get_meta_path;
94 }
95
96 static void
97 camel_local_store_finalize (CamelLocalStore *local_store)
98 {
99         if (local_store->toplevel_dir)
100                 g_free (local_store->toplevel_dir);
101 }
102
103 CamelType
104 camel_local_store_get_type (void)
105 {
106         static CamelType camel_local_store_type = CAMEL_INVALID_TYPE;
107         
108         if (camel_local_store_type == CAMEL_INVALID_TYPE)       {
109                 camel_local_store_type = camel_type_register (CAMEL_STORE_TYPE, "CamelLocalStore",
110                                                              sizeof (CamelLocalStore),
111                                                              sizeof (CamelLocalStoreClass),
112                                                              (CamelObjectClassInitFunc) camel_local_store_class_init,
113                                                              NULL,
114                                                              NULL,
115                                                              (CamelObjectFinalizeFunc) camel_local_store_finalize);
116         }
117         
118         return camel_local_store_type;
119 }
120
121 static void
122 construct (CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex)
123 {
124         CamelLocalStore *local_store = CAMEL_LOCAL_STORE (service);
125         int len;
126
127         CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
128         if (camel_exception_is_set (ex))
129                 return;
130
131         len = strlen (service->url->path);
132         if (!G_IS_DIR_SEPARATOR (service->url->path[len - 1]))
133                 local_store->toplevel_dir = g_strdup_printf ("%s/", service->url->path);
134         else
135                 local_store->toplevel_dir = g_strdup (service->url->path);
136 }
137
138 const char *
139 camel_local_store_get_toplevel_dir (CamelLocalStore *store)
140 {
141         return store->toplevel_dir;
142 }
143
144 static CamelFolder *
145 get_folder(CamelStore * store, const char *folder_name, guint32 flags, CamelException * ex)
146 {
147         int len = strlen(((CamelLocalStore *)store)->toplevel_dir);
148         char *path = g_alloca(len + 1);
149         struct stat st;
150
151         strcpy(path, ((CamelLocalStore *)store)->toplevel_dir);
152         if (G_IS_DIR_SEPARATOR(path[len-1]))
153                 path[len-1] = '\0';
154         
155         if (!g_path_is_absolute(path)) {
156                 camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
157                                      _("Store root %s is not an absolute path"), path);
158                 return NULL;
159         }
160
161         if (g_stat(path, &st) == 0) {
162                 if (!S_ISDIR(st.st_mode)) {
163                         camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
164                                              _("Store root %s is not a regular directory"), path);
165                         return NULL;
166                 }
167                 return (CamelFolder *) 0xdeadbeef;
168         }
169
170         if (errno != ENOENT
171             || (flags & CAMEL_STORE_FOLDER_CREATE) == 0) {
172                 camel_exception_setv (ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
173                                       _("Cannot get folder: %s: %s"),
174                                       path, g_strerror (errno));
175                 return NULL;
176         }
177         
178         /* need to create the dir heirarchy */
179         if (g_mkdir_with_parents (path, 0777) == -1 && errno != EEXIST) {
180                 camel_exception_setv (ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
181                                       _("Cannot get folder: %s: %s"),
182                                       path, g_strerror (errno));
183                 return NULL;
184         }
185         
186         return (CamelFolder *) 0xdeadbeef;
187 }
188
189 static CamelFolder *
190 local_get_inbox(CamelStore *store, CamelException *ex)
191 {
192         camel_exception_set(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
193                             _("Local stores do not have an inbox"));
194         return NULL;
195 }
196
197 static CamelFolder *
198 local_get_trash(CamelStore *store, CamelException *ex)
199 {
200         CamelFolder *folder = CAMEL_STORE_CLASS(parent_class)->get_trash(store, ex);
201
202         if (folder) {
203                 char *state = camel_local_store_get_meta_path(store, CAMEL_VTRASH_NAME, ".cmeta");
204
205                 camel_object_set(folder, NULL, CAMEL_OBJECT_STATE_FILE, state, NULL);
206                 g_free(state);
207                 /* no defaults? */
208                 camel_object_state_read(folder);
209         }
210
211         return folder;
212 }
213
214 static CamelFolder *
215 local_get_junk(CamelStore *store, CamelException *ex)
216 {
217         CamelFolder *folder = CAMEL_STORE_CLASS(parent_class)->get_junk(store, ex);
218
219         if (folder) {
220                 char *state = camel_local_store_get_meta_path(store, CAMEL_VJUNK_NAME, ".cmeta");
221
222                 camel_object_set(folder, NULL, CAMEL_OBJECT_STATE_FILE, state, NULL);
223                 g_free(state);
224                 /* no defaults? */
225                 camel_object_state_read(folder);
226         }
227
228         return folder;
229 }
230
231 static char *
232 get_name (CamelService *service, gboolean brief)
233 {
234         char *dir = ((CamelLocalStore*)service)->toplevel_dir;
235
236         if (brief)
237                 return g_strdup (dir);
238         else
239                 return g_strdup_printf (_("Local mail file %s"), dir);
240 }
241
242 static CamelFolderInfo *
243 get_folder_info (CamelStore *store, const char *top,
244                  guint32 flags, CamelException *ex)
245 {
246         /* FIXME: This is broken, but it corresponds to what was
247          * there before.
248          */
249
250         d(printf("-- LOCAL STORE -- get folder info: %s\n", top));
251
252         return NULL;
253 }
254
255 static CamelFolderInfo *
256 create_folder(CamelStore *store, const char *parent_name, const char *folder_name, CamelException *ex)
257 {
258         char *path = ((CamelLocalStore *)store)->toplevel_dir;
259         char *name;
260         CamelFolder *folder;
261         CamelFolderInfo *info = NULL;
262         struct stat st;
263
264         /* This is a pretty hacky version of create folder, but should basically work */
265
266         if (!g_path_is_absolute(path)) {
267                 camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
268                                      _("Store root %s is not an absolute path"), path);
269                 return NULL;
270         }
271
272         if (parent_name)
273                 name = g_strdup_printf("%s/%s/%s", path, parent_name, folder_name);
274         else
275                 name = g_strdup_printf("%s/%s", path, folder_name);
276
277         if (g_stat(name, &st) == 0 || errno != ENOENT) {
278                 camel_exception_setv (ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
279                                       _("Cannot get folder: %s: %s"),
280                                       name, g_strerror (errno));
281                 g_free(name);
282                 return NULL;
283         }
284
285         g_free(name);
286
287         if (parent_name)
288                 name = g_strdup_printf("%s/%s", parent_name, folder_name);
289         else
290                 name = g_strdup_printf("%s", folder_name);
291
292         folder = ((CamelStoreClass *)((CamelObject *)store)->klass)->get_folder(store, name, CAMEL_STORE_FOLDER_CREATE, ex);
293         if (folder) {
294                 camel_object_unref((CamelObject *)folder);
295                 info = ((CamelStoreClass *)((CamelObject *)store)->klass)->get_folder_info(store, name, 0, ex);
296
297                 /* get_folder(CREATE) will emit a folder_created event for us */
298                 /*if (info)
299                   camel_object_trigger_event((CamelObject *)store, "folder_created", info);*/
300         }
301
302         g_free(name);
303
304         return info;
305 }
306
307 static int xrename(const char *oldp, const char *newp, const char *prefix, const char *suffix, int missingok, CamelException *ex)
308 {
309         struct stat st;
310         char *old = g_strconcat(prefix, oldp, suffix, NULL);
311         char *new = g_strconcat(prefix, newp, suffix, NULL);
312         int ret = -1;
313         int err = 0;
314
315         d(printf("renaming %s%s to %s%s\n", oldp, suffix, newp, suffix));
316
317         if (g_stat(old, &st) == -1) {
318                 if (missingok && errno == ENOENT) {
319                         ret = 0;
320                 } else {
321                         err = errno;
322                         ret = -1;
323                 }
324 #ifndef G_OS_WIN32
325         } else if (S_ISDIR(st.st_mode)) { /* use rename for dirs */
326                 if (rename(old, new) == 0
327                     || stat(new, &st) == 0) {
328                         ret = 0;
329                 } else {
330                         err = errno;
331                         ret = -1;
332                 }
333         } else if (link(old, new) == 0 /* and link for files */
334                    || (stat(new, &st) == 0 && st.st_nlink == 2)) {
335                 if (unlink(old) == 0) {
336                         ret = 0;
337                 } else {
338                         err = errno;
339                         unlink(new);
340                         ret = -1;
341                 }
342         } else {
343                 err = errno;
344                 ret = -1;
345 #else
346         } else if ((!g_file_test (new, G_FILE_TEST_EXISTS) || g_remove (new) == 0) &&
347                    g_rename(old, new) == 0) {
348                 ret = 0;
349         } else {
350                 err = errno;
351                 ret = -1;
352 #endif
353         }
354
355         if (ret == -1) {
356                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
357                                       _("Could not rename folder %s to %s: %s"),
358                                       old, new, g_strerror (err));
359         }
360
361         g_free(old);
362         g_free(new);
363         return ret;
364 }
365
366 /* default implementation, rename all */
367 static void
368 rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex)
369 {
370         char *path = CAMEL_LOCAL_STORE (store)->toplevel_dir;
371         CamelLocalFolder *folder = NULL;
372         char *newibex = g_strdup_printf("%s%s.ibex", path, new);
373         char *oldibex = g_strdup_printf("%s%s.ibex", path, old);
374
375         /* try to rollback failures, has obvious races */
376
377         d(printf("local rename folder '%s' '%s'\n", old, new));
378
379         folder = camel_object_bag_get(store->folders, old);
380         if (folder && folder->index) {
381                 if (camel_index_rename(folder->index, newibex) == -1)
382                         goto ibex_failed;
383         } else {
384                 /* TODO: camel_text_index_rename should find out if we have an active index itself? */
385                 if (camel_text_index_rename(oldibex, newibex) == -1)
386                         goto ibex_failed;
387         }
388
389         if (xrename(old, new, path, ".ev-summary", TRUE, ex))
390                 goto summary_failed;
391
392         if (xrename(old, new, path, ".ev-summary-meta", TRUE, ex))
393                 goto summary_failed;
394
395         if (xrename(old, new, path, ".cmeta", TRUE, ex))
396                 goto cmeta_failed;
397
398         if (xrename(old, new, path, "", FALSE, ex))
399                 goto base_failed;
400
401         g_free(newibex);
402         g_free(oldibex);
403
404         if (folder)
405                 camel_object_unref(folder);
406
407         return;
408
409         /* The (f)utility of this recovery effort is quesitonable */
410
411 base_failed:
412         xrename(new, old, path, ".cmeta", TRUE, ex);
413
414 cmeta_failed:
415         xrename(new, old, path, ".ev-summary", TRUE, ex);
416         xrename(new, old, path, ".ev-summary-meta", TRUE, ex);
417 summary_failed:
418         if (folder) {
419                 if (folder->index)
420                         camel_index_rename(folder->index, oldibex);
421         } else
422                 camel_text_index_rename(newibex, oldibex);
423 ibex_failed:
424         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
425                               _("Could not rename '%s': %s"),
426                               old, g_strerror (errno));
427
428         g_free(newibex);
429         g_free(oldibex);
430
431         if (folder)
432                 camel_object_unref(folder);
433 }
434
435 /* default implementation, only delete metadata */
436 static void
437 delete_folder(CamelStore *store, const char *folder_name, CamelException *ex)
438 {
439         CamelFolderInfo *fi;
440         CamelException lex;
441         CamelFolder *lf;
442         char *name;
443         char *str;
444         
445         /* remove metadata only */
446         name = g_strdup_printf("%s%s", CAMEL_LOCAL_STORE(store)->toplevel_dir, folder_name);
447         str = g_strdup_printf("%s.ev-summary", name);
448         if (g_unlink(str) == -1 && errno != ENOENT) {
449                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
450                                       _("Could not delete folder summary file `%s': %s"),
451                                       str, g_strerror (errno));
452                 g_free(str);
453                 g_free (name);
454                 return;
455         }
456         g_free(str);
457         str = g_strdup_printf("%s.ev-summary-meta", name);
458         if (g_unlink(str) == -1 && errno != ENOENT) {
459                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
460                                       _("Could not delete folder summary file `%s': %s"),
461                                       str, g_strerror (errno));
462                 g_free(str);
463                 g_free (name);
464                 return;
465         }
466         g_free(str);
467         str = g_strdup_printf("%s.ibex", name);
468         if (camel_text_index_remove(str) == -1 && errno != ENOENT) {
469                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
470                                       _("Could not delete folder index file `%s': %s"),
471                                       str, g_strerror (errno));
472                 g_free(str);
473                 g_free (name);
474                 return;
475         }
476         g_free(str);
477
478         str = NULL;
479         camel_exception_init (&lex);
480         if ((lf = camel_store_get_folder (store, folder_name, 0, &lex))) {
481                 camel_object_get (lf, NULL, CAMEL_OBJECT_STATE_FILE, &str, NULL);
482                 camel_object_set (lf, NULL, CAMEL_OBJECT_STATE_FILE, NULL, NULL);
483                 camel_object_unref (lf);
484         } else {
485                 camel_exception_clear (&lex);
486         }
487         
488         if (str == NULL)
489                 str = g_strdup_printf ("%s.cmeta", name);
490         
491         if (g_unlink (str) == -1 && errno != ENOENT) {
492                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
493                                       _("Could not delete folder meta file `%s': %s"),
494                                       str, g_strerror (errno));
495                 g_free (name);
496                 g_free (str);
497                 return;
498         }
499         
500         g_free (str);
501         g_free (name);
502         
503         fi = g_new0 (CamelFolderInfo, 1);
504         fi->full_name = g_strdup (folder_name);
505         fi->name = g_path_get_basename (folder_name);
506         fi->uri = g_strdup_printf ("%s:%s#%s", ((CamelService *) store)->url->protocol,
507                                    CAMEL_LOCAL_STORE(store)->toplevel_dir, folder_name);
508         fi->unread = -1;
509         
510         camel_object_trigger_event (store, "folder_deleted", fi);
511         
512         camel_folder_info_free (fi);
513 }
514
515 static char *
516 local_get_full_path(CamelLocalStore *ls, const char *full_name)
517 {
518         return g_strdup_printf("%s%s", ls->toplevel_dir, full_name);
519 }
520
521 static char *
522 local_get_meta_path(CamelLocalStore *ls, const char *full_name, const char *ext)
523 {
524         return g_strdup_printf("%s%s%s", ls->toplevel_dir, full_name, ext);
525 }