1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 * Copyright (C) 2000 Ximian, Inc.
5 * Authors: Michael Zucchi <notzed@ximian.com>
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.
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.
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
32 #include <glib/gi18n-lib.h>
34 #include "camel-exception.h"
35 #include "camel-private.h"
36 #include "camel-stream-buffer.h"
37 #include "camel-stream-fs.h"
38 #include "camel-url.h"
40 #include "camel-mh-folder.h"
41 #include "camel-mh-store.h"
42 #include "camel-mh-summary.h"
44 static CamelLocalStoreClass *parent_class = NULL;
48 /* Returns the class for a CamelMhStore */
49 #define CMHS_CLASS(so) CAMEL_MH_STORE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
50 #define CF_CLASS(so) CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
51 #define CMHF_CLASS(so) CAMEL_MH_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
53 static void construct (CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex);
54 static CamelFolder *get_folder(CamelStore * store, const char *folder_name, guint32 flags, CamelException * ex);
55 static CamelFolder *get_inbox (CamelStore *store, CamelException *ex);
56 static void delete_folder(CamelStore * store, const char *folder_name, CamelException * ex);
57 static void rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex);
58 static CamelFolderInfo * get_folder_info (CamelStore *store, const char *top, guint32 flags, CamelException *ex);
60 static void camel_mh_store_class_init(CamelObjectClass * camel_mh_store_class)
62 CamelStoreClass *camel_store_class = CAMEL_STORE_CLASS(camel_mh_store_class);
63 CamelServiceClass *camel_service_class = CAMEL_SERVICE_CLASS(camel_mh_store_class);
65 parent_class = (CamelLocalStoreClass *)camel_type_get_global_classfuncs(camel_local_store_get_type());
67 /* virtual method overload, use defaults for most */
68 camel_service_class->construct = construct;
70 camel_store_class->get_folder = get_folder;
71 camel_store_class->get_inbox = get_inbox;
72 camel_store_class->delete_folder = delete_folder;
73 camel_store_class->rename_folder = rename_folder;
74 camel_store_class->get_folder_info = get_folder_info;
77 CamelType camel_mh_store_get_type(void)
79 static CamelType camel_mh_store_type = CAMEL_INVALID_TYPE;
81 if (camel_mh_store_type == CAMEL_INVALID_TYPE) {
82 camel_mh_store_type = camel_type_register(CAMEL_LOCAL_STORE_TYPE, "CamelMhStore",
84 sizeof(CamelMhStoreClass),
85 (CamelObjectClassInitFunc) camel_mh_store_class_init,
91 return camel_mh_store_type;
95 construct (CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex)
97 CamelMhStore *mh_store = (CamelMhStore *)service;
99 CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
100 if (camel_exception_is_set (ex))
103 if (camel_url_get_param(url, "dotfolders"))
104 mh_store->flags |= CAMEL_MH_DOTFOLDERS;
114 /* update the .folders file if it exists, or create it if it doesn't */
116 folders_update(const char *root, int mode, const char *folder, const char *new)
118 char *tmp, *tmpnew, *line = NULL;
119 CamelStream *stream, *in = NULL, *out = NULL;
120 int flen = strlen(folder);
122 tmpnew = g_alloca (strlen (root) + 16);
123 sprintf (tmpnew, "%s.folders~", root);
125 out = camel_stream_fs_new_with_name(tmpnew, O_WRONLY|O_CREAT|O_TRUNC, 0666);
129 tmp = g_alloca (strlen (root) + 16);
130 sprintf (tmp, "%s.folders", root);
131 stream = camel_stream_fs_new_with_name(tmp, O_RDONLY, 0);
133 in = camel_stream_buffer_new(stream, CAMEL_STREAM_BUFFER_READ);
134 camel_object_unref(stream);
136 if (in == NULL || stream == NULL) {
137 if (mode == UPDATE_ADD && camel_stream_printf(out, "%s\n", folder) == -1)
142 while ((line = camel_stream_buffer_read_line((CamelStreamBuffer *)in))) {
147 if (strcmp(line, folder) == 0)
151 if (strncmp(line, folder, flen) == 0
152 && (line[flen] == 0 || line[flen] == '/')) {
153 if (camel_stream_write(out, new, strlen(new)) == -1
154 || camel_stream_write(out, line+flen, strlen(line)-flen) == -1
155 || camel_stream_write(out, "\n", 1) == -1)
161 int cmp = strcmp(line, folder);
164 /* found insertion point */
165 if (camel_stream_printf(out, "%s\n", folder) == -1)
168 } else if (tmp == 0) {
177 if (copy && camel_stream_printf(out, "%s\n", line) == -1)
185 if (mode == UPDATE_ADD && camel_stream_printf(out, "%s\n", folder) == -1)
188 if (camel_stream_close(out) == -1)
192 /* should we care if this fails? I suppose so ... */
195 unlink(tmpnew); /* remove it if its there */
198 camel_object_unref(in);
200 camel_object_unref(out);
204 get_folder(CamelStore * store, const char *folder_name, guint32 flags, CamelException * ex)
209 if (!((CamelStoreClass *)parent_class)->get_folder(store, folder_name, flags, ex))
212 name = g_strdup_printf("%s%s", CAMEL_LOCAL_STORE(store)->toplevel_dir, folder_name);
214 if (stat(name, &st) == -1) {
215 if (errno != ENOENT) {
216 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
217 _("Cannot get folder `%s': %s"),
218 folder_name, g_strerror (errno));
222 if ((flags & CAMEL_STORE_FOLDER_CREATE) == 0) {
223 camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
224 _("Cannot get folder `%s': folder does not exist."),
230 if (mkdir(name, 0777) != 0) {
231 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
232 _("Could not create folder `%s': %s"),
233 folder_name, g_strerror (errno));
238 /* add to .folders if we are supposed to */
239 /* FIXME: throw exception on error */
240 if (((CamelMhStore *)store)->flags & CAMEL_MH_DOTFOLDERS)
241 folders_update(((CamelLocalStore *)store)->toplevel_dir, UPDATE_ADD, folder_name, NULL);
242 } else if (!S_ISDIR(st.st_mode)) {
243 camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
244 _("Cannot get folder `%s': not a directory."), folder_name);
247 } else if (flags & CAMEL_STORE_FOLDER_EXCL) {
248 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
249 _("Cannot create folder `%s': folder exists."), folder_name);
256 return camel_mh_folder_new(store, folder_name, flags, ex);
260 get_inbox (CamelStore *store, CamelException *ex)
262 return get_folder (store, "inbox", 0, ex);
265 static void delete_folder(CamelStore * store, const char *folder_name, CamelException * ex)
269 /* remove folder directory - will fail if not empty */
270 name = g_strdup_printf("%s%s", CAMEL_LOCAL_STORE(store)->toplevel_dir, folder_name);
271 if (rmdir(name) == -1) {
272 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
273 _("Could not delete folder `%s': %s"),
274 folder_name, g_strerror (errno));
280 /* remove from .folders if we are supposed to */
281 if (((CamelMhStore *)store)->flags & CAMEL_MH_DOTFOLDERS)
282 folders_update(((CamelLocalStore *)store)->toplevel_dir, UPDATE_REMOVE, folder_name, NULL);
284 /* and remove metadata */
285 ((CamelStoreClass *)parent_class)->delete_folder(store, folder_name, ex);
289 rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex)
293 camel_exception_init(&e);
294 ((CamelStoreClass *)parent_class)->rename_folder(store, old, new, &e);
295 if (camel_exception_is_set(&e)) {
296 camel_exception_xfer(ex, &e);
299 camel_exception_clear(&e);
301 if (((CamelMhStore *)store)->flags & CAMEL_MH_DOTFOLDERS) {
302 /* yeah this is messy, but so is mh! */
303 folders_update(((CamelLocalStore *)store)->toplevel_dir, UPDATE_RENAME, old, new);
308 fill_fi(CamelStore *store, CamelFolderInfo *fi, guint32 flags)
312 folder = camel_object_bag_get(store->folders, fi->full_name);
315 && (flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0)
316 folder = camel_store_get_folder(store, fi->full_name, 0, NULL);
319 if ((flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0)
320 camel_folder_refresh_info(folder, NULL);
321 fi->unread = camel_folder_get_unread_message_count(folder);
322 fi->total = camel_folder_get_message_count(folder);
323 camel_object_unref(folder);
325 char *path, *folderpath;
326 CamelFolderSummary *s;
329 /* This should be fast enough not to have to test for INFO_FAST */
331 /* We could: if we have no folder, and FAST isn't specified, perform a full
332 scan of all messages for their status flags. But its probably not worth
333 it as we need to read the top of every file, i.e. very very slow */
335 root = camel_local_store_get_toplevel_dir((CamelLocalStore *)store);
336 path = g_strdup_printf("%s/%s.ev-summary", root, fi->full_name);
337 folderpath = g_strdup_printf("%s/%s", root, fi->full_name);
338 s = (CamelFolderSummary *)camel_mh_summary_new(NULL, path, folderpath, NULL);
339 if (camel_folder_summary_header_load(s) != -1) {
340 fi->unread = s->unread_count;
341 fi->total = s->saved_count;
343 camel_object_unref(s);
349 static CamelFolderInfo *
350 folder_info_new (CamelStore *store, CamelURL *url, const char *root, const char *path, guint32 flags)
352 /* FIXME: need to set fi->flags = CAMEL_FOLDER_NOSELECT (and possibly others) when appropriate */
356 base = strrchr(path, '/');
358 camel_url_set_fragment (url, path);
360 /* Build the folder info structure. */
361 fi = g_malloc0(sizeof(*fi));
362 fi->uri = camel_url_to_string (url, 0);
363 fi->full_name = g_strdup(path);
364 fi->name = g_strdup(base?base+1:path);
365 fill_fi(store, fi, flags);
367 d(printf("New folderinfo:\n '%s'\n '%s'\n '%s'\n", fi->full_name, fi->uri, fi->path));
372 /* used to find out where we've visited already */
378 /* Scan path, under root, for directories to add folders for. Both
379 * root and path should have a trailing "/" if they aren't empty. */
381 recursive_scan (CamelStore *store, CamelURL *url, CamelFolderInfo **fip, CamelFolderInfo *parent,
382 GHashTable *visited, const char *root, const char *path, guint32 flags)
384 char *fullpath, *tmp;
389 struct _inode in, *inew;
391 /* Open the specified directory. */
393 fullpath = alloca (strlen (root) + strlen (path) + 2);
394 sprintf (fullpath, "%s/%s", root, path);
396 fullpath = (char *)root;
398 if (stat(fullpath, &st) == -1 || !S_ISDIR(st.st_mode))
401 in.dnode = st.st_dev;
402 in.inode = st.st_ino;
404 /* see if we've visited already */
405 if (g_hash_table_lookup(visited, &in) != NULL)
408 inew = g_malloc(sizeof(*inew));
410 g_hash_table_insert(visited, inew, inew);
413 fi = folder_info_new(store, url, root, path, flags);
418 if (((flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) || parent == NULL)) {
419 /* now check content for possible other directories */
420 dp = opendir(fullpath);
424 /* Look for subdirectories to add and scan. */
425 while ((d = readdir(dp)) != NULL) {
426 /* Skip current and parent directory. */
427 if (strcmp(d->d_name, ".") == 0
428 || strcmp(d->d_name, "..") == 0)
431 /* skip fully-numerical entries (i.e. mh messages) */
432 strtoul(d->d_name, &tmp, 10);
436 /* otherwise, treat at potential node, and recurse, a bit more expensive than needed, but tough! */
438 tmp = g_strdup_printf("%s/%s", path, d->d_name);
439 recursive_scan(store, url, &fi->child, fi, visited, root, tmp, flags);
442 recursive_scan(store, url, &fi->child, fi, visited, root, d->d_name, flags);
450 /* scan a .folders file */
452 folders_scan(CamelStore *store, CamelURL *url, const char *root, const char *top, CamelFolderInfo **fip, guint32 flags)
455 char line[512], *path, *tmp;
456 CamelStream *stream, *in;
462 tmp = g_alloca (strlen (root) + 16);
463 sprintf (tmp, "%s/.folders", root);
464 stream = camel_stream_fs_new_with_name(tmp, 0, O_RDONLY);
468 in = camel_stream_buffer_new(stream, CAMEL_STREAM_BUFFER_READ);
469 camel_object_unref(stream);
473 visited = g_hash_table_new(g_str_hash, g_str_equal);
474 folders = g_ptr_array_new();
476 while ( (len = camel_stream_buffer_gets((CamelStreamBuffer *)in, line, sizeof(line))) > 0) {
477 /* ignore blank lines */
480 /* check for invalidly long lines, we abort evreything and fallback */
481 if (line[len-1] != '\n') {
484 for (i=0;i<folders->len;i++)
485 camel_folder_info_free(folders->pdata[i]);
486 g_ptr_array_set_size(folders, 0);
494 int toplen = strlen(top);
496 /* check is dir or subdir */
497 if (strncmp(top, line, toplen) != 0
498 || (line[toplen] != 0 && line[toplen] != '/'))
501 /* check is not sub-subdir if not recursive */
502 if ((flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) == 0
503 && (tmp = strrchr(line, '/'))
504 && tmp > line+toplen)
508 if (g_hash_table_lookup(visited, line) != NULL)
511 tmp = g_strdup(line);
512 g_hash_table_insert(visited, tmp, tmp);
514 path = g_strdup_printf("%s/%s", root, line);
515 if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
516 fi = folder_info_new(store, url, root, line, flags);
517 g_ptr_array_add(folders, fi);
523 *fip = camel_folder_info_build(folders, top, '/', TRUE);
524 g_ptr_array_free(folders, TRUE);
526 g_hash_table_foreach(visited, (GHFunc)g_free, NULL);
527 g_hash_table_destroy(visited);
529 camel_object_unref(in);
532 /* FIXME: move to camel-local, this is shared with maildir code */
533 static guint inode_hash(const void *d)
535 const struct _inode *v = d;
537 return v->inode ^ v->dnode;
540 static gboolean inode_equal(const void *a, const void *b)
542 const struct _inode *v1 = a, *v2 = b;
544 return v1->inode == v2->inode && v1->dnode == v2->dnode;
547 static void inode_free(void *k, void *v, void *d)
552 static CamelFolderInfo *
553 get_folder_info (CamelStore *store, const char *top, guint32 flags, CamelException *ex)
555 CamelFolderInfo *fi = NULL;
559 root = ((CamelService *)store)->url->path;
561 url = camel_url_copy (((CamelService *) store)->url);
563 /* use .folders if we are supposed to */
564 if (((CamelMhStore *)store)->flags & CAMEL_MH_DOTFOLDERS) {
565 folders_scan(store, url, root, top, &fi, flags);
567 GHashTable *visited = g_hash_table_new(inode_hash, inode_equal);
572 recursive_scan(store, url, &fi, NULL, visited, root, top, flags);
574 /* if we actually scanned from root, we have a "" root node we dont want */
575 if (fi != NULL && top[0] == 0) {
576 CamelFolderInfo *rfi;
581 camel_folder_info_free(rfi);
584 g_hash_table_foreach(visited, inode_free, NULL);
585 g_hash_table_destroy(visited);
588 camel_url_free (url);