1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-pop3-folder.c : class for a pop3 folder */
6 * Dan Winship <danw@ximian.com>
7 * Michael Zucchi <notzed@ximian.com>
9 * Copyright (C) 2002 Ximian, Inc. (www.ximian.com)
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of version 2 of the GNU Lesser General Public
13 * License as published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
34 #include <glib/gi18n-lib.h>
36 #include <libedataserver/md5-utils.h>
38 #include "camel-data-cache.h"
39 #include "camel-exception.h"
40 #include "camel-mime-message.h"
41 #include "camel-operation.h"
42 #include "camel-pop3-folder.h"
43 #include "camel-pop3-store.h"
44 #include "camel-stream-filter.h"
45 #include "camel-stream-mem.h"
49 #define CF_CLASS(o) (CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(o)))
50 static CamelFolderClass *parent_class;
52 static void pop3_finalize (CamelObject *object);
53 static void pop3_refresh_info (CamelFolder *folder, CamelException *ex);
54 static void pop3_sync (CamelFolder *folder, gboolean expunge, CamelException *ex);
55 static gint pop3_get_message_count (CamelFolder *folder);
56 static GPtrArray *pop3_get_uids (CamelFolder *folder);
57 static CamelMimeMessage *pop3_get_message (CamelFolder *folder, const char *uid, CamelException *ex);
58 static gboolean pop3_set_message_flags (CamelFolder *folder, const char *uid, guint32 flags, guint32 set);
61 camel_pop3_folder_class_init (CamelPOP3FolderClass *camel_pop3_folder_class)
63 CamelFolderClass *camel_folder_class = CAMEL_FOLDER_CLASS(camel_pop3_folder_class);
65 parent_class = CAMEL_FOLDER_CLASS(camel_folder_get_type());
67 /* virtual method overload */
68 camel_folder_class->refresh_info = pop3_refresh_info;
69 camel_folder_class->sync = pop3_sync;
71 camel_folder_class->get_message_count = pop3_get_message_count;
72 camel_folder_class->get_uids = pop3_get_uids;
73 camel_folder_class->free_uids = camel_folder_free_shallow;
75 camel_folder_class->get_message = pop3_get_message;
76 camel_folder_class->set_message_flags = pop3_set_message_flags;
80 camel_pop3_folder_get_type (void)
82 static CamelType camel_pop3_folder_type = CAMEL_INVALID_TYPE;
84 if (!camel_pop3_folder_type) {
85 camel_pop3_folder_type = camel_type_register (CAMEL_FOLDER_TYPE, "CamelPOP3Folder",
86 sizeof (CamelPOP3Folder),
87 sizeof (CamelPOP3FolderClass),
88 (CamelObjectClassInitFunc) camel_pop3_folder_class_init,
91 (CamelObjectFinalizeFunc) pop3_finalize);
94 return camel_pop3_folder_type;
98 pop3_finalize (CamelObject *object)
100 CamelPOP3Folder *pop3_folder = CAMEL_POP3_FOLDER (object);
101 CamelPOP3FolderInfo **fi = (CamelPOP3FolderInfo **)pop3_folder->uids->pdata;
102 CamelPOP3Store *pop3_store = (CamelPOP3Store *)((CamelFolder *)pop3_folder)->parent_store;
105 if (pop3_folder->uids) {
106 for (i=0;i<pop3_folder->uids->len;i++,fi++) {
108 while (camel_pop3_engine_iterate(pop3_store->engine, fi[0]->cmd) > 0)
110 camel_pop3_engine_command_free(pop3_store->engine, fi[0]->cmd);
117 g_ptr_array_free(pop3_folder->uids, TRUE);
118 g_hash_table_destroy(pop3_folder->uids_uid);
123 camel_pop3_folder_new (CamelStore *parent, CamelException *ex)
127 d(printf("opening pop3 INBOX folder\n"));
129 folder = CAMEL_FOLDER (camel_object_new (CAMEL_POP3_FOLDER_TYPE));
130 camel_folder_construct (folder, parent, "inbox", "inbox");
132 /* mt-ok, since we dont have the folder-lock for new() */
133 camel_folder_refresh_info (folder, ex);/* mt-ok */
134 if (camel_exception_is_set (ex)) {
135 camel_object_unref (CAMEL_OBJECT (folder));
142 /* create a uid from md5 of 'top' output */
144 cmd_builduid(CamelPOP3Engine *pe, CamelPOP3Stream *stream, void *data)
146 CamelPOP3FolderInfo *fi = data;
148 unsigned char digest[16];
149 struct _camel_header_raw *h;
152 /* TODO; somehow work out the limit and use that for proper progress reporting
153 We need a pointer to the folder perhaps? */
154 camel_operation_progress_count(NULL, fi->id);
157 mp = camel_mime_parser_new();
158 camel_mime_parser_init_with_stream(mp, (CamelStream *)stream);
159 switch (camel_mime_parser_step(mp, NULL, NULL)) {
160 case CAMEL_MIME_PARSER_STATE_HEADER:
161 case CAMEL_MIME_PARSER_STATE_MESSAGE:
162 case CAMEL_MIME_PARSER_STATE_MULTIPART:
163 h = camel_mime_parser_headers_raw(mp);
165 if (g_ascii_strcasecmp(h->name, "status") != 0
166 && g_ascii_strcasecmp(h->name, "x-status") != 0) {
167 md5_update(&md5, (const guchar *) h->name, strlen(h->name));
168 md5_update(&md5, (const guchar *) h->value, strlen(h->value));
175 camel_object_unref(mp);
176 md5_final(&md5, digest);
177 fi->uid = camel_base64_encode_simple((const char *) digest, 16);
179 d(printf("building uid for id '%d' = '%s'\n", fi->id, fi->uid));
183 cmd_list(CamelPOP3Engine *pe, CamelPOP3Stream *stream, void *data)
186 unsigned int len, id, size;
188 CamelFolder *folder = data;
189 CamelPOP3Store *pop3_store = CAMEL_POP3_STORE (folder->parent_store);
190 CamelPOP3FolderInfo *fi;
193 ret = camel_pop3_stream_line(stream, &line, &len);
195 if (sscanf((char *) line, "%u %u", &id, &size) == 2) {
196 fi = g_malloc0(sizeof(*fi));
199 fi->index = ((CamelPOP3Folder *)folder)->uids->len;
200 if ((pop3_store->engine->capa & CAMEL_POP3_CAP_UIDL) == 0)
201 fi->cmd = camel_pop3_engine_command_new(pe, CAMEL_POP3_COMMAND_MULTI, cmd_builduid, fi, "TOP %u 0\r\n", id);
202 g_ptr_array_add(((CamelPOP3Folder *)folder)->uids, fi);
203 g_hash_table_insert(((CamelPOP3Folder *)folder)->uids_id, GINT_TO_POINTER(id), fi);
210 cmd_uidl(CamelPOP3Engine *pe, CamelPOP3Stream *stream, void *data)
217 CamelPOP3FolderInfo *fi;
218 CamelPOP3Folder *folder = data;
221 ret = camel_pop3_stream_line(stream, &line, &len);
223 if (strlen((char *) line) > 1024)
225 if (sscanf((char *) line, "%u %s", &id, uid) == 2) {
226 fi = g_hash_table_lookup(folder->uids_id, GINT_TO_POINTER(id));
228 camel_operation_progress(NULL, (fi->index+1) * 100 / folder->uids->len);
229 fi->uid = g_strdup(uid);
230 g_hash_table_insert(folder->uids_uid, fi->uid, fi);
232 g_warning("ID %u (uid: %s) not in previous LIST output", id, uid);
240 pop3_refresh_info (CamelFolder *folder, CamelException *ex)
242 CamelPOP3Store *pop3_store = CAMEL_POP3_STORE (folder->parent_store);
243 CamelPOP3Folder *pop3_folder = (CamelPOP3Folder *) folder;
244 CamelPOP3Command *pcl, *pcu = NULL;
247 camel_operation_start (NULL, _("Retrieving POP summary"));
249 pop3_folder->uids = g_ptr_array_new ();
250 pop3_folder->uids_uid = g_hash_table_new(g_str_hash, g_str_equal);
251 /* only used during setup */
252 pop3_folder->uids_id = g_hash_table_new(NULL, NULL);
254 pcl = camel_pop3_engine_command_new(pop3_store->engine, CAMEL_POP3_COMMAND_MULTI, cmd_list, folder, "LIST\r\n");
255 if (pop3_store->engine->capa & CAMEL_POP3_CAP_UIDL) {
256 pcu = camel_pop3_engine_command_new(pop3_store->engine, CAMEL_POP3_COMMAND_MULTI, cmd_uidl, folder, "UIDL\r\n");
258 while ((i = camel_pop3_engine_iterate(pop3_store->engine, NULL)) > 0)
263 camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("User canceled"));
265 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
266 _("Cannot get POP summary: %s"),
270 /* TODO: check every id has a uid & commands returned OK too? */
272 camel_pop3_engine_command_free(pop3_store->engine, pcl);
274 if (pop3_store->engine->capa & CAMEL_POP3_CAP_UIDL) {
275 camel_pop3_engine_command_free(pop3_store->engine, pcu);
277 for (i=0;i<pop3_folder->uids->len;i++) {
278 CamelPOP3FolderInfo *fi = pop3_folder->uids->pdata[i];
280 camel_pop3_engine_command_free(pop3_store->engine, fi->cmd);
284 g_hash_table_insert(pop3_folder->uids_uid, fi->uid, fi);
288 /* dont need this anymore */
289 g_hash_table_destroy(pop3_folder->uids_id);
291 camel_operation_end (NULL);
296 pop3_sync (CamelFolder *folder, gboolean expunge, CamelException *ex)
298 CamelPOP3Folder *pop3_folder;
299 CamelPOP3Store *pop3_store;
301 CamelPOP3FolderInfo *fi;
303 pop3_folder = CAMEL_POP3_FOLDER (folder);
304 pop3_store = CAMEL_POP3_STORE (folder->parent_store);
306 if(pop3_store->delete_after && !expunge)
308 d(printf("%s(%d): pop3_store->delete_after = [%d], expunge=[%d]\n",
309 __FILE__, __LINE__, pop3_store->delete_after, expunge));
310 camel_operation_start(NULL, _("Expunging old messages"));
311 camel_pop3_delete_old(folder, pop3_store->delete_after,ex);
318 camel_operation_start(NULL, _("Expunging deleted messages"));
320 for (i = 0; i < pop3_folder->uids->len; i++) {
321 fi = pop3_folder->uids->pdata[i];
322 /* busy already? wait for that to finish first */
324 while (camel_pop3_engine_iterate(pop3_store->engine, fi->cmd) > 0)
326 camel_pop3_engine_command_free(pop3_store->engine, fi->cmd);
330 if (fi->flags & CAMEL_MESSAGE_DELETED) {
331 fi->cmd = camel_pop3_engine_command_new(pop3_store->engine,
338 /* also remove from cache */
339 if (pop3_store->cache && fi->uid)
340 camel_data_cache_remove(pop3_store->cache, "cache", fi->uid, NULL);
344 for (i = 0; i < pop3_folder->uids->len; i++) {
345 fi = pop3_folder->uids->pdata[i];
346 /* wait for delete commands to finish */
348 while (camel_pop3_engine_iterate(pop3_store->engine, fi->cmd) > 0)
350 camel_pop3_engine_command_free(pop3_store->engine, fi->cmd);
353 camel_operation_progress(NULL, (i+1) * 100 / pop3_folder->uids->len);
356 camel_operation_end(NULL);
358 camel_pop3_store_expunge (pop3_store, ex);
362 camel_pop3_delete_old(CamelFolder *folder, int days_to_delete, CamelException *ex)
364 CamelPOP3Folder *pop3_folder;
365 CamelPOP3FolderInfo *fi;
367 CamelPOP3Store *pop3_store;
369 CamelMessageInfo *minfo;
371 pop3_folder = CAMEL_POP3_FOLDER (folder);
372 pop3_store = CAMEL_POP3_STORE (CAMEL_FOLDER(pop3_folder)->parent_store);
375 d(printf("%s(%d): pop3_folder->uids->len=[%d]\n", __FILE__, __LINE__, pop3_folder->uids->len));
376 for (i = 0; i < pop3_folder->uids->len; i++) {
377 fi = pop3_folder->uids->pdata[i];
379 minfo = camel_folder_get_message_info (folder, fi->uid);
380 d(printf("%s(%d): fi->uid=[%s]\n", __FILE__, __LINE__, fi->uid));
382 time_t message_time = ((CamelMessageInfoBase *)minfo)->date_received;
383 double time_diff = difftime(temp,message_time);
384 int day_lag = time_diff/(60*60*24);
386 d(printf("%s(%d): message_time= [%ld]\n", __FILE__, __LINE__, message_time));
387 d(printf("%s(%d): day_lag=[%d] \t days_to_delete=[%d]\n",
388 __FILE__, __LINE__, day_lag, days_to_delete));
390 if( day_lag > days_to_delete)
393 while (camel_pop3_engine_iterate(pop3_store->engine, fi->cmd) > 0) {
394 ; /* do nothing - iterating until end */
397 camel_pop3_engine_command_free(pop3_store->engine, fi->cmd);
400 d(printf("%s(%d): Deleting old messages\n", __FILE__, __LINE__));
401 fi->cmd = camel_pop3_engine_command_new(pop3_store->engine,
407 /* also remove from cache */
408 if (pop3_store->cache && fi->uid) {
409 camel_data_cache_remove(pop3_store->cache, "cache", fi->uid, NULL);
412 /* free message - not used anymore */
413 camel_folder_free_message_info (folder, minfo);
417 for (i = 0; i < pop3_folder->uids->len; i++) {
418 fi = pop3_folder->uids->pdata[i];
419 /* wait for delete commands to finish */
421 while (camel_pop3_engine_iterate(pop3_store->engine, fi->cmd) > 0)
423 camel_pop3_engine_command_free(pop3_store->engine, fi->cmd);
426 camel_operation_progress(NULL, (i+1) * 100 / pop3_folder->uids->len);
429 camel_operation_end(NULL);
431 camel_pop3_store_expunge (pop3_store, ex);
438 cmd_tocache(CamelPOP3Engine *pe, CamelPOP3Stream *stream, void *data)
440 CamelPOP3FolderInfo *fi = data;
444 /* What if it fails? */
446 /* We write an '*' to the start of the stream to say its not complete yet */
447 /* This should probably be part of the cache code */
448 if ((n = camel_stream_write(fi->stream, "*", 1)) == -1)
451 while ((n = camel_stream_read((CamelStream *)stream, buffer, sizeof(buffer))) > 0) {
452 n = camel_stream_write(fi->stream, buffer, n);
460 camel_operation_progress(NULL, (w * 100) / fi->size);
463 /* it all worked, output a '#' to say we're a-ok */
465 camel_stream_reset(fi->stream);
466 n = camel_stream_write(fi->stream, "#", 1);
471 g_warning("POP3 retrieval failed: %s", strerror(errno));
476 camel_object_unref((CamelObject *)fi->stream);
480 static CamelMimeMessage *
481 pop3_get_message (CamelFolder *folder, const char *uid, CamelException *ex)
483 CamelMimeMessage *message = NULL;
484 CamelPOP3Store *pop3_store = CAMEL_POP3_STORE (folder->parent_store);
485 CamelPOP3Folder *pop3_folder = (CamelPOP3Folder *)folder;
486 CamelPOP3Command *pcr;
487 CamelPOP3FolderInfo *fi;
490 CamelStream *stream = NULL;
492 fi = g_hash_table_lookup(pop3_folder->uids_uid, uid);
494 camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID_UID,
495 _("No message with UID %s"), uid);
499 /* Sigh, most of the crap in this function is so that the cancel button
500 returns the proper exception code. Sigh. */
502 camel_operation_start_transient(NULL, _("Retrieving POP message %d"), fi->id);
504 /* If we have an oustanding retrieve message running, wait for that to complete
505 & then retrieve from cache, otherwise, start a new one, and similar */
507 if (fi->cmd != NULL) {
508 while ((i = camel_pop3_engine_iterate(pop3_store->engine, fi->cmd)) > 0)
514 /* getting error code? */
515 /*g_assert (fi->cmd->state == CAMEL_POP3_COMMAND_DATA);*/
516 camel_pop3_engine_command_free(pop3_store->engine, fi->cmd);
520 if (fi->err == EINTR)
521 camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("User canceled"));
523 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
524 _("Cannot get message %s: %s"),
525 uid, g_strerror (fi->err));
530 /* check to see if we have safely written flag set */
531 if (pop3_store->cache == NULL
532 || (stream = camel_data_cache_get(pop3_store->cache, "cache", fi->uid, NULL)) == NULL
533 || camel_stream_read(stream, buffer, 1) != 1
534 || buffer[0] != '#') {
536 /* Initiate retrieval, if disk backing fails, use a memory backing */
537 if (pop3_store->cache == NULL
538 || (stream = camel_data_cache_add(pop3_store->cache, "cache", fi->uid, NULL)) == NULL)
539 stream = camel_stream_mem_new();
541 /* ref it, the cache storage routine unref's when done */
542 camel_object_ref((CamelObject *)stream);
545 pcr = camel_pop3_engine_command_new(pop3_store->engine, CAMEL_POP3_COMMAND_MULTI, cmd_tocache, fi, "RETR %u\r\n", fi->id);
547 /* Also initiate retrieval of some of the following messages, assume we'll be receiving them */
548 if (pop3_store->cache != NULL) {
549 /* This should keep track of the last one retrieved, also how many are still
550 oustanding incase of random access on large folders */
552 last = MIN(i+10, pop3_folder->uids->len);
554 CamelPOP3FolderInfo *pfi = pop3_folder->uids->pdata[i];
556 if (pfi->uid && pfi->cmd == NULL) {
557 pfi->stream = camel_data_cache_add(pop3_store->cache, "cache", pfi->uid, NULL);
560 pfi->cmd = camel_pop3_engine_command_new(pop3_store->engine, CAMEL_POP3_COMMAND_MULTI,
561 cmd_tocache, pfi, "RETR %u\r\n", pfi->id);
567 /* now wait for the first one to finish */
568 while ((i = camel_pop3_engine_iterate(pop3_store->engine, pcr)) > 0)
574 /* getting error code? */
575 /*g_assert (pcr->state == CAMEL_POP3_COMMAND_DATA);*/
576 camel_pop3_engine_command_free(pop3_store->engine, pcr);
577 camel_stream_reset(stream);
579 /* Check to see we have safely written flag set */
581 if (fi->err == EINTR)
582 camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("User canceled"));
584 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
585 _("Cannot get message %s: %s"),
586 uid, g_strerror (fi->err));
590 if (camel_stream_read(stream, buffer, 1) != 1 || buffer[0] != '#') {
591 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
592 _("Cannot get message %s: %s"), uid, _("Unknown reason"));
597 message = camel_mime_message_new ();
598 if (camel_data_wrapper_construct_from_stream((CamelDataWrapper *)message, stream) == -1) {
600 camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("User canceled"));
602 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
603 _("Cannot get message %s: %s"),
604 uid, g_strerror (errno));
605 camel_object_unref((CamelObject *)message);
609 camel_object_unref((CamelObject *)stream);
611 camel_operation_end(NULL);
617 pop3_set_message_flags (CamelFolder *folder, const char *uid, guint32 flags, guint32 set)
619 CamelPOP3Folder *pop3_folder = CAMEL_POP3_FOLDER (folder);
620 CamelPOP3FolderInfo *fi;
621 gboolean res = FALSE;
623 fi = g_hash_table_lookup(pop3_folder->uids_uid, uid);
625 guint32 new = (fi->flags & ~flags) | (set & flags);
627 if (fi->flags != new) {
637 pop3_get_message_count (CamelFolder *folder)
639 CamelPOP3Folder *pop3_folder = CAMEL_POP3_FOLDER (folder);
641 return pop3_folder->uids->len;
645 pop3_get_uids (CamelFolder *folder)
647 CamelPOP3Folder *pop3_folder = CAMEL_POP3_FOLDER (folder);
648 GPtrArray *uids = g_ptr_array_new();
649 CamelPOP3FolderInfo **fi = (CamelPOP3FolderInfo **)pop3_folder->uids->pdata;
652 for (i=0;i<pop3_folder->uids->len;i++,fi++) {
654 g_ptr_array_add(uids, fi[0]->uid);