9123102a0393d6dd4aefe3f4dc4c2f2144ede4f4
[platform/upstream/evolution-data-server.git] / camel / providers / imapp / camel-imapp-store.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-imap-store.c : class for a imap store */
3
4 /* 
5  * Authors: Michael Zucchi <notzed@ximian.com>
6  *
7  * Copyright (C) 2000-2002 Ximian, Inc. (www.ximian.com)
8  *
9  * This program is free software; you can redistribute it and/or 
10  * modify it under the terms of version 2 of the GNU General Public 
11  * License as published by the Free Software Foundation.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
21  * USA
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <sys/types.h>
29 #include <sys/socket.h>
30 #include <netinet/in.h>
31 #include <errno.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <unistd.h>
36 #include <errno.h>
37
38 #include "camel/camel-operation.h"
39
40 #include "camel/camel-stream-buffer.h"
41 #include "camel/camel-session.h"
42 #include "camel/camel-exception.h"
43 #include "camel/camel-url.h"
44 #include "camel/camel-sasl.h"
45 #include "camel/camel-data-cache.h"
46 #include "camel/camel-tcp-stream.h"
47 #include "camel/camel-tcp-stream-raw.h"
48 #ifdef HAVE_SSL
49 #include "camel/camel-tcp-stream-ssl.h"
50 #endif
51
52 #include "camel-imapp-store-summary.h"
53 #include "camel-imapp-store.h"
54 #include "camel-imapp-folder.h"
55 #include "camel-imapp-engine.h"
56 #include "camel-imapp-exception.h"
57 #include "camel-imapp-utils.h"
58 #include "camel-imapp-driver.h"
59
60 /* Specified in RFC 2060 section 2.1 */
61 #define IMAP_PORT 143
62
63 static CamelStoreClass *parent_class = NULL;
64
65 static void finalize (CamelObject *object);
66
67 static void imap_construct(CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex);
68 /* static char *imap_get_name(CamelService *service, gboolean brief);*/
69 static gboolean imap_connect (CamelService *service, CamelException *ex);
70 static gboolean imap_disconnect (CamelService *service, gboolean clean, CamelException *ex);
71 static GList *imap_query_auth_types (CamelService *service, CamelException *ex);
72
73 static void imap_init_trash (CamelStore *store);
74 static CamelFolder *imap_get_trash  (CamelStore *store, CamelException *ex);
75
76 static CamelFolder *imap_get_folder(CamelStore * store, const char *folder_name, guint32 flags, CamelException * ex);
77 static CamelFolder *imap_get_inbox (CamelStore *store, CamelException *ex);
78 static void imap_rename_folder(CamelStore *store, const char *old_name, const char *new_name, CamelException *ex);
79 static CamelFolderInfo *imap_get_folder_info (CamelStore *store, const char *top, guint32 flags, CamelException *ex);
80 static void imap_delete_folder(CamelStore *store, const char *folder_name, CamelException *ex);
81 static void imap_rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex);
82 static CamelFolderInfo *imap_create_folder(CamelStore *store, const char *parent_name, const char *folder_name, CamelException *ex);
83
84 static int store_resp_list(CamelIMAPPEngine *ie, guint32 id, void *data);
85
86 /* yet to see if this should go global or not */
87 void camel_imapp_store_folder_selected(CamelIMAPPStore *store, CamelIMAPPFolder *folder, CamelIMAPPSelectResponse *select);
88
89 static void
90 camel_imapp_store_class_init (CamelIMAPPStoreClass *camel_imapp_store_class)
91 {
92         CamelServiceClass *camel_service_class = CAMEL_SERVICE_CLASS(camel_imapp_store_class);
93         CamelStoreClass *camel_store_class = CAMEL_STORE_CLASS(camel_imapp_store_class);
94
95         parent_class = CAMEL_STORE_CLASS(camel_type_get_global_classfuncs(camel_store_get_type()));
96         
97         /* virtual method overload */
98         camel_service_class->construct = imap_construct;
99         /*camel_service_class->get_name = imap_get_name;*/
100         camel_service_class->query_auth_types = imap_query_auth_types;
101         camel_service_class->connect = imap_connect;
102         camel_service_class->disconnect = imap_disconnect;
103
104         camel_store_class->init_trash = imap_init_trash;
105         camel_store_class->get_trash = imap_get_trash;
106
107         camel_store_class->get_folder = imap_get_folder;
108         camel_store_class->get_inbox = imap_get_inbox;
109
110         camel_store_class->create_folder = imap_create_folder;
111         camel_store_class->rename_folder = imap_rename_folder;
112         camel_store_class->delete_folder = imap_delete_folder;
113         camel_store_class->get_folder_info = imap_get_folder_info;
114 }
115
116 static void
117 camel_imapp_store_init (gpointer object, gpointer klass)
118 {
119         CamelIMAPPStore *istore = object;
120 }
121
122 CamelType
123 camel_imapp_store_get_type (void)
124 {
125         static CamelType camel_imapp_store_type = CAMEL_INVALID_TYPE;
126
127         if (!camel_imapp_store_type) {
128                 camel_imapp_store_type = camel_type_register(CAMEL_STORE_TYPE,
129                                                             "CamelIMAPPStore",
130                                                             sizeof (CamelIMAPPStore),
131                                                             sizeof (CamelIMAPPStoreClass),
132                                                             (CamelObjectClassInitFunc) camel_imapp_store_class_init,
133                                                             NULL,
134                                                             (CamelObjectInitFunc) camel_imapp_store_init,
135                                                             finalize);
136         }
137
138         return camel_imapp_store_type;
139 }
140
141 static void
142 finalize (CamelObject *object)
143 {
144         CamelIMAPPStore *imap_store = CAMEL_IMAPP_STORE (object);
145
146         /* force disconnect so we dont have it run later, after we've cleaned up some stuff */
147         /* SIGH */
148
149         camel_service_disconnect((CamelService *)imap_store, TRUE, NULL);
150
151         if (imap_store->driver)
152                 camel_object_unref(imap_store->driver);
153         if (imap_store->cache)
154                 camel_object_unref(imap_store->cache);
155 }
156
157 static void imap_construct(CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex)
158 {
159         char *root, *summary;
160         CamelIMAPPStore *store = (CamelIMAPPStore *)service;
161
162         CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
163         if (camel_exception_is_set(ex))
164                 return;
165         
166         CAMEL_TRY {
167                 store->summary = camel_imapp_store_summary_new();
168                 root = camel_session_get_storage_path(service->session, service, ex);
169                 if (root) {
170                         summary = g_build_filename(root, ".ev-store-summary", NULL);
171                         camel_store_summary_set_filename((CamelStoreSummary *)store->summary, summary);
172                         /* FIXME: need to remove params, passwords, etc */
173                         camel_store_summary_set_uri_base((CamelStoreSummary *)store->summary, service->url);
174                         camel_store_summary_load((CamelStoreSummary *)store->summary);
175                 }
176         } CAMEL_CATCH(e) {
177                 camel_exception_xfer(ex, e);
178         } CAMEL_DONE;
179 }
180
181 enum {
182         USE_SSL_NEVER,
183         USE_SSL_ALWAYS,
184         USE_SSL_WHEN_POSSIBLE
185 };
186
187 #define SSL_PORT_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
188 #define STARTTLS_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
189
190 static void
191 connect_to_server (CamelService *service, int ssl_mode, int try_starttls)
192 /* throws IO exception */
193 {
194         CamelIMAPPStore *store = CAMEL_IMAPP_STORE (service);
195         CamelStream * volatile tcp_stream = NULL;
196         CamelIMAPPStream * volatile imap_stream = NULL;
197         struct hostent *h = NULL;
198         int ret, port;
199         CamelException *ex;
200
201         ex = camel_exception_new();
202         CAMEL_TRY {
203                 /* parent class connect initialization */
204                 CAMEL_SERVICE_CLASS (parent_class)->connect (service, ex);
205                 if (ex->id)
206                         camel_exception_throw_ex(ex);
207
208                 h = camel_service_gethost(service, ex);
209                 if (ex->id)
210                         camel_exception_throw_ex(ex);
211                 
212                 port = service->url->port ? service->url->port : IMAP_PORT;
213                 
214 #ifdef HAVE_SSL 
215                 if (camel_url_get_param (service->url, "use_ssl")) {
216                         if (try_starttls)
217                                 tcp_stream = camel_tcp_stream_ssl_new_raw (service->session, service->url->host, STARTTLS_FLAGS);
218                         else {
219                                 port = service->url->port ? service->url->port : 995;
220                                 tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, SSL_PORT_FLAGS);
221                         }
222                 } else {
223                         tcp_stream = camel_tcp_stream_raw_new ();
224                 }
225 #else   
226                 tcp_stream = camel_tcp_stream_raw_new ();
227 #endif /* HAVE_SSL */
228                 
229                 ret = camel_tcp_stream_connect (CAMEL_TCP_STREAM (tcp_stream), h, port);
230                 camel_free_host (h);
231                 if (ret == -1) {
232                         if (errno == EINTR)
233                                 camel_exception_throw(CAMEL_EXCEPTION_USER_CANCEL, _("Connection cancelled"));
234                         else
235                                 camel_exception_throw(CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
236                                                       _("Could not connect to %s (port %d): %s"),
237                                                       service->url->host, port, strerror(errno));
238                 }
239
240                 imap_stream = (CamelIMAPPStream *)camel_imapp_stream_new(tcp_stream);
241                 store->driver = camel_imapp_driver_new(imap_stream);
242
243                 camel_object_unref(imap_stream);
244                 camel_object_unref(tcp_stream);
245         } CAMEL_CATCH(e) {
246                 if (tcp_stream)
247                         camel_object_unref(tcp_stream);
248                 if (imap_stream)
249                         camel_object_unref((CamelObject *)imap_stream);
250                 camel_exception_throw_ex(e);
251         } CAMEL_DONE;
252
253         camel_exception_free(ex);
254 }
255
256 #if 0
257
258 /* leave this stuff out for now */
259
260
261 static struct {
262         char *value;
263         int mode;
264 } ssl_options[] = {
265         { "",              USE_SSL_ALWAYS        },
266         { "always",        USE_SSL_ALWAYS        },
267         { "when-possible", USE_SSL_WHEN_POSSIBLE },
268         { "never",         USE_SSL_NEVER         },
269         { NULL,            USE_SSL_NEVER         },
270 };
271
272 static gboolean
273 connect_to_server_wrapper (CamelService *service, CamelException *ex)
274 {
275 #ifdef HAVE_SSL
276         const char *use_ssl;
277         int i, ssl_mode;
278         
279         use_ssl = camel_url_get_param (service->url, "use_ssl");
280         if (use_ssl) {
281                 for (i = 0; ssl_options[i].value; i++)
282                         if (!strcmp (ssl_options[i].value, use_ssl))
283                                 break;
284                 ssl_mode = ssl_options[i].mode;
285         } else
286                 ssl_mode = USE_SSL_NEVER;
287         
288         if (ssl_mode == USE_SSL_ALWAYS) {
289                 /* First try the ssl port */
290                 if (!connect_to_server (service, ssl_mode, FALSE, ex)) {
291                         if (camel_exception_get_id (ex) == CAMEL_EXCEPTION_SERVICE_UNAVAILABLE) {
292                                 /* The ssl port seems to be unavailable, lets try STARTTLS */
293                                 camel_exception_clear (ex);
294                                 return connect_to_server (service, ssl_mode, TRUE, ex);
295                         } else {
296                                 return FALSE;
297                         }
298                 }
299                 
300                 return TRUE;
301         } else if (ssl_mode == USE_SSL_WHEN_POSSIBLE) {
302                 /* If the server supports STARTTLS, use it */
303                 return connect_to_server (service, ssl_mode, TRUE, ex);
304         } else {
305                 /* User doesn't care about SSL */
306                 return connect_to_server (service, ssl_mode, FALSE, ex);
307         }
308 #else
309         return connect_to_server (service, USE_SSL_NEVER, FALSE, ex);
310 #endif
311 }
312 #endif
313
314 extern CamelServiceAuthType camel_imapp_password_authtype;
315 extern CamelServiceAuthType camel_imapp_apop_authtype;
316
317 static GList *
318 imap_query_auth_types (CamelService *service, CamelException *ex)
319 {
320         /*CamelIMAPPStore *store = CAMEL_IMAPP_STORE (service);*/
321         GList *types = NULL;
322
323         types = CAMEL_SERVICE_CLASS (parent_class)->query_auth_types (service, ex);
324         if (types == NULL)
325                 return NULL;
326
327 #if 0
328         if (connect_to_server_wrapper (service, NULL)) {
329                 types = g_list_concat(types, g_list_copy(store->engine->auth));
330                 imap_disconnect (service, TRUE, NULL);
331         } else {
332                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
333                                       _("Could not connect to POP server on %s"),
334                                       service->url->host);
335         }
336 #endif
337         return types;
338 }
339
340 static void
341 store_get_pass(CamelIMAPPStore *store)
342 {
343         if (((CamelService *)store)->url->passwd == NULL) {
344                 char *prompt;
345                 CamelException ex;
346
347                 camel_exception_init(&ex);
348                 
349                 prompt = g_strdup_printf (_("%sPlease enter the IMAP password for %s@%s"),
350                                           store->login_error?store->login_error:"",
351                                           ((CamelService *)store)->url->user,
352                                           ((CamelService *)store)->url->host);
353                 ((CamelService *)store)->url->passwd = camel_session_get_password(camel_service_get_session((CamelService *)store),
354                                                                                   prompt, FALSE, TRUE, (CamelService*)store, "password", &ex);
355                 g_free (prompt);
356                 if (camel_exception_is_set(&ex))
357                         camel_exception_throw_ex(&ex);
358         }
359 }
360
361 static struct _CamelSasl *
362 store_get_sasl(struct _CamelIMAPPDriver *driver, CamelIMAPPStore *store)
363 {
364         store_get_pass(store);
365
366         if (((CamelService *)store)->url->authmech)
367                 return camel_sasl_new("imap", ((CamelService *)store)->url->authmech, (CamelService *)store);
368
369         return NULL;
370 }
371
372 static void
373 store_get_login(struct _CamelIMAPPDriver *driver, char **login, char **pass, CamelIMAPPStore *store)
374 {
375         store_get_pass(store);
376         
377         *login = g_strdup(((CamelService *)store)->url->user);
378         *pass = g_strdup(((CamelService *)store)->url->passwd);
379 }
380
381 static gboolean
382 imap_connect (CamelService *service, CamelException *ex)
383 {
384         CamelIMAPPStore *store = (CamelIMAPPStore *)service;
385         volatile int ret = FALSE;
386
387         CAMEL_TRY {
388                 volatile int retry = TRUE;
389
390                 if (store->cache == NULL) {
391                         char *root;
392                         
393                         root = camel_session_get_storage_path(service->session, service, ex);
394                         if (root) {
395                                 store->cache = camel_data_cache_new(root, 0, ex);
396                                 g_free(root);
397                                 if (store->cache) {
398                                         /* Default cache expiry - 1 week or not visited in a day */
399                                         camel_data_cache_set_expire_age(store->cache, 60*60*24*7);
400                                         camel_data_cache_set_expire_access(store->cache, 60*60*24);
401                                 }
402                         }
403                         if (camel_exception_is_set(ex))
404                                 camel_exception_throw_ex(ex);
405                 }
406
407                 connect_to_server(service, USE_SSL_NEVER, FALSE);
408
409                 camel_imapp_driver_set_sasl_factory(store->driver, (CamelIMAPPSASLFunc)store_get_sasl, store);
410                 camel_imapp_driver_set_login_query(store->driver, (CamelIMAPPLoginFunc)store_get_login, store);
411                 store->login_error = NULL;
412
413                 do {
414                         CAMEL_TRY {
415                                 if (store->driver->engine->state != IMAP_ENGINE_AUTH)
416                                         camel_imapp_driver_login(store->driver);
417                                 ret = TRUE;
418                                 retry = FALSE;
419                         } CAMEL_CATCH(e) {
420                                 g_free(store->login_error);
421                                 store->login_error = NULL;
422                                 switch (e->id) {
423                                 case CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE:
424                                         store->login_error = g_strdup_printf("%s\n\n", e->desc);
425                                         camel_session_forget_password(service->session, service, "password", ex);
426                                         camel_url_set_passwd(service->url, NULL);
427                                         break;
428                                 default:
429                                         camel_exception_throw_ex(e);
430                                         break;
431                                 }
432                         } CAMEL_DONE;
433                 } while (retry);
434         } CAMEL_CATCH(e) {
435                 camel_exception_xfer(ex, e);
436                 camel_service_disconnect(service, TRUE, NULL);
437                 ret = FALSE;
438         } CAMEL_DONE;
439
440         g_free(store->login_error);
441         store->login_error = NULL;
442
443         return ret;
444 }
445
446 static gboolean
447 imap_disconnect (CamelService *service, gboolean clean, CamelException *ex)
448 {
449         CamelIMAPPStore *store = CAMEL_IMAPP_STORE (service);
450
451         /* FIXME: logout */
452         
453         if (!CAMEL_SERVICE_CLASS (parent_class)->disconnect (service, clean, ex))
454                 return FALSE;
455
456         /* logout/disconnect */
457         if (store->driver) {
458                 camel_object_unref(store->driver);
459                 store->driver = NULL;
460         }
461         
462         return TRUE;
463 }
464
465 static void
466 imap_init_trash (CamelStore *store)
467 {
468         /* no-op */
469         ;
470 }
471
472 static CamelFolder *
473 imap_get_trash (CamelStore *store, CamelException *ex)
474 {
475         /* no-op */
476         return NULL;
477 }
478
479 static CamelFolder *
480 imap_get_folder (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex)
481 {
482         CamelIMAPPStore *istore = (CamelIMAPPStore *)store;
483         CamelIMAPPFolder * volatile folder = NULL;
484
485         /* ??? */
486
487         /* 1. create the folder */
488         /* 2. run select? */
489         /* 3. update the folder */
490
491         CAMEL_TRY {
492                 folder = (CamelIMAPPFolder *)camel_imapp_folder_new(store, folder_name);
493                 camel_imapp_driver_select(istore->driver, folder);
494         } CAMEL_CATCH (e) {
495                 if (folder) {
496                         camel_object_unref(folder);
497                         folder = NULL;
498                 }
499                 camel_exception_xfer(ex, e);
500         } CAMEL_DONE;
501
502         return (CamelFolder *)folder;
503 }
504
505 static CamelFolder *
506 imap_get_inbox(CamelStore *store, CamelException *ex)
507 {
508         camel_exception_setv(ex, 1, "get_inbox::unimplemented");
509
510         return NULL;
511 }
512
513 /* 8 bit, string compare */
514 static int folders_build_cmp(const void *app, const void *bpp)
515 {
516         struct _list_info *a = *((struct _list_info **)app);
517         struct _list_info *b = *((struct _list_info **)bpp);
518         unsigned char *ap = (unsigned char *)(a->name);
519         unsigned char *bp = (unsigned char *)(b->name);
520
521         printf("qsort, cmp '%s' <> '%s'\n", ap, bp);
522
523         while (*ap && *ap == *bp) {
524                 ap++;
525                 bp++;
526         }
527
528         if (*ap < *bp)
529                 return -1;
530         else if (*ap > *bp)
531                 return 1;
532         return 0;
533 }
534
535 /* FIXME: this should go via storesummary? */
536 static CamelFolderInfo *
537 folders_build_info(CamelURL *base, struct _list_info *li)
538 {
539         char *path, *full_name, *name;
540         CamelFolderInfo *fi;
541
542         full_name = imapp_list_get_path(li);
543         name = strrchr(full_name, '/');
544         if (name)
545                 name++;
546         else
547                 name = full_name;
548
549         path = alloca(strlen(full_name)+2);
550         sprintf(path, "/%s", full_name);
551         camel_url_set_path(base, path);
552
553         fi = g_malloc0(sizeof(*fi));
554         fi->url = camel_url_to_string(base, CAMEL_URL_HIDE_ALL);
555         fi->name = g_strdup(name);
556         fi->full_name = full_name;
557         fi->path = g_strdup(path);
558         fi->unread_message_count = -1;
559         fi->flags = li->flags;
560
561         /* TODO: could look up count here ... */
562         /* ?? */
563         /*folder = camel_object_bag_get(store->folders, "INBOX");*/
564
565         return fi;
566 }
567
568 /*
569   a
570   a/b
571   a/b/c
572   a/d
573   b
574   c/d
575
576 */
577
578 /* note, pname is the raw name, not the folderinfo name */
579 /* note also this free's as we go, since we never go 'backwards' */
580 static CamelFolderInfo *
581 folders_build_rec(CamelURL *base, GPtrArray *folders, int *ip, CamelFolderInfo *pfi, char *pname)
582 {
583         int plen = 0;
584         CamelFolderInfo *last = NULL, *first = NULL;
585
586         if (pfi)
587                 plen = strlen(pname);
588
589         for(;(*ip)<(int)folders->len;) {
590                 CamelFolderInfo *fi;
591                 struct _list_info *li;
592
593                 li = folders->pdata[*ip];
594                 printf("checking '%s' is child of '%s'\n", li->name, pname);
595
596                 /* is this a child of the parent? */
597                 if (pfi != NULL
598                     && (strncmp(pname, li->name, strlen(pname)) != 0
599                         || li->name[plen] != li->separator)) {
600                         printf("  nope\n");
601                         break;
602                 }
603                 printf("  yep\n");
604
605                 /* is this not an immediate child of the parent? */
606 #if 0
607                 char *p;
608                 if (pfi != NULL
609                     && li->separator != 0
610                     && (p = strchr(li->name + plen + 1, li->separator)) != NULL) {
611                         if (last == NULL) {
612                                 struct _list_info tli;
613
614                                 tli.flags = CAMEL_FOLDER_NOSELECT|CAMEL_FOLDER_CHILDREN;
615                                 tli.separator = li->separator;
616                                 tli.name = g_strndup(li->name, p-li->name+1);
617                                 fi = folders_build_info(base, &tli);
618                                 fi->parent = pfi;
619                                 if (pfi && pfi->child == NULL)
620                                         pfi->child = fi;
621                                 i = folders_build_rec(folders, i, fi, tli.name);
622                                 break;
623                         }
624                 }
625 #endif
626
627                 fi = folders_build_info(base, li);
628                 fi->parent = pfi;
629                 if (last != NULL)
630                         last->sibling = fi;
631                 last = fi;
632                 if (first == NULL)
633                         first = fi;
634
635                 (*ip)++;
636                 fi->child = folders_build_rec(base, folders, ip, fi, li->name);
637                 imap_free_list(li);
638         }
639
640         return first;
641 }
642
643 static void
644 folder_info_dump(CamelFolderInfo *fi, int depth)
645 {
646         char *s;
647
648         s = alloca(depth+1);
649         memset(s, ' ', depth);
650         s[depth] = 0;
651         while (fi) {
652                 printf("%s%s (%s)\n", s, fi->name, fi->url);
653                 if (fi->child)
654                         folder_info_dump(fi->child, depth+2);
655                 fi = fi->sibling;
656         }
657         
658 }
659
660 static CamelFolderInfo *
661 imap_get_folder_info(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
662 {
663         CamelIMAPPStore *istore = (CamelIMAPPStore *)store;
664         CamelFolderInfo * fi= NULL;
665         char *name;
666
667         /* FIXME: temporary, since this is not a disco store */
668         if (istore->driver == NULL
669             && !camel_service_connect((CamelService *)store, ex))
670                 return NULL;
671
672         name = (char *)top;
673         if (name == NULL || name[0] == 0) {
674                 /* namespace? */
675                 name = "";
676         }
677
678         name = "";
679
680         CAMEL_TRY {
681                 CamelURL *base;
682                 int i;
683                 GPtrArray *folders;
684
685                 /* FIXME: subscriptions? lsub? */
686                 folders = camel_imapp_driver_list(istore->driver, name, flags);
687
688                 /* this greatly simplifies the tree algorithm ... but it might
689                    be faster just to use a hashtable to find parents? */
690                 qsort(folders->pdata, folders->len, sizeof(folders->pdata[0]), folders_build_cmp);
691
692                 i = 0;
693                 base = camel_url_copy(((CamelService *)store)->url);
694                 fi = folders_build_rec(base, folders, &i, NULL, NULL);
695                 camel_url_free(base);
696                 g_ptr_array_free(folders, TRUE);
697         } CAMEL_CATCH(e) {
698                 camel_exception_xfer(ex, e);
699         } CAMEL_DONE;
700
701         printf("built folder info:\n");
702         folder_info_dump(fi, 2);
703
704         return fi;
705
706 #if 0
707         if (top == NULL || !g_ascii_strcasecmp(top, "inbox")) {
708                 CamelURL *uri = camel_url_copy(((CamelService *)store)->url);
709
710                 camel_url_set_path(uri, "/INBOX");
711                 fi = g_malloc0(sizeof(*fi));
712                 fi->url = camel_url_to_string(uri, CAMEL_URL_HIDE_ALL);
713                 camel_url_free(uri);
714                 fi->name = g_strdup("INBOX");
715                 fi->full_name = g_strdup("INBOX");
716                 fi->path = g_strdup("/INBOX");
717                 fi->unread_message_count = -1;
718                 fi->flags = 0;
719
720                 folder = camel_object_bag_get(store->folders, "INBOX");
721                 if (folder) {
722                         /*if (!cflags & FAST)*/
723                         camel_imapp_driver_update(istore->driver, (CamelIMAPPFolder *)folder);
724                         fi->unread_message_count = camel_folder_get_unread_message_count(folder);
725                         camel_object_unref(folder);
726                 }
727         } else {
728                 camel_exception_setv(ex, 1, "not implemented");
729         }
730 #endif
731         return fi;
732
733 #if 0
734         istore->pending_list = g_ptr_array_new();
735
736         CAMEL_TRY {
737                 ic = camel_imapp_engine_command_new(istore->driver->engine, "LIST", NULL, "LIST \"\" %f", top);
738                 camel_imapp_engine_command_queue(istore->driver->engine, ic);
739                 while (camel_imapp_engine_iterate(istore->driver->engine, ic) > 0)
740                         ;
741
742                 if (ic->status->result != IMAP_OK)
743                         camel_exception_throw(1, "list failed: %s", ic->status->text);
744         } CAMEL_CATCH (e) {
745                 camel_exception_xfer(ex, e);
746         } CAMEL_DONE;
747         
748         camel_imapp_engine_command_free(istore->driver->engine, ic);
749
750         printf("got folder list:\n");
751         for (i=0;i<(int)istore->pending_list->len;i++) {
752                 struct _list_info *linfo = istore->pending_list->pdata[i];
753
754                 printf("%s (%c)\n", linfo->name, linfo->separator);
755                 imap_free_list(linfo);
756         }
757         istore->pending_list = NULL;
758
759         return NULL;
760 #endif
761 }
762
763 static void
764 imap_delete_folder(CamelStore *store, const char *folder_name, CamelException *ex)
765 {
766         camel_exception_setv(ex, 1, "delete_folder::unimplemented");
767 }
768
769 static void
770 imap_rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex)
771 {
772         camel_exception_setv(ex, 1, "rename_folder::unimplemented");
773 }
774
775 static CamelFolderInfo *
776 imap_create_folder(CamelStore *store, const char *parent_name, const char *folder_name, CamelException *ex)
777 {
778         camel_exception_setv(ex, 1, "create_folder::unimplemented");
779         return NULL;
780 }
781
782 /* ********************************************************************** */
783 #if 0
784 static int store_resp_fetch(CamelIMAPPEngine *ie, guint32 id, void *data)
785 {
786         struct _fetch_info *finfo;
787         CamelIMAPPStore *istore = data;
788         CamelMessageInfo *info;
789         struct _pending_fetch *pending;
790
791         finfo = imap_parse_fetch(ie->stream);
792         if (istore->selected) {
793                 if ((finfo->got & FETCH_UID) == 0) {
794                         printf("didn't get uid in fetch response?\n");
795                 } else {
796                         info = camel_folder_summary_index(((CamelFolder *)istore->selected)->summary, id-1);
797                         /* exists, check/update */
798                         if (info) {
799                                 if (strcmp(finfo->uid, camel_message_info_uid(info)) != 0) {
800                                         printf("summary at index %d has uid %s expected %s\n", id, camel_message_info_uid(info), finfo->uid);
801                                         /* uid mismatch???  try do it based on uid instead? try to reorder?  i dont know? */
802                                         camel_folder_summary_info_free(((CamelFolder *)istore->selected)->summary, info);
803                                         info = camel_folder_summary_uid(((CamelFolder *)istore->selected)->summary, finfo->uid);
804                                 }
805                         }
806
807                         if (info) {
808                                 if (finfo->got & (FETCH_FLAGS)) {
809                                         printf("updating flags for uid '%s'\n", finfo->uid);
810                                         info->flags = finfo->flags;
811                                         camel_folder_change_info_change_uid(istore->selected->changes, finfo->uid);
812                                 }
813                                 if (finfo->got & FETCH_MINFO) {
814                                         printf("got envelope unexpectedly?\n");
815                                 }
816                                 /* other things go here, like body fetches */
817                         } else {
818                                 pending = g_hash_table_lookup(istore->pending_fetch_table, finfo->uid);
819
820                                 /* we need to create a new info, we only care about flags and minfo */
821
822                                 if (pending)
823                                         info = pending->info;
824                                 else {
825                                         info = camel_folder_summary_info_new(((CamelFolder *)istore->selected)->summary);
826                                         camel_message_info_set_uid(info, g_strdup(finfo->uid));
827                                 }
828
829                                 if (finfo->got & FETCH_FLAGS)
830                                         info->flags = finfo->flags;
831
832                                 if (finfo->got & FETCH_MINFO) {
833                                         /* if we only use ENVELOPE? */
834                                         camel_message_info_set_subject(info, g_strdup(camel_message_info_subject(finfo->minfo)));
835                                         camel_message_info_set_from(info, g_strdup(camel_message_info_from(finfo->minfo)));
836                                         camel_message_info_set_to(info, g_strdup(camel_message_info_to(finfo->minfo)));
837                                         camel_message_info_set_cc(info, g_strdup(camel_message_info_cc(finfo->minfo)));
838                                         info->date_sent = finfo->minfo->date_sent;
839                                         camel_folder_summary_add(((CamelFolder *)istore->selected)->summary, info);
840                                         camel_folder_change_info_add_uid(istore->selected->changes, finfo->uid);
841                                         if (pending) {
842                                                 e_dlist_remove((EDListNode *)pending);
843                                                 g_hash_table_remove(istore->pending_fetch_table, finfo->uid);
844                                                 /*e_memchunk_free(istore->pending_fetch_chunks, pending);*/
845                                         }
846                                 } else if (finfo->got & FETCH_HEADER) {
847                                         /* if we only use HEADER? */
848                                         CamelMimeParser *mp;
849
850                                         if (pending == NULL)
851                                                 camel_folder_summary_info_free(((CamelFolder *)istore->selected)->summary, info);
852                                         mp = camel_mime_parser_new();
853                                         camel_mime_parser_init_with_stream(mp, finfo->header);
854                                         info = camel_folder_summary_info_new_from_parser(((CamelFolder *)istore->selected)->summary, mp);
855                                         camel_object_unref(mp);
856                                         camel_message_info_set_uid(info, g_strdup(finfo->uid));
857
858                                         camel_folder_summary_add(((CamelFolder *)istore->selected)->summary, info);
859                                         camel_folder_change_info_add_uid(istore->selected->changes, finfo->uid);
860                                         if (pending) {
861                                                 /* FIXME: use a dlist */
862                                                 e_dlist_remove((EDListNode *)pending);
863                                                 g_hash_table_remove(istore->pending_fetch_table, camel_message_info_uid(pending->info));
864                                                 camel_folder_summary_info_free(((CamelFolder *)istore->selected)->summary, pending->info);
865                                                 /*e_memchunk_free(istore->pending_fetch_chunks, pending);*/
866                                         }
867                                 } else if (finfo->got & FETCH_FLAGS) {
868                                         if (pending == NULL) {
869                                                 pending = e_memchunk_alloc(istore->pending_fetch_chunks);
870                                                 pending->info = info;
871                                                 g_hash_table_insert(istore->pending_fetch_table, (char *)camel_message_info_uid(info), pending);
872                                                 e_dlist_addtail(&istore->pending_fetch_list, (EDListNode *)pending);
873                                         }
874                                 } else {
875                                         if (pending == NULL)
876                                                 camel_folder_summary_info_free(((CamelFolder *)istore->selected)->summary, info);
877                                         printf("got unexpected fetch response?\n");
878                                         imap_dump_fetch(finfo);
879                                 }
880                         }
881                 }
882         } else {
883                 printf("unexpected fetch response, no folder selected?\n");
884         }
885         /*imap_dump_fetch(finfo);*/
886         imap_free_fetch(finfo);
887
888         return camel_imapp_engine_skip(ie);
889 }
890 #endif
891
892 /* ********************************************************************** */
893
894 /* should be moved to imapp-utils?
895    stuff in imapp-utils should be moved to imapp-parse? */
896
897 /* ********************************************************************** */
898
899 #if 0
900 void
901 camel_imapp_store_folder_selected(CamelIMAPPStore *store, CamelIMAPPFolder *folder, CamelIMAPPSelectResponse *select)
902 {
903         CamelIMAPPCommand * volatile ic = NULL;
904         CamelIMAPPStore *istore = (CamelIMAPPStore *)store;
905         int i;
906         struct _uidset_state ss;
907         GPtrArray *fetch;
908         CamelMessageInfo *info;
909         struct _pending_fetch *fw, *fn;
910
911         printf("imap folder selected\n");
912
913         if (select->uidvalidity == folder->uidvalidity
914             && select->exists == folder->exists
915             && select->recent == folder->recent
916             && select->unseen == folder->unseen) {
917                 /* no work to do? */
918                 return;
919         }
920
921         istore->pending_fetch_table = g_hash_table_new(g_str_hash, g_str_equal);
922         istore->pending_fetch_chunks = e_memchunk_new(256, sizeof(struct _pending_fetch));
923
924         /* perform an update - flags first (and see what we have) */
925         CAMEL_TRY {
926                 ic = camel_imapp_engine_command_new(istore->engine, "FETCH", NULL, "FETCH 1:%d (UID FLAGS)", select->exists);
927                 camel_imapp_engine_command_queue(istore->engine, ic);
928                 while (camel_imapp_engine_iterate(istore->engine, ic) > 0)
929                         ;
930
931                 if (ic->status->result != IMAP_OK)
932                         camel_exception_throw(1, "fetch failed: %s", ic->status->text);
933
934                 /* pending_fetch_list now contains any new messages */
935                 /* FIXME: how do we work out no-longer present messages? */
936                 printf("now fetching info for messages?\n");
937                 uidset_init(&ss, store->engine);
938                 ic = camel_imapp_engine_command_new(istore->engine, "FETCH", NULL, "UID FETCH ");
939                 fw = (struct _pending_fetch *)istore->pending_fetch_list.head;
940                 fn = fw->next;
941                 while (fn) {
942                         info = fw->info;
943                         /* if the uid set fills, then flush the command out */
944                         if (uidset_add(&ss, ic, camel_message_info_uid(info))
945                             || (fn->next == NULL && uidset_done(&ss, ic))) {
946                                 camel_imapp_engine_command_add(istore->engine, ic, " (FLAGS RFC822.HEADER)");
947                                 camel_imapp_engine_command_queue(istore->engine, ic);
948                                 while (camel_imapp_engine_iterate(istore->engine, ic) > 0)
949                                         ;
950                                 if (ic->status->result != IMAP_OK)
951                                         camel_exception_throw(1, "fetch failed: %s", ic->status->text);
952                                 /* if not end ... */
953                                 camel_imapp_engine_command_free(istore->engine, ic);
954                                 ic = camel_imapp_engine_command_new(istore->engine, "FETCH", NULL, "UID FETCH ");
955                         }
956                         fw = fn;
957                         fn = fn->next;
958                 }
959
960                 printf("The pending list should now be empty: %s\n", e_dlist_empty(&istore->pending_fetch_list)?"TRUE":"FALSE");
961                 for (i=0;i<10;i++) {
962                         info = camel_folder_summary_index(((CamelFolder *)istore->selected)->summary, i);
963                         if (info) {
964                                 printf("message info [%d] =\n", i);
965                                 camel_message_info_dump(info);
966                                 camel_folder_summary_info_free(((CamelFolder *)istore->selected)->summary, info);
967                         }
968                 }
969         } CAMEL_CATCH (e) {
970                 /* FIXME: cleanup */
971                 camel_exception_throw_ex(e);
972         } CAMEL_DONE;
973
974         g_hash_table_destroy(istore->pending_fetch_table);
975         istore->pending_fetch_table = NULL;
976         e_memchunk_destroy(istore->pending_fetch_chunks);
977
978         camel_imapp_engine_command_free(istore->engine, ic);
979 }
980 #endif
981
982 #if 0
983 /*char *uids[] = {"1", "2", "4", "5", "6", "7", "9", "11", "12", 0};*/
984 /*char *uids[] = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", 0};*/
985 char *uids[] = {"1", "3", "5", "7", "9", "11", "12", "13", "14", "15", "20", "21", "24", "25", "26", 0};
986
987 void
988 uidset_test(CamelIMAPPEngine *ie)
989 {
990         struct _uidset_state ss;
991         CamelIMAPPCommand *ic;
992         int i;
993
994         /*ic = camel_imapp_engine_command_new(ie, 0, "FETCH", NULL, "FETCH ");*/
995         uidset_init(&ss, 0, 0);
996         for (i=0;uids[i];i++) {
997                 if (uidset_add(&ss, uids[i])) {
998                         printf("\n[%d] flushing uids\n", i);
999                 }
1000         }
1001
1002         if (uidset_done(&ss)) {
1003                 printf("\nflushing uids\n");
1004         }
1005 }
1006 #endif