From 5e144756c987f97f4289d554d9bcdf222cd9b326 Mon Sep 17 00:00:00 2001 From: Not Zed Date: Tue, 3 Sep 2002 14:55:03 +0000 Subject: Lots of changes, too numerous to list. Changed to use 2002-09-04 Not Zed * providers/imap/camel-imap-store.c, providers/imap/camel-imap-folder.c: Lots of changes, too numerous to list. Changed to use camel-imap-store-summary to cache list requests. Changed to use a canonicalised url path with / instead of per-store directory separator. Indirects folder name so invalid folder names can still be accessed. Summary now stored in a new expandable format in .ev-store-summary. 2002-08-28 Not Zed * providers/imap/camel-imap-store.c (construct): Load store summary if it exists. (can_work_offline): Just see if we have any folders to say whether we can work offline or not. Should probably always just return true. 2002-08-27 Not Zed * providers/imap/camel-imap-store-summary.[ch]: New files to handle offline definition of folders, etc. * camel-url.h: Define CamelURL to be struct _CamelURL rather than anonymous struct. * camel-store-summary.[ch]: a few api tweaks. Also, the summary header is versioned separately at each level, so that version upgrades can be handled separately. Renamed FolderInfo -> StoreInfo to avoid namespace with current FolderInfo code. This should be reversed when the FolderInfo code is rationalised to this new base. 2002-08-23 Not Zed * providers/imap/camel-imap-command.c (camel_imap_command): domt encode folder name. * providers/imap/camel-imap-folder.c (do_copy): dont encode folder name. (do_append): dont encode folder name. * providers/imap/camel-imap-store.c (get_folder_status): don encode folder name in imap request. (get_folder_online): here too for creating folder. (rename_folder): Assume the incoming 'new name' is a utf8 path, whereas the 'old name' is as from get folder info (raw). (create_folder): Dont encode parent_name, assume its the raw thing. 2002-08-22 Not Zed * providers/imap/camel-imap-store.c (get_folder_online): Select based on unconverted name. (imap_build_folder_info): New function to create a folderinfo properly based on raw name. (subscribe_folder): Use above helper. (imap_folder_effectively_unsubscribed): Same here. (imap_forget_folder): Same here. (get_one_folder_offline): " 2002-08-21 Not Zed * providers/imap/camel-imap-store.c (parse_list_response_as_folder_info): Setup path properly, as decoded path with / separator. Setup full_name as non-decoded raw name. Keep url as decoded path but with server separator ... (ick). (create_folder): Dont call build_path anymore, get_folders() does it for us. (subscribe_folder): Build the path ourself. (imap_folder_effectively_unsubscribed): Same here. (get_subscribed_folders): list using %S not %F, we're using the raw server provided name directly. (subscribe_folder): As above, for SUBSCRIBE. (unsubscribe_folder): Same here. (delete_folder): Same. (rename_folder_info): Same here for source name. (rename_folder): And here? (get_folders_online): Amd here. * providers/imap/camel-imap-utils.c: (imap_parse_list_response): Dont decode the mailbox. * camel-utf8.[ch]: some new utf8 & utf7 utilities. * providers/imap/camel-imap-utils.c (imap_mailbox_encode): (imap_mailbox_decode): use camel_utf7/8* functions instead. : Add config.h and alloca.h headers. svn path=/trunk/; revision=17943 --- camel/providers/imap/Makefile.am | 2 + camel/providers/imap/camel-imap-command.c | 8 +- camel/providers/imap/camel-imap-store-summary.c | 519 +++++++++++++++ camel/providers/imap/camel-imap-store-summary.h | 100 +++ camel/providers/imap/camel-imap-store.c | 815 +++++++++++++----------- camel/providers/imap/camel-imap-store.h | 4 +- camel/providers/imap/camel-imap-utils.c | 15 +- 7 files changed, 1078 insertions(+), 385 deletions(-) create mode 100644 camel/providers/imap/camel-imap-store-summary.c create mode 100644 camel/providers/imap/camel-imap-store-summary.h (limited to 'camel/providers') diff --git a/camel/providers/imap/Makefile.am b/camel/providers/imap/Makefile.am index dbee888f39..469964a9a6 100644 --- a/camel/providers/imap/Makefile.am +++ b/camel/providers/imap/Makefile.am @@ -24,6 +24,7 @@ libcamelimap_la_SOURCES = \ camel-imap-provider.c \ camel-imap-search.c \ camel-imap-store.c \ + camel-imap-store-summary.c \ camel-imap-summary.c \ camel-imap-utils.c \ camel-imap-wrapper.c @@ -34,6 +35,7 @@ libcamelimapinclude_HEADERS = \ camel-imap-message-cache.h \ camel-imap-search.h \ camel-imap-store.h \ + camel-imap-store-summary.h \ camel-imap-summary.h \ camel-imap-types.h \ camel-imap-utils.h \ diff --git a/camel/providers/imap/camel-imap-command.c b/camel/providers/imap/camel-imap-command.c index ef7dd1ba35..e3581c80af 100644 --- a/camel/providers/imap/camel-imap-command.c +++ b/camel/providers/imap/camel-imap-command.c @@ -37,9 +37,11 @@ #include "camel-imap-utils.h" #include "camel-imap-folder.h" #include "camel-imap-store.h" +#include "camel-imap-store-summary.h" #include "camel-imap-private.h" #include #include +#include #define d(x) x @@ -764,8 +766,10 @@ imap_command_strdup_vprintf (CamelImapStore *store, const char *fmt, case 'S': case 'F': string = args->pdata[i++]; - if (*p == 'F') - string = imap_mailbox_encode (string, strlen (string)); + if (*p == 'F') { + char *s = camel_imap_store_summary_full_from_path(store->summary, string); + string = s?s:camel_utf8_utf7(string); + } if (imap_is_atom (string)) { outptr += sprintf (outptr, "%s", string); diff --git a/camel/providers/imap/camel-imap-store-summary.c b/camel/providers/imap/camel-imap-store-summary.c new file mode 100644 index 0000000000..4ba5bb9b68 --- /dev/null +++ b/camel/providers/imap/camel-imap-store-summary.c @@ -0,0 +1,519 @@ +/* + * Copyright (C) 2002 Ximian Inc. + * + * Authors: Michael Zucchi + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include "camel-imap-store-summary.h" + +#include "camel-file-utils.h" + +#include "hash-table-utils.h" +#include "e-util/md5-utils.h" +#include "e-util/e-memory.h" + +#include "camel-private.h" +#include "camel-utf8.h" + +#define d(x) +#define io(x) /* io debug */ + +#define CAMEL_IMAP_STORE_SUMMARY_VERSION_0 (0) + +#define CAMEL_IMAP_STORE_SUMMARY_VERSION (0) + +#define _PRIVATE(o) (((CamelImapStoreSummary *)(o))->priv) + +static int summary_header_load(CamelStoreSummary *, FILE *); +static int summary_header_save(CamelStoreSummary *, FILE *); + +/*static CamelStoreInfo * store_info_new(CamelStoreSummary *, const char *);*/ +static CamelStoreInfo * store_info_load(CamelStoreSummary *, FILE *); +static int store_info_save(CamelStoreSummary *, FILE *, CamelStoreInfo *); +static void store_info_free(CamelStoreSummary *, CamelStoreInfo *); + +static const char *store_info_string(CamelStoreSummary *, const CamelStoreInfo *, int); +static void store_info_set_string(CamelStoreSummary *, CamelStoreInfo *, int, const char *); + +static void camel_imap_store_summary_class_init (CamelImapStoreSummaryClass *klass); +static void camel_imap_store_summary_init (CamelImapStoreSummary *obj); +static void camel_imap_store_summary_finalise (CamelObject *obj); + +static CamelStoreSummaryClass *camel_imap_store_summary_parent; + +static void +camel_imap_store_summary_class_init (CamelImapStoreSummaryClass *klass) +{ + CamelStoreSummaryClass *ssklass = (CamelStoreSummaryClass *)klass; + + ssklass->summary_header_load = summary_header_load; + ssklass->summary_header_save = summary_header_save; + + /*ssklass->store_info_new = store_info_new;*/ + ssklass->store_info_load = store_info_load; + ssklass->store_info_save = store_info_save; + ssklass->store_info_free = store_info_free; + + ssklass->store_info_string = store_info_string; + ssklass->store_info_set_string = store_info_set_string; +} + +static void +camel_imap_store_summary_init (CamelImapStoreSummary *s) +{ + /*struct _CamelImapStoreSummaryPrivate *p; + + p = _PRIVATE(s) = g_malloc0(sizeof(*p));*/ + + ((CamelStoreSummary *)s)->store_info_size = sizeof(CamelImapStoreInfo); + s->version = CAMEL_IMAP_STORE_SUMMARY_VERSION; +} + +static void +camel_imap_store_summary_finalise (CamelObject *obj) +{ + /*struct _CamelImapStoreSummaryPrivate *p;*/ + /*CamelImapStoreSummary *s = (CamelImapStoreSummary *)obj;*/ + + /*p = _PRIVATE(obj); + g_free(p);*/ +} + +CamelType +camel_imap_store_summary_get_type (void) +{ + static CamelType type = CAMEL_INVALID_TYPE; + + if (type == CAMEL_INVALID_TYPE) { + camel_imap_store_summary_parent = (CamelStoreSummaryClass *)camel_store_summary_get_type(); + type = camel_type_register((CamelType)camel_imap_store_summary_parent, "CamelImapStoreSummary", + sizeof (CamelImapStoreSummary), + sizeof (CamelImapStoreSummaryClass), + (CamelObjectClassInitFunc) camel_imap_store_summary_class_init, + NULL, + (CamelObjectInitFunc) camel_imap_store_summary_init, + (CamelObjectFinalizeFunc) camel_imap_store_summary_finalise); + } + + return type; +} + +/** + * camel_imap_store_summary_new: + * + * Create a new CamelImapStoreSummary object. + * + * Return value: A new CamelImapStoreSummary widget. + **/ +CamelImapStoreSummary * +camel_imap_store_summary_new (void) +{ + CamelImapStoreSummary *new = CAMEL_IMAP_STORE_SUMMARY ( camel_object_new (camel_imap_store_summary_get_type ())); + + return new; +} + +/** + * camel_imap_store_summary_full_name: + * @s: + * @path: + * + * Retrieve a summary item by full name. + * + * A referenced to the summary item is returned, which may be + * ref'd or free'd as appropriate. + * + * Return value: The summary item, or NULL if the @full_name name + * is not available. + * It must be freed using camel_store_summary_info_free(). + **/ +CamelImapStoreInfo * +camel_imap_store_summary_full_name(CamelImapStoreSummary *s, const char *full_name) +{ + int count, i; + CamelImapStoreInfo *info; + + count = camel_store_summary_count((CamelStoreSummary *)s); + for (i=0;ifull_name, full_name) == 0) + return info; + camel_store_summary_info_free((CamelStoreSummary *)s, (CamelStoreInfo *)info); + } + } + + return NULL; +} + +char * +camel_imap_store_summary_full_to_path(CamelImapStoreSummary *s, const char *full_name, char dir_sep) +{ + char *path, *p; + int c; + const char *f; + + if (dir_sep != '/') { + p = path = alloca(strlen(full_name)*3+1); + f = full_name; + while ( (c = *f++ & 0xff) ) { + if (c == dir_sep) + *p++ = '/'; + else if (c == '/' || c == '%') + p += sprintf(p, "%%%02X", c); + else + *p++ = c; + } + *p = 0; + } else + path = (char *)full_name; + + return camel_utf7_utf8(path); +} + +static guint32 hexnib(guint32 c) +{ + if (c >= '0' && c <= '9') + return c-'0'; + else if (c>='A' && c <= 'Z') + return c-'A'+10; + else + return 0; +} + +char * +camel_imap_store_summary_path_to_full(CamelImapStoreSummary *s, const char *path, char dir_sep) +{ + unsigned char *full, *f; + guint32 c, v = 0; + const char *p; + int state=0; + char *subpath, *last = NULL; + CamelStoreInfo *si; + + /* check to see if we have a subpath of path already defined */ + subpath = alloca(strlen(path)+1); + strcpy(subpath, path); + do { + si = camel_store_summary_path((CamelStoreSummary *)s, subpath); + if (si == NULL) { + last = strrchr(subpath, '/'); + if (last) + *last = 0; + } + } while (si == NULL && last); + + /* path is already present, use the raw version we have */ + if (si && strlen(subpath) == strlen(path)) { + f = g_strdup(camel_imap_store_info_full_name(s, si)); + camel_store_summary_info_free((CamelStoreSummary *)s, si); + return f; + } + + f = full = alloca(strlen(path)*2+1); + if (si) + p = path + strlen(subpath); + else + p = path; + while ( (c = camel_utf8_getc((const unsigned char **)&p)) ) { + switch(state) { + case 0: + if (c == '%') + state = 1; + else { + if (c == '/') + c = dir_sep; + camel_utf8_putc(&f, c); + } + break; + case 1: + state = 2; + v = hexnib(c)<<4; + break; + case 2: + state = 0; + v |= hexnib(c); + camel_utf8_putc(&f, v); + break; + } + } + camel_utf8_putc(&f, c); + + /* merge old path part if required */ + f = camel_utf8_utf7(full); + if (si) { + full = g_strdup_printf("%s%s", camel_imap_store_info_full_name(s, si), f); + g_free(f); + camel_store_summary_info_free((CamelStoreSummary *)s, si); + f = full; + } + + return f; +} + +CamelImapStoreInfo * +camel_imap_store_summary_add_from_full(CamelImapStoreSummary *s, const char *full_name, char dir_sep) +{ + CamelImapStoreInfo *info; + char *pathu8; + + d(printf("adding full name '%s' '%c'\n", full_name, dir_sep)); + + info = camel_imap_store_summary_full_name(s, full_name); + if (info) { + camel_store_summary_info_free((CamelStoreSummary *)s, (CamelStoreInfo *)info); + d(printf(" already there\n")); + return info; + } + + pathu8 = camel_imap_store_summary_full_to_path(s, full_name, dir_sep); + + info = (CamelImapStoreInfo *)camel_store_summary_add_from_path((CamelStoreSummary *)s, pathu8); + if (info) { + d(printf(" '%s' -> '%s'\n", pathu8, full_name)); + camel_store_info_set_string((CamelStoreSummary *)s, (CamelStoreInfo *)info, CAMEL_IMAP_STORE_INFO_FULL_NAME, full_name); + } else + d(printf(" failed\n")); + + return info; +} + +/* should this be const? */ +char * +camel_imap_store_summary_full_from_path(CamelImapStoreSummary *s, const char *path) +{ + CamelImapStoreInfo *si; + + si = (CamelImapStoreInfo *)camel_store_summary_path((CamelStoreSummary *)s, path); + + d(printf("looking up path %s -> %s\n", path, si?si->full_name:"not found")); + + if (si) + return g_strdup(si->full_name); + + return NULL; +} + +/* TODO: this api needs some more work */ +CamelImapStoreNamespace *camel_imap_store_summary_namespace_new(CamelImapStoreSummary *s, const char *full_name, char dir_sep) +{ + CamelImapStoreNamespace *ns; + + ns = g_malloc0(sizeof(*ns)); + ns->full_name = g_strdup(full_name); + ns->sep = dir_sep; + ns->path = camel_imap_store_summary_full_to_path(s, full_name, dir_sep); + + return ns; +} + +void camel_imap_store_summary_namespace_set(CamelImapStoreSummary *s, CamelImapStoreNamespace *ns) +{ + static void namespace_clear(CamelStoreSummary *s); + + namespace_clear((CamelStoreSummary *)s); + s->namespace = ns; + camel_store_summary_touch((CamelStoreSummary *)s); +} + +static void +namespace_free(CamelStoreSummary *s, CamelImapStoreNamespace *ns) +{ + g_free(ns->path); + g_free(ns->full_name); + g_free(ns); +} + +static void +namespace_clear(CamelStoreSummary *s) +{ + CamelImapStoreSummary *is = (CamelImapStoreSummary *)s; + + if (is->namespace) + namespace_free(s, is->namespace); + is->namespace = NULL; +} + +static CamelImapStoreNamespace * +namespace_load(CamelStoreSummary *s, FILE *in) +{ + CamelImapStoreNamespace *ns; + guint32 sep = '/'; + + ns = g_malloc0(sizeof(*ns)); + if (camel_file_util_decode_string(in, &ns->path) == -1 + || camel_file_util_decode_string(in, &ns->full_name) == -1 + || camel_file_util_decode_uint32(in, &sep) == -1) { + namespace_free(s, ns); + ns = NULL; + } else { + ns->sep = sep; + } + + return ns; +} + +static int +namespace_save(CamelStoreSummary *s, FILE *in, CamelImapStoreNamespace *ns) +{ + if (camel_file_util_encode_string(in, ns->path) == -1 + || camel_file_util_encode_string(in, ns->full_name) == -1 + || camel_file_util_encode_uint32(in, (guint32)ns->sep) == -1) + return -1; + + return 0; +} + +static int +summary_header_load(CamelStoreSummary *s, FILE *in) +{ + CamelImapStoreSummary *is = (CamelImapStoreSummary *)s; + gint32 version, capabilities, count; + + namespace_clear(s); + + if (camel_imap_store_summary_parent->summary_header_load((CamelStoreSummary *)s, in) == -1 + || camel_file_util_decode_fixed_int32(in, &version) == -1) + return -1; + + is->version = version; + + if (version < CAMEL_IMAP_STORE_SUMMARY_VERSION_0) { + g_warning("Store summary header version too low"); + return -1; + } + + /* note file format can be expanded to contain more namespaces, but only 1 at the moment */ + if (camel_file_util_decode_fixed_int32(in, &capabilities) == -1 + || camel_file_util_decode_fixed_int32(in, &count) == -1 + || count > 1) + return -1; + + is->capabilities = capabilities; + if (count == 1) { + if ((is->namespace = namespace_load(s, in)) == NULL) + return -1; + } + + return 0; +} + +static int +summary_header_save(CamelStoreSummary *s, FILE *out) +{ + CamelImapStoreSummary *is = (CamelImapStoreSummary *)s; + guint32 count; + + count = is->namespace?1:0; + + /* always write as latest version */ + if (camel_imap_store_summary_parent->summary_header_save((CamelStoreSummary *)s, out) == -1 + || camel_file_util_encode_fixed_int32(out, CAMEL_IMAP_STORE_SUMMARY_VERSION) == -1 + || camel_file_util_encode_fixed_int32(out, is->capabilities) == -1 + || camel_file_util_encode_fixed_int32(out, count) == -1) + return -1; + + if (is->namespace && namespace_save(s, out, is->namespace) == -1) + return -1; + + return 0; +} + +static CamelStoreInfo * +store_info_load(CamelStoreSummary *s, FILE *in) +{ + CamelImapStoreInfo *mi; + + mi = (CamelImapStoreInfo *)camel_imap_store_summary_parent->store_info_load(s, in); + if (mi) { + if (camel_file_util_decode_string(in, &mi->full_name) == -1) { + camel_store_summary_info_free(s, (CamelStoreInfo *)mi); + mi = NULL; + } + } + + return (CamelStoreInfo *)mi; +} + +static int +store_info_save(CamelStoreSummary *s, FILE *out, CamelStoreInfo *mi) +{ + CamelImapStoreInfo *isi = (CamelImapStoreInfo *)mi; + + if (camel_imap_store_summary_parent->store_info_save(s, out, mi) == -1 + || camel_file_util_encode_string(out, isi->full_name) == -1) + return -1; + + return 0; +} + +static void +store_info_free(CamelStoreSummary *s, CamelStoreInfo *mi) +{ + CamelImapStoreInfo *isi = (CamelImapStoreInfo *)mi; + + g_free(isi->full_name); + camel_imap_store_summary_parent->store_info_free(s, mi); +} + +static const char * +store_info_string(CamelStoreSummary *s, const CamelStoreInfo *mi, int type) +{ + CamelImapStoreInfo *isi = (CamelImapStoreInfo *)mi; + + /* FIXME: Locks? */ + + g_assert (mi != NULL); + + switch (type) { + case CAMEL_IMAP_STORE_INFO_FULL_NAME: + return isi->full_name; + default: + return camel_imap_store_summary_parent->store_info_string(s, mi, type); + } +} + +static void +store_info_set_string(CamelStoreSummary *s, CamelStoreInfo *mi, int type, const char *str) +{ + CamelImapStoreInfo *isi = (CamelImapStoreInfo *)mi; + + g_assert(mi != NULL); + + switch(type) { + case CAMEL_IMAP_STORE_INFO_FULL_NAME: + d(printf("Set full name %s -> %s\n", isi->full_name, str)); + CAMEL_STORE_SUMMARY_LOCK(s, summary_lock); + g_free(isi->full_name); + isi->full_name = g_strdup(str); + CAMEL_STORE_SUMMARY_UNLOCK(s, summary_lock); + break; + default: + camel_imap_store_summary_parent->store_info_set_string(s, mi, type, str); + break; + } +} diff --git a/camel/providers/imap/camel-imap-store-summary.h b/camel/providers/imap/camel-imap-store-summary.h new file mode 100644 index 0000000000..013283b5c1 --- /dev/null +++ b/camel/providers/imap/camel-imap-store-summary.h @@ -0,0 +1,100 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2002 Ximian Inc. + * + * Authors: Michael Zucchi + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifndef _CAMEL_IMAP_STORE_SUMMARY_H +#define _CAMEL_IMAP_STORE_SUMMARY_H + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +#include +#include + +#define CAMEL_IMAP_STORE_SUMMARY(obj) CAMEL_CHECK_CAST (obj, camel_imap_store_summary_get_type (), CamelImapStoreSummary) +#define CAMEL_IMAP_STORE_SUMMARY_CLASS(klass) CAMEL_CHECK_CLASS_CAST (klass, camel_imap_store_summary_get_type (), CamelImapStoreSummaryClass) +#define CAMEL_IS_IMAP_STORE_SUMMARY(obj) CAMEL_CHECK_TYPE (obj, camel_imap_store_summary_get_type ()) + +typedef struct _CamelImapStoreSummary CamelImapStoreSummary; +typedef struct _CamelImapStoreSummaryClass CamelImapStoreSummaryClass; + +typedef struct _CamelImapStoreInfo CamelImapStoreInfo; + +enum { + CAMEL_IMAP_STORE_INFO_FULL_NAME = CAMEL_STORE_INFO_LAST, + CAMEL_IMAP_STORE_INFO_LAST, +}; + +struct _CamelImapStoreInfo { + CamelStoreInfo info; + char *full_name; +}; + +typedef struct _CamelImapStoreNamespace CamelImapStoreNamespace; + +struct _CamelImapStoreNamespace { + char *path; /* display path */ + char *full_name; /* real name */ + char sep; /* directory separator */ +}; + +struct _CamelImapStoreSummary { + CamelStoreSummary summary; + + struct _CamelImapStoreSummaryPrivate *priv; + + /* header info */ + guint32 version; /* version of base part of file */ + guint32 capabilities; + CamelImapStoreNamespace *namespace; /* eventually to be a list */ +}; + +struct _CamelImapStoreSummaryClass { + CamelStoreSummaryClass summary_class; +}; + +CamelType camel_imap_store_summary_get_type (void); +CamelImapStoreSummary *camel_imap_store_summary_new (void); + +/* TODO: this api needs some more work */ +CamelImapStoreNamespace *camel_imap_store_summary_namespace_new(CamelImapStoreSummary *s, const char *full_name, char dir_sep); +void camel_imap_store_summary_namespace_set(CamelImapStoreSummary *s, CamelImapStoreNamespace *ns); + +/* converts to/from utf8 canonical nasmes */ +char *camel_imap_store_summary_full_to_path(CamelImapStoreSummary *s, const char *full_name, char dir_sep); +char *camel_imap_store_summary_path_to_full(CamelImapStoreSummary *s, const char *path, char dir_sep); + +CamelImapStoreInfo *camel_imap_store_summary_full_name(CamelImapStoreSummary *s, const char *full_name); +CamelImapStoreInfo *camel_imap_store_summary_add_from_full(CamelImapStoreSummary *s, const char *full_name, char dir_sep); + +/* a convenience lookup function. always use this if path known */ +char *camel_imap_store_summary_full_from_path(CamelImapStoreSummary *s, const char *path); + +/* helper macro's */ +#define camel_imap_store_info_full_name(s, i) (camel_store_info_string((CamelStoreSummary *)s, (const CamelStoreInfo *)i, CAMEL_IMAP_STORE_INFO_FULL_NAME)) + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* ! _CAMEL_IMAP_STORE_SUMMARY_H */ diff --git a/camel/providers/imap/camel-imap-store.c b/camel/providers/imap/camel-imap-store.c index c56976466d..7eb3c3accb 100644 --- a/camel/providers/imap/camel-imap-store.c +++ b/camel/providers/imap/camel-imap-store.c @@ -38,6 +38,7 @@ #include "e-util/e-path.h" #include "camel-imap-store.h" +#include "camel-imap-store-summary.h" #include "camel-imap-folder.h" #include "camel-imap-utils.h" #include "camel-imap-command.h" @@ -55,6 +56,7 @@ #include "camel-tcp-stream-ssl.h" #include "camel-url.h" #include "camel-sasl.h" +#include "camel-utf8.h" #include "string-utils.h" #include "camel-imap-private.h" @@ -114,14 +116,10 @@ static void get_folders_online (CamelImapStore *imap_store, const char *pattern, GPtrArray *folders, gboolean lsub, CamelException *ex); -static void imap_folder_effectively_unsubscribed(CamelImapStore *imap_store, - const char *folder_name, CamelException *ex); - -static gboolean imap_check_folder_still_extant (CamelImapStore *imap_store, const char *full_name, - CamelException *ex); - -static void imap_forget_folder(CamelImapStore *imap_store, const char *folder_name, - CamelException *ex); +static void imap_folder_effectively_unsubscribed(CamelImapStore *imap_store, const char *folder_name, CamelException *ex); +static gboolean imap_check_folder_still_extant (CamelImapStore *imap_store, const char *full_name, CamelException *ex); +static void imap_forget_folder(CamelImapStore *imap_store, const char *folder_name, CamelException *ex); +static void imap_set_server_level (CamelImapStore *store); static void camel_imap_store_class_init (CamelImapStoreClass *camel_imap_store_class) @@ -180,6 +178,11 @@ static void camel_imap_store_finalize (CamelObject *object) { CamelImapStore *imap_store = CAMEL_IMAP_STORE (object); + + if (imap_store->summary) { + camel_store_summary_save((CamelStoreSummary *)imap_store->summary); + camel_object_unref(imap_store->summary); + } if (imap_store->istream) camel_object_unref (CAMEL_OBJECT (imap_store->istream)); @@ -254,7 +257,6 @@ camel_imap_store_init (gpointer object, gpointer klass) imap_store->dir_sep = '\0'; imap_store->current_folder = NULL; imap_store->connected = FALSE; - imap_store->subscribed_folders = NULL; imap_store->tag_prefix = imap_tag_prefix++; if (imap_tag_prefix > 'Z') @@ -294,6 +296,8 @@ construct (CamelService *service, CamelSession *session, { CamelImapStore *imap_store = CAMEL_IMAP_STORE (service); CamelStore *store = CAMEL_STORE (service); + char *tmp; + CamelURL *summary_url; CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex); if (camel_exception_is_set (ex)) @@ -313,6 +317,7 @@ construct (CamelService *service, CamelSession *session, store->flags |= CAMEL_STORE_SUBSCRIPTIONS; if (camel_url_get_param (url, "namespace")) { imap_store->parameters |= IMAP_PARAM_OVERRIDE_NAMESPACE; + g_free(imap_store->namespace); imap_store->namespace = g_strdup (camel_url_get_param (url, "namespace")); } if (camel_url_get_param (url, "check_all")) @@ -321,6 +326,32 @@ construct (CamelService *service, CamelSession *session, imap_store->parameters |= IMAP_PARAM_FILTER_INBOX; store->flags |= CAMEL_STORE_FILTER_INBOX; } + + /* setup/load the store summary */ + tmp = alloca(strlen(imap_store->storage_path)+32); + sprintf(tmp, "%s/.ev-store-summary", imap_store->storage_path); + imap_store->summary = camel_imap_store_summary_new(); + camel_store_summary_set_filename((CamelStoreSummary *)imap_store->summary, tmp); + summary_url = camel_url_new(imap_store->base_url, NULL); + camel_store_summary_set_uri_base((CamelStoreSummary *)imap_store->summary, summary_url); + camel_url_free(summary_url); + if (camel_store_summary_load((CamelStoreSummary *)imap_store->summary) == 0) { + CamelImapStoreSummary *is = imap_store->summary; + + if (is->namespace) { + /* if namespace has changed, clear folder list */ + if (imap_store->namespace && strcmp(imap_store->namespace, is->namespace->full_name) != 0) { + camel_store_summary_clear((CamelStoreSummary *)is); + } else { + imap_store->namespace = g_strdup(is->namespace->full_name); + imap_store->dir_sep = is->namespace->sep; + store->dir_sep = is->namespace->sep; + } + } + + imap_store->capabilities = is->capabilities; + imap_set_server_level(imap_store); + } } static int @@ -502,6 +533,12 @@ imap_get_capability (CamelService *service, CamelException *ex) g_free (result); imap_set_server_level (store); + + if (store->summary->capabilities != store->capabilities) { + store->summary->capabilities = store->capabilities; + camel_store_summary_touch((CamelStoreSummary *)store->summary); + camel_store_summary_save((CamelStoreSummary *)store->summary); + } return TRUE; } @@ -761,40 +798,62 @@ query_auth_types (CamelService *service, CamelException *ex) return g_list_prepend (sasl_types, &camel_imap_password_authtype); } +/* folder_name is path name */ +static CamelFolderInfo * +imap_build_folder_info(CamelImapStore *imap_store, const char *folder_name) +{ + CamelURL *url; + const char *name; + CamelFolderInfo *fi; + + fi = g_malloc0(sizeof(*fi)); + + fi->full_name = g_strdup(folder_name); + fi->unread_message_count = 0; + + url = camel_url_new (imap_store->base_url, NULL); + g_free (url->path); + url->path = g_strdup_printf ("/%s", folder_name); + fi->url = camel_url_to_string (url, CAMEL_URL_HIDE_ALL); + camel_url_free(url); + fi->path = g_strdup_printf("/%s", folder_name); + name = strrchr (fi->path, '/'); + if (name) + name++; + else + name = fi->path; + + fi->name = g_strdup (name); + + return fi; +} + static void imap_folder_effectively_unsubscribed(CamelImapStore *imap_store, const char *folder_name, CamelException *ex) { - gpointer key, value; CamelFolderInfo *fi; - const char *name; - - if (g_hash_table_lookup_extended (imap_store->subscribed_folders, - folder_name, &key, &value)) { - g_hash_table_remove (imap_store->subscribed_folders, key); - g_free (key); + CamelStoreInfo *si; + + si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, folder_name); + if (si) { + if (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) { + si->flags &= ~CAMEL_STORE_INFO_FOLDER_SUBSCRIBED; + camel_store_summary_touch((CamelStoreSummary *)imap_store->summary); + camel_store_summary_save((CamelStoreSummary *)imap_store->summary); + } + camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si); } - + if (imap_store->renaming) { /* we don't need to emit a "folder_unsubscribed" signal if we are in the process of renaming folders, so we are done here... */ return; + } - - name = strrchr (folder_name, imap_store->dir_sep); - if (name) - name++; - else - name = folder_name; - - fi = g_new0 (CamelFolderInfo, 1); - fi->full_name = g_strdup (folder_name); - fi->name = g_strdup (name); - fi->url = g_strdup_printf ("%s/%s", imap_store->base_url, folder_name); - fi->unread_message_count = -1; - camel_folder_info_build_path (fi, imap_store->dir_sep); - + + fi = imap_build_folder_info(imap_store, folder_name); camel_object_trigger_event (CAMEL_OBJECT (imap_store), "folder_unsubscribed", fi); camel_folder_info_free (fi); } @@ -809,7 +868,7 @@ imap_forget_folder (CamelImapStore *imap_store, const char *folder_name, CamelEx char *folder_dir, *storage_path; CamelFolderInfo *fi; const char *name; - + name = strrchr (folder_name, imap_store->dir_sep); if (name) name++; @@ -850,13 +909,11 @@ imap_forget_folder (CamelImapStore *imap_store, const char *folder_name, CamelEx g_free (folder_dir); event: - - fi = g_new0 (CamelFolderInfo, 1); - fi->full_name = g_strdup (folder_name); - fi->name = g_strdup (name); - fi->url = g_strdup_printf ("%s/%s", imap_store->base_url, folder_name); - fi->unread_message_count = -1; - camel_folder_info_build_path (fi, imap_store->dir_sep); + + camel_store_summary_remove_path((CamelStoreSummary *)imap_store->summary, folder_name); + camel_store_summary_save((CamelStoreSummary *)imap_store->summary); + + fi = imap_build_folder_info(imap_store, folder_name); camel_object_trigger_event (CAMEL_OBJECT (imap_store), "folder_deleted", fi); camel_folder_info_free (fi); } @@ -1108,19 +1165,12 @@ imap_auth_loop (CamelService *service, CamelException *ex) return TRUE; } -#define IMAP_STOREINFO_VERSION 2 - static gboolean can_work_offline (CamelDiscoStore *disco_store) { CamelImapStore *store = CAMEL_IMAP_STORE (disco_store); - char *path; - gboolean can; - path = g_strdup_printf ("%s/storeinfo", store->storage_path); - can = access (path, F_OK) == 0; - g_free (path); - return can; + return camel_store_summary_count((CamelStoreSummary *)store->summary) != 0; } static gboolean @@ -1131,10 +1181,10 @@ imap_connect_online (CamelService *service, CamelException *ex) CamelImapResponse *response; struct _namespaces *namespaces; char *result, *name, *path; - FILE *storeinfo; - int i, flags; + int i; size_t len; - + CamelImapStoreNamespace *ns; + CAMEL_SERVICE_LOCK (store, connect_lock); if (!connect_to_server_wrapper (service, ex) || !imap_auth_loop (service, ex)) { @@ -1143,16 +1193,6 @@ imap_connect_online (CamelService *service, CamelException *ex) return FALSE; } - path = g_strdup_printf ("%s/storeinfo", store->storage_path); - storeinfo = fopen (path, "w"); - if (!storeinfo) - g_warning ("Could not open storeinfo %s", path); - g_free (path); - - /* Write header and capabilities */ - camel_file_util_encode_uint32 (storeinfo, IMAP_STOREINFO_VERSION); - camel_file_util_encode_uint32 (storeinfo, store->capabilities); - /* Get namespace and hierarchy separator */ if ((store->capabilities & IMAP_CAPABILITY_NAMESPACE) && !(store->parameters & IMAP_PARAM_OVERRIDE_NAMESPACE)) { @@ -1189,7 +1229,7 @@ imap_connect_online (CamelService *service, CamelException *ex) if (!store->namespace) store->namespace = g_strdup (""); - + if (!store->dir_sep) { if (store->server_level >= IMAP_LEVEL_IMAP4REV1) { /* This idiom means "tell me the hierarchy separator @@ -1230,32 +1270,24 @@ imap_connect_online (CamelService *service, CamelException *ex) g_free (store->namespace); store->namespace = tmp; } - - /* Write namespace/separator out */ - camel_file_util_encode_string (storeinfo, store->namespace); - camel_file_util_encode_uint32 (storeinfo, store->dir_sep); + + ns = camel_imap_store_summary_namespace_new(store->summary, store->namespace, store->dir_sep); + camel_imap_store_summary_namespace_set(store->summary, ns); if (CAMEL_STORE (store)->flags & CAMEL_STORE_SUBSCRIPTIONS) { - /* Get subscribed folders */ - response = camel_imap_command (store, NULL, ex, "LSUB \"\" \"*\""); - if (!response) - goto done; - store->subscribed_folders = g_hash_table_new (g_str_hash, g_str_equal); - for (i = 0; i < response->untagged->len; i++) { - result = response->untagged->pdata[i]; - if (!imap_parse_list_response (store, result, &flags, NULL, &name)) - continue; - if (flags & (CAMEL_IMAP_FOLDER_MARKED | CAMEL_IMAP_FOLDER_UNMARKED)) + GPtrArray *folders; + + /* this pre-fills the summary, and checks that lsub is useful */ + folders = g_ptr_array_new(); + get_folders_online(store, "*", folders, TRUE, ex); + for (i=0;ilen;i++) { + CamelFolderInfo *fi = folders->pdata[i]; + + if (fi->flags & (CAMEL_IMAP_FOLDER_MARKED | CAMEL_IMAP_FOLDER_UNMARKED)) store->capabilities |= IMAP_CAPABILITY_useful_lsub; - if (flags & CAMEL_FOLDER_NOSELECT) { - g_free (name); - continue; - } - g_hash_table_insert (store->subscribed_folders, name, - GINT_TO_POINTER (1)); - camel_file_util_encode_string (storeinfo, result); + camel_folder_info_free(fi); } - camel_imap_response_free (store, response); + g_ptr_array_free(folders, TRUE); } path = g_strdup_printf ("%s/journal", store->storage_path); @@ -1263,7 +1295,9 @@ imap_connect_online (CamelService *service, CamelException *ex) g_free (path); done: - fclose (storeinfo); + /* save any changes we had */ + camel_store_summary_save((CamelStoreSummary *)store->summary); + CAMEL_SERVICE_UNLOCK (store, connect_lock); if (camel_exception_is_set (ex)) @@ -1279,9 +1313,7 @@ imap_connect_offline (CamelService *service, CamelException *ex) { CamelImapStore *store = CAMEL_IMAP_STORE (service); CamelDiscoStore *disco_store = CAMEL_DISCO_STORE (service); - char *buf, *name, *path; - FILE *storeinfo; - guint32 tmp; + char *path; path = g_strdup_printf ("%s/journal", store->storage_path); disco_store->diary = camel_disco_diary_new (disco_store, path, ex); @@ -1289,48 +1321,6 @@ imap_connect_offline (CamelService *service, CamelException *ex) if (!disco_store->diary) return FALSE; - path = g_strdup_printf ("%s/storeinfo", store->storage_path); - storeinfo = fopen (path, "r"); - g_free (path); - tmp = 0; - if (storeinfo) - camel_file_util_decode_uint32 (storeinfo, &tmp); - if (tmp != IMAP_STOREINFO_VERSION) { - if (storeinfo) - fclose (storeinfo); - - /* We know we're offline, so this will have to set @ex - * and return FALSE. - */ - return camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex); - } - - store->subscribed_folders = g_hash_table_new (g_str_hash, g_str_equal); - - camel_file_util_decode_uint32 (storeinfo, &store->capabilities); - imap_set_server_level (store); - camel_file_util_decode_string (storeinfo, &name); - /* if the namespace has changed, the subscribed folder list in this file is bogus */ - if (store->namespace == NULL || (name != NULL && strcmp(name, store->namespace) == 0)) { - g_free(store->namespace); - store->namespace = name; - camel_file_util_decode_uint32 (storeinfo, &tmp); - store->dir_sep = tmp; - ((CamelStore *)store)->dir_sep = tmp; - while (camel_file_util_decode_string (storeinfo, &buf) == 0) { - if (!imap_parse_list_response (store, buf, NULL, NULL, &name)) { - g_free (buf); - continue; - } - g_hash_table_insert (store->subscribed_folders, name, - GINT_TO_POINTER (1)); - g_free (buf); - } - } else { - g_free(name); - } - - fclose (storeinfo); imap_store_refresh_folders (store, ex); store->connected = !camel_exception_is_set (ex); @@ -1349,13 +1339,6 @@ imap_disconnect_offline (CamelService *service, gboolean clean, CamelException * store->current_folder = NULL; } - if (store->subscribed_folders) { - g_hash_table_foreach_remove (store->subscribed_folders, - free_key, NULL); - g_hash_table_destroy (store->subscribed_folders); - store->subscribed_folders = NULL; - } - if (store->authtypes) { g_hash_table_foreach_remove (store->authtypes, free_key, NULL); @@ -1529,29 +1512,34 @@ get_folder_online (CamelStore *store, const char *folder_name, if (!g_strcasecmp (folder_name, "INBOX")) folder_name = "INBOX"; - + /* Lock around the whole lot to check/create atomically */ CAMEL_SERVICE_LOCK (imap_store, connect_lock); if (imap_store->current_folder) { camel_object_unref (CAMEL_OBJECT (imap_store->current_folder)); imap_store->current_folder = NULL; } - response = camel_imap_command (imap_store, NULL, NULL, - "SELECT %F", folder_name); + response = camel_imap_command (imap_store, NULL, NULL, "SELECT %F", folder_name); if (!response) { + char *folder_real; + if (!flags & CAMEL_STORE_FOLDER_CREATE) { CAMEL_SERVICE_UNLOCK (imap_store, connect_lock); return no_such_folder (folder_name, ex); } - - response = camel_imap_command (imap_store, NULL, ex, - "CREATE %F", folder_name); + + folder_real = camel_imap_store_summary_path_to_full(imap_store->summary, folder_name, store->dir_sep); + + response = camel_imap_command (imap_store, NULL, ex, "CREATE %S", folder_real); + if (response) { + camel_imap_store_summary_add_from_full(imap_store->summary, folder_real, store->dir_sep); + camel_imap_response_free (imap_store, response); - response = camel_imap_command (imap_store, NULL, NULL, - "SELECT %F", folder_name); + response = camel_imap_command (imap_store, NULL, NULL, "SELECT %F", folder_name); } + g_free(folder_real); if (!response) { CAMEL_SERVICE_UNLOCK (imap_store, connect_lock); return NULL; @@ -1652,60 +1640,79 @@ delete_folder (CamelStore *store, const char *folder_name, CamelException *ex) } static void -manage_subscriptions (CamelStore *store, CamelFolderInfo *fi, gboolean subscribe) +manage_subscriptions (CamelStore *store, const char *old_name, gboolean subscribe) { - while (fi) { - if (fi->child) - manage_subscriptions (store, fi->child, subscribe); - - if (subscribe) - subscribe_folder (store, fi->full_name, NULL); - else - unsubscribe_folder (store, fi->full_name, NULL); - - fi = fi->sibling; + CamelImapStore *imap_store = CAMEL_IMAP_STORE (store); + CamelStoreInfo *si; + int olen = strlen(old_name); + const char *path; + int i, count; + + count = camel_store_summary_count((CamelStoreSummary *)imap_store->summary); + for (i=0;isummary, i); + if (si) { + path = camel_store_info_path(imap_store->summary, si); + if (strncmp(path, old_name, olen) == 0) { + if (subscribe) + subscribe_folder(store, path, NULL); + else + unsubscribe_folder(store, path, NULL); + } + camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si); + } } } -#define subscribe_folders(store, fi) manage_subscriptions (store, fi, TRUE) -#define unsubscribe_folders(store, fi) manage_subscriptions (store, fi, FALSE) - static void -rename_folder_info (CamelImapStore *imap_store, CamelFolderInfo *fi, const char *old_name, const char *new_name) +rename_folder_info (CamelImapStore *imap_store, const char *old_name, const char *new_name) { - CamelImapResponse *response; - char *name; - - while (fi) { - if (fi->child) - rename_folder_info (imap_store, fi->child, old_name, new_name); - - name = g_strdup_printf ("%s%s", new_name, fi->full_name + strlen (old_name)); - - if (imap_store->dir_sep == '.') { - /* kludge around imap servers like Courier that don't rename - subfolders when you rename the parent folder - like - the spec says to do!!! */ - response = camel_imap_command (imap_store, NULL, NULL, "RENAME %F %F", fi->full_name, name); - if (response) - camel_imap_response_free (imap_store, response); + int i, count; + CamelStoreInfo *si; + int olen = strlen(old_name); + const char *path; + char *npath, *nfull; + + count = camel_store_summary_count((CamelStoreSummary *)imap_store->summary); + for (i=0;isummary, i); + if (si == NULL) + continue; + path = camel_store_info_path(imap_store->summary, si); + if (strncmp(path, old_name, olen) == 0) { + if (strlen(path) > olen) + npath = g_strdup_printf("%s/%s", new_name, path+olen+1); + else + npath = g_strdup(new_name); + nfull = camel_imap_store_summary_path_to_full(imap_store->summary, npath, imap_store->dir_sep); + + /* workaround for broken server (courier uses '.') that doesn't rename + subordinate folders as required by rfc 2060 */ + if (imap_store->dir_sep == '.') { + CamelImapResponse *response; + + response = camel_imap_command (imap_store, NULL, NULL, "RENAME %F %S", path, nfull); + if (response) + camel_imap_response_free (imap_store, response); + } + + camel_store_info_set_string((CamelStoreSummary *)imap_store->summary, si, CAMEL_STORE_INFO_PATH, npath); + camel_store_info_set_string((CamelStoreSummary *)imap_store->summary, si, CAMEL_IMAP_STORE_INFO_FULL_NAME, nfull); + + camel_store_summary_touch((CamelStoreSummary *)imap_store->summary); + g_free(nfull); + g_free(npath); } - - g_free (fi->full_name); - fi->full_name = name; - - fi = fi->sibling; + camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si); } } static void -rename_folder (CamelStore *store, const char *old_name, const char *new_name, CamelException *ex) +rename_folder (CamelStore *store, const char *old_name, const char *new_name_in, CamelException *ex) { CamelImapStore *imap_store = CAMEL_IMAP_STORE (store); CamelImapResponse *response; - char *oldpath, *newpath, *storage_path; - CamelFolderInfo *fi; - guint32 flags; + char *oldpath, *newpath, *storage_path, *new_name; if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex)) return; @@ -1730,35 +1737,31 @@ rename_folder (CamelStore *store, const char *old_name, const char *new_name, Ca imap_store->renaming = TRUE; - flags = CAMEL_STORE_FOLDER_INFO_FAST | CAMEL_STORE_FOLDER_INFO_RECURSIVE | - (store->flags & CAMEL_STORE_SUBSCRIPTIONS ? CAMEL_STORE_FOLDER_INFO_SUBSCRIBED : 0); - - fi = ((CamelStoreClass *)((CamelObject *)store)->klass)->get_folder_info (store, old_name, flags, ex); - if (fi && store->flags & CAMEL_STORE_SUBSCRIPTIONS) - unsubscribe_folders (store, fi); - - response = camel_imap_command (imap_store, NULL, ex, "RENAME %F %F", old_name, new_name); + if (store->flags & CAMEL_STORE_SUBSCRIPTIONS) + manage_subscriptions(store, old_name, FALSE); + + new_name = camel_imap_store_summary_path_to_full(imap_store->summary, new_name_in, store->dir_sep); + response = camel_imap_command (imap_store, NULL, ex, "RENAME %F %S", old_name, new_name); if (!response) { - if (fi && store->flags & CAMEL_STORE_SUBSCRIPTIONS) - subscribe_folders (store, fi); - - camel_store_free_folder_info (store, fi); + if (store->flags & CAMEL_STORE_SUBSCRIPTIONS) + manage_subscriptions(store, old_name, TRUE); + g_free(new_name); imap_store->renaming = FALSE; return; } camel_imap_response_free (imap_store, response); - - rename_folder_info (imap_store, fi, old_name, new_name); - if (fi && store->flags & CAMEL_STORE_SUBSCRIPTIONS) - subscribe_folders (store, fi); - - camel_store_free_folder_info (store, fi); - + + /* rename summary, and handle broken server */ + rename_folder_info(imap_store, old_name, new_name_in); + + if (store->flags & CAMEL_STORE_SUBSCRIPTIONS) + manage_subscriptions(store, new_name_in, TRUE); + storage_path = g_strdup_printf("%s/folders", imap_store->storage_path); oldpath = e_path_to_physical (storage_path, old_name); - newpath = e_path_to_physical (storage_path, new_name); + newpath = e_path_to_physical (storage_path, new_name_in); g_free(storage_path); /* So do we care if this didn't work? Its just a cache? */ @@ -1769,7 +1772,8 @@ rename_folder (CamelStore *store, const char *old_name, const char *new_name, Ca g_free (oldpath); g_free (newpath); - + g_free(new_name); + imap_store->renaming = FALSE; } @@ -1778,13 +1782,11 @@ create_folder (CamelStore *store, const char *parent_name, const char *folder_name, CamelException *ex) { CamelImapStore *imap_store = CAMEL_IMAP_STORE (store); - char *full_name, *resp, *thisone; + char *full_name, *resp, *thisone, *parent_real, *real_name; CamelImapResponse *response; CamelException internal_ex; CamelFolderInfo *root = NULL; gboolean need_convert; - char **pathnames = NULL; - GPtrArray *folders = NULL; int i = 0, flags; if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex)) @@ -1801,12 +1803,22 @@ create_folder (CamelStore *store, const char *parent_name, } /* check if the parent allows inferiors */ - + + /* FIXME: use storesummary directly */ + parent_real = camel_imap_store_summary_full_from_path(imap_store->summary, parent_name); + if (parent_real == NULL) { + camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID_STATE, + _("Unknown parent folder: %s"), parent_name); + return NULL; + } + need_convert = FALSE; - response = camel_imap_command (imap_store, NULL, ex, "LIST \"\" %F", - parent_name); - if (!response) /* whoa, this is bad */ + response = camel_imap_command (imap_store, NULL, ex, "LIST \"\" %S", + parent_real); + if (!response) /* whoa, this is bad */ { + g_free(parent_real); return NULL; + } /* FIXME: does not handle unexpected circumstances very well */ for (i = 0; i < response->untagged->len; i++) { @@ -1833,6 +1845,7 @@ create_folder (CamelStore *store, const char *parent_name, if (get_folder_status (imap_store, parent_name, "MESSAGES")) { camel_exception_set (ex, CAMEL_EXCEPTION_FOLDER_INVALID_STATE, _("The parent folder is not allowed to contain subfolders")); + g_free(parent_real); return NULL; } @@ -1844,91 +1857,53 @@ create_folder (CamelStore *store, const char *parent_name, } /* add the dirsep to the end of parent_name */ - name = g_strdup_printf ("%s%c", parent_name, imap_store->dir_sep); - response = camel_imap_command (imap_store, NULL, ex, "CREATE %F", + name = g_strdup_printf ("%s%c", parent_real, imap_store->dir_sep); + response = camel_imap_command (imap_store, NULL, ex, "CREATE %S", name); g_free (name); - if (!response) + if (!response) { + g_free(parent_real); return NULL; - else + } else camel_imap_response_free (imap_store, response); + + root = imap_build_folder_info(imap_store, parent_name); } /* ok now we can create the folder */ - - full_name = imap_concat (imap_store, parent_name, folder_name); - response = camel_imap_command (imap_store, NULL, ex, "CREATE %F", - full_name); - g_free (full_name); + real_name = camel_imap_store_summary_path_to_full(imap_store->summary, folder_name, store->dir_sep); + full_name = imap_concat (imap_store, parent_real, real_name); + g_free(real_name); + response = camel_imap_command (imap_store, NULL, ex, "CREATE %S", full_name); if (response) { - CamelFolderInfo *parent, *fi; - + CamelImapStoreInfo *si; + CamelFolderInfo *fi; + camel_imap_response_free (imap_store, response); - - /* We have to do this in case we are creating a - recursive directory structure */ - i = 0; - pathnames = imap_parse_folder_name (imap_store, folder_name); - full_name = imap_concat (imap_store, parent_name, pathnames[i]); - g_free (pathnames[i]); - - folders = g_ptr_array_new (); - - get_folders_online (imap_store, full_name, folders, FALSE, ex); - g_free (full_name); - if (camel_exception_is_set (&internal_ex)) { - camel_exception_xfer (&internal_ex, ex); - goto exception; - } - - root = parent = folders->pdata[i]; - - for (i = 1; parent && pathnames[i]; i++) { - full_name = imap_concat (imap_store, parent_name, pathnames[i]); - g_free (pathnames[i]); - - get_folders_online (imap_store, full_name, folders, FALSE, &internal_ex); - if (camel_exception_is_set (&internal_ex)) { - camel_exception_xfer (&internal_ex, ex); - goto exception; - } - g_free (full_name); - - if (folders->len != i + 1) - break; - - fi = folders->pdata[i]; - camel_folder_info_build_path (fi, imap_store->dir_sep); - parent->child = fi; - fi->parent = parent; - parent = fi; + + si = camel_imap_store_summary_add_from_full(imap_store->summary, full_name, store->dir_sep); + camel_store_summary_save((CamelStoreSummary *)imap_store->summary); + fi = imap_build_folder_info(imap_store, camel_store_info_path(imap_store->summary, si)); + if (root) { + root->child = fi; + fi->parent = root; + } else { + root = fi; } - - camel_folder_info_build_path(root, imap_store->dir_sep); camel_object_trigger_event (CAMEL_OBJECT (store), "folder_created", root); - - g_free (pathnames); - - g_ptr_array_free (folders, TRUE); + } else if (root) { + /* need to re-recreate the folder we just deleted */ + camel_object_trigger_event (CAMEL_OBJECT (store), "folder_created", root); + camel_folder_info_free(root); + root = NULL; } + + g_free (full_name); + g_free(parent_real); return root; - - exception: - - for (/* i is already set */; pathnames && pathnames[i]; i++) - g_free (pathnames[i]); - g_free (pathnames); - - if (folders) { - for (i = 0; i < folders->len; i++) - camel_folder_info_free (folders->pdata[i]); - g_ptr_array_free (folders, TRUE); - } - - return NULL; } static CamelFolderInfo * @@ -1936,32 +1911,45 @@ parse_list_response_as_folder_info (CamelImapStore *imap_store, const char *response) { CamelFolderInfo *fi; - int flags; - char sep, *dir, *name = NULL; + int flags, i; + char sep, *dir, *name = NULL, *path; CamelURL *url; if (!imap_parse_list_response (imap_store, response, &flags, &sep, &dir)) return NULL; + + /* FIXME: should use imap_build_folder_info, note the differences with param setting tho */ + path = camel_utf7_utf8(dir); + + /* hack: pokes in value from any list response */ + camel_imap_store_summary_add_from_full(imap_store->summary, dir, sep?sep:'/'); - if (sep) { - name = strrchr (dir, sep); - if (name && !*++name) { - g_free (dir); + if (sep && (name = strrchr(path, sep))) { + if (!*++name) { + g_free(dir); + g_free(path); return NULL; } - } + } else + name = path; fi = g_new0 (CamelFolderInfo, 1); fi->flags = flags; - fi->full_name = dir; - if (sep && name) - fi->name = g_strdup (name); - else - fi->name = g_strdup (dir); - + /*fi->full_name = dir;*/ + fi->name = g_strdup(name); + fi->path = g_strdup_printf("/%s", path); + + if (sep && sep != '/') { + for (i=0;fi->path[i];i++) + if (fi->path[i] == sep) + fi->path[i] = '/'; + } + fi->full_name = g_strdup(fi->path+1); + url = camel_url_new (imap_store->base_url, NULL); g_free (url->path); - url->path = g_strdup_printf ("/%s", dir); + url->path = g_strdup_printf ("/%s", fi->full_name); + if (flags & CAMEL_FOLDER_NOSELECT || fi->name[0] == 0) camel_url_set_param (url, "noselect", "yes"); fi->url = camel_url_to_string (url, 0); @@ -1974,43 +1962,40 @@ parse_list_response_as_folder_info (CamelImapStore *imap_store, return fi; } -static void -copy_folder_name (gpointer name, gpointer key, gpointer array) -{ - g_ptr_array_add (array, name); -} - /* this is used when lsub doesn't provide very useful information */ static GPtrArray * get_subscribed_folders (CamelImapStore *imap_store, const char *top, CamelException *ex) { GPtrArray *names, *folders; + int i, toplen = strlen (top); + CamelStoreInfo *si; CamelImapResponse *response; CamelFolderInfo *fi; char *result; - int i, toplen = strlen (top); - + folders = g_ptr_array_new (); names = g_ptr_array_new (); - g_hash_table_foreach (imap_store->subscribed_folders, - copy_folder_name, names); + for (i=0;(si = camel_store_summary_index((CamelStoreSummary *)imap_store->summary, i));i++) { + if (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) + g_ptr_array_add(names, (char *)camel_imap_store_info_full_name(imap_store->summary, si)); + camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si); + } if (names->len == 0) g_ptr_array_add (names, "INBOX"); - + for (i = 0; i < names->len; i++) { response = camel_imap_command (imap_store, NULL, ex, - "LIST \"\" %F", + "LIST \"\" %S", names->pdata[i]); if (!response) break; result = camel_imap_response_extract (imap_store, response, "LIST", NULL); if (!result) { - g_hash_table_remove (imap_store->subscribed_folders, - names->pdata[i]); - g_free (names->pdata[i]); - g_ptr_array_remove_index_fast (names, i--); + camel_store_summary_remove_path((CamelStoreSummary *)imap_store->summary, names->pdata[i]); + g_ptr_array_remove_index_fast (names, i); + i--; continue; } @@ -2031,6 +2016,31 @@ get_subscribed_folders (CamelImapStore *imap_store, const char *top, CamelExcept return folders; } +static int imap_match_pattern(char dir_sep, const char *pattern, const char *name) +{ + char p, n; + + p = *pattern++; + n = *name++; + while (n && p) { + if (n == p) { + p = *pattern++; + n = *name++; + } else if (p == '%') { + if (n != dir_sep) { + n = *name++; + } else { + p = *pattern++; + } + } else if (p == '*') { + return TRUE; + } else + return FALSE; + } + + return n == 0 && (p == '%' || p == 0); +} + static void get_folders_online (CamelImapStore *imap_store, const char *pattern, GPtrArray *folders, gboolean lsub, CamelException *ex) @@ -2038,21 +2048,56 @@ get_folders_online (CamelImapStore *imap_store, const char *pattern, CamelImapResponse *response; CamelFolderInfo *fi; char *list; - int i; - + int i, count; + GHashTable *present; + CamelStoreInfo *si; + response = camel_imap_command (imap_store, NULL, ex, - "%s \"\" %F", lsub ? "LSUB" : "LIST", + "%s \"\" %S", lsub ? "LSUB" : "LIST", pattern); if (!response) return; - + + present = g_hash_table_new(g_str_hash, g_str_equal); for (i = 0; i < response->untagged->len; i++) { list = response->untagged->pdata[i]; fi = parse_list_response_as_folder_info (imap_store, list); - if (fi) - g_ptr_array_add (folders, fi); + if (fi) { + g_ptr_array_add(folders, fi); + g_hash_table_insert(present, fi->full_name, fi); + } } camel_imap_response_free (imap_store, response); + + /* update our summary to match the server */ + count = camel_store_summary_count((CamelStoreSummary *)imap_store->summary); + for (i=0;isummary, i); + if (si == NULL) + continue; + + if (imap_match_pattern(((CamelStore *)imap_store)->dir_sep, pattern, camel_imap_store_info_full_name(imap_store->summary, si))) { + if (g_hash_table_lookup(present, camel_store_info_path(imap_store->summary, si)) != NULL) { + if (lsub && (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) == 0) { + si->flags |= CAMEL_STORE_INFO_FOLDER_SUBSCRIBED; + camel_store_summary_touch((CamelStoreSummary *)imap_store->summary); + } + } else { + if (lsub) { + if (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) { + si->flags &= ~CAMEL_STORE_INFO_FOLDER_SUBSCRIBED; + camel_store_summary_touch((CamelStoreSummary *)imap_store->summary); + } + } else { + camel_store_summary_remove((CamelStoreSummary *)imap_store->summary, si); + count--; + i--; + } + } + } + camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si); + } + g_hash_table_destroy(present); } #if 1 @@ -2072,7 +2117,7 @@ dumpfi(CamelFolderInfo *fi) } while (fi) { - printf("%-40s %-30s %*s\n", fi->path, fi->full_name, depth*2+strlen(fi->name), fi->name); + printf("%-40s %-30s %*s\n", fi->path, fi->full_name, depth*2+strlen(fi->url), fi->url); if (fi->child) dumpfi(fi->child); fi = fi->sibling; @@ -2179,13 +2224,21 @@ get_folders(CamelStore *store, const char *top, guint32 flags, CamelException *e infos = g_hash_table_new(folder_hash, folder_eq); /* get starting point & strip trailing '/' */ - if (top[0] == 0 && imap_store->namespace) - top = imap_store->namespace; - i = strlen(top)-1; - name = alloca(i+2); - strcpy(name, top); - while (i>0 && name[i] == store->dir_sep) - name[i--] = 0; + if (top[0] == 0) { + if (imap_store->namespace) { + top = imap_store->namespace; + i = strlen(top)-1; + name = g_malloc(i+2); + strcpy(name, top); + while (i>0 && name[i] == store->dir_sep) + name[i--] = 0; + } else + name = g_strdup(""); + } else { + name = camel_imap_store_summary_full_from_path(imap_store->summary, top); + if (name == NULL) + name = camel_imap_store_summary_path_to_full(imap_store->summary, top, store->dir_sep); + } d(printf("\n\nList '%s' %s\n", name, flags&CAMEL_STORE_FOLDER_INFO_RECURSIVE?"RECURSIVE":"NON-RECURSIVE")); @@ -2194,13 +2247,18 @@ get_folders(CamelStore *store, const char *top, guint32 flags, CamelException *e /* first get working list of names */ get_folders_online (imap_store, name[0]?name:"%", folders, flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED, ex); + if (camel_exception_is_set(ex)) + goto fail; for (i=0; ilen && !haveinbox; i++) { fi = folders->pdata[i]; haveinbox = (strcasecmp(fi->full_name, "INBOX")) == 0; } - if (!haveinbox && top == imap_store->namespace) + if (!haveinbox && top == imap_store->namespace) { get_folders_online(imap_store, "INBOX", folders, flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED, ex); + if (camel_exception_is_set(ex)) + goto fail; + } for (i=0; ilen; i++) p = g_slist_prepend(p, folders->pdata[i]); @@ -2230,11 +2288,13 @@ get_folders(CamelStore *store, const char *top, guint32 flags, CamelException *e /* Otherwise, if this has (or might have) children, scan it */ else if ( (fi->flags & (CAMEL_IMAP_FOLDER_NOCHILDREN|CAMEL_FOLDER_NOINFERIORS)) == 0 || (fi->flags & CAMEL_FOLDER_CHILDREN) != 0) { - char *n; - - n = imap_concat(imap_store, fi->full_name, "%"); + char *n, *real; + + real = camel_imap_store_summary_full_from_path(imap_store->summary, fi->full_name); + n = imap_concat(imap_store, real?real:fi->full_name, "%"); get_folders_online(imap_store, n, folders, flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED, ex); g_free(n); + g_free(real); if (folders->len > 0) fi->flags |= CAMEL_FOLDER_CHILDREN; @@ -2259,8 +2319,16 @@ get_folders(CamelStore *store, const char *top, guint32 flags, CamelException *e g_ptr_array_free(folders, TRUE); g_hash_table_destroy(infos); + g_free(name); return folders_out; +fail: + g_ptr_array_free(folders, TRUE); + g_ptr_array_free(folders_out, TRUE); + g_hash_table_destroy(infos); + g_free(name); + + return NULL; } static CamelFolderInfo * @@ -2280,14 +2348,18 @@ get_folder_info_online (CamelStore *store, const char *top, guint32 flags, Camel else folders = get_folders(store, top, flags, ex); + if (folders == NULL) + return NULL; + /* note the weird top stuff, it is so a namespace based list "" is properly tree-ised */ - tree = camel_folder_info_build(folders, top[0] == 0 && imap_store->namespace?"":top, imap_store->dir_sep, TRUE); + tree = camel_folder_info_build(folders, top[0] == 0 && imap_store->namespace?"":top, '/', TRUE); g_ptr_array_free(folders, TRUE); if (!(flags & CAMEL_STORE_FOLDER_INFO_FAST)) get_folder_counts(imap_store, tree, ex); dumpfi(tree); + camel_store_summary_save((CamelStoreSummary *)imap_store->summary); return tree; } @@ -2298,7 +2370,7 @@ get_one_folder_offline (const char *physical_path, const char *path, gpointer da GPtrArray *folders = data; CamelImapStore *imap_store = folders->pdata[0]; CamelFolderInfo *fi; - CamelURL *url; + CamelStoreInfo *si; if (*path != '/') return TRUE; @@ -2309,26 +2381,16 @@ get_one_folder_offline (const char *physical_path, const char *path, gpointer da * not a folder we're explicitly interested in. */ - if (g_hash_table_lookup (imap_store->subscribed_folders, path + 1) == 0) - return TRUE; - - fi = g_new0 (CamelFolderInfo, 1); - fi->full_name = g_strdup (path+1); - fi->name = strrchr (fi->full_name, imap_store->dir_sep); - if (fi->name) - fi->name = g_strdup (fi->name + 1); - else - fi->name = g_strdup (fi->full_name); - - url = camel_url_new(imap_store->base_url, NULL); - camel_url_set_path(url, path); - fi->url = camel_url_to_string(url, 0); - camel_url_free(url); - - /* FIXME: check summary */ - fi->unread_message_count = -1; + si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, path+1); + if (si) { + if ((((CamelStore *)imap_store)->flags & CAMEL_STORE_SUBSCRIPTIONS) == 0 + || si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) { + fi = imap_build_folder_info(imap_store, path+1); + g_ptr_array_add (folders, fi); + } + camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si); + } - g_ptr_array_add (folders, fi); return TRUE; } @@ -2363,8 +2425,7 @@ get_folder_info_offline (CamelStore *store, const char *top, fi = NULL; } else { g_ptr_array_remove_index_fast (folders, 0); - fi = camel_folder_info_build (folders, "", - imap_store->dir_sep, TRUE); + fi = camel_folder_info_build (folders, "", '/', TRUE); } g_free(storage_path); @@ -2376,13 +2437,19 @@ static gboolean folder_subscribed (CamelStore *store, const char *folder_name) { CamelImapStore *imap_store = CAMEL_IMAP_STORE (store); + CamelStoreInfo *si; + int truth = FALSE; - g_return_val_if_fail (imap_store->subscribed_folders != NULL, FALSE); + si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, folder_name); + if (si) { + truth = (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) != 0; + camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si); + } - return g_hash_table_lookup (imap_store->subscribed_folders, - folder_name) != NULL; + return truth; } +/* Note: folder_name must match a folder as listed with get_folder_info() -> full_name */ static void subscribe_folder (CamelStore *store, const char *folder_name, CamelException *ex) @@ -2390,9 +2457,8 @@ subscribe_folder (CamelStore *store, const char *folder_name, CamelImapStore *imap_store = CAMEL_IMAP_STORE (store); CamelImapResponse *response; CamelFolderInfo *fi; - const char *name; - CamelURL *url; - + CamelStoreInfo *si; + if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex)) return; if (!camel_imap_store_connected (imap_store, ex)) @@ -2404,8 +2470,15 @@ subscribe_folder (CamelStore *store, const char *folder_name, return; camel_imap_response_free (imap_store, response); - g_hash_table_insert (imap_store->subscribed_folders, - g_strdup (folder_name), GUINT_TO_POINTER (1)); + si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, folder_name); + if (si) { + if ((si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) == 0) { + si->flags |= CAMEL_STORE_INFO_FOLDER_SUBSCRIBED; + camel_store_summary_touch((CamelStoreSummary *)imap_store->summary); + camel_store_summary_save((CamelStoreSummary *)imap_store->summary); + } + camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si); + } if (imap_store->renaming) { /* we don't need to emit a "folder_subscribed" signal @@ -2413,26 +2486,8 @@ subscribe_folder (CamelStore *store, const char *folder_name, are done here... */ return; } - - name = strrchr (folder_name, imap_store->dir_sep); - if (name) - name++; - else - name = folder_name; - - url = camel_url_new (imap_store->base_url, NULL); - g_free (url->path); - url->path = g_strdup_printf ("/%s", folder_name); - - fi = g_new0 (CamelFolderInfo, 1); - fi->full_name = g_strdup (folder_name); - fi->name = g_strdup (name); - fi->url = camel_url_to_string (url, CAMEL_URL_HIDE_ALL); - fi->unread_message_count = -1; - camel_folder_info_build_path (fi, imap_store->dir_sep); - - camel_url_free (url); - + + fi = imap_build_folder_info(imap_store, folder_name); camel_object_trigger_event (CAMEL_OBJECT (store), "folder_subscribed", fi); camel_folder_info_free (fi); } diff --git a/camel/providers/imap/camel-imap-store.h b/camel/providers/imap/camel-imap-store.h index 499c282649..38df1880a0 100644 --- a/camel/providers/imap/camel-imap-store.h +++ b/camel/providers/imap/camel-imap-store.h @@ -102,6 +102,8 @@ struct _CamelImapStore { CamelStream *istream; CamelStream *ostream; + + struct _CamelImapStoreSummary *summary; /* Information about the command channel / connection status */ gboolean connected; @@ -113,7 +115,7 @@ struct _CamelImapStore { CamelImapServerLevel server_level; guint32 capabilities, parameters; char *namespace, dir_sep, *base_url, *storage_path; - GHashTable *authtypes, *subscribed_folders; + GHashTable *authtypes; gboolean renaming; diff --git a/camel/providers/imap/camel-imap-utils.c b/camel/providers/imap/camel-imap-utils.c index 15bf2540b3..fc507398db 100644 --- a/camel/providers/imap/camel-imap-utils.c +++ b/camel/providers/imap/camel-imap-utils.c @@ -20,11 +20,19 @@ * */ +#ifdef HAVE_CONFIG_H +#include +#endif + #include #include #include #include -#include +#include + +#ifdef HAVE_ALLOCA_H +#include +#endif #include "camel-imap-utils.h" #include "camel-imap-summary.h" @@ -363,7 +371,9 @@ imap_parse_list_response (CamelImapStore *store, const char *buf, int *flags, ch astring = imap_parse_astring (&word, &len); if (!astring) return FALSE; - + + *folder = astring; +#if 0 mailbox = imap_mailbox_decode (astring, strlen (astring)); g_free (astring); if (!mailbox) @@ -385,6 +395,7 @@ imap_parse_list_response (CamelImapStore *store, const char *buf, int *flags, ch *flags &= ~CAMEL_FOLDER_NOSELECT; *folder = mailbox; +#endif } return TRUE; -- cgit