Rename camel_service_get_settings().
[platform/upstream/evolution-data-server.git] / camel / providers / pop3 / camel-pop3-folder.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-pop3-folder.c : class for a pop3 folder */
3
4 /*
5  * Authors:
6  *   Dan Winship <danw@ximian.com>
7  *   Michael Zucchi <notzed@ximian.com>
8  *
9  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
10  *
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.
14  *
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.
19  *
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
23  * USA
24  */
25
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
29
30 #include <stdlib.h>
31 #include <string.h>
32
33 #include <glib/gi18n-lib.h>
34
35 #include "camel-pop3-folder.h"
36 #include "camel-pop3-store.h"
37 #include "camel-pop3-settings.h"
38
39 #define d(x) if (camel_debug("pop3")) x;
40
41 G_DEFINE_TYPE (CamelPOP3Folder, camel_pop3_folder, CAMEL_TYPE_FOLDER)
42
43 static void
44 free_fi (CamelPOP3Folder *pop3_folder,CamelPOP3FolderInfo *fi)
45 {
46
47         CamelPOP3Store *pop3_store;
48         CamelStore *store;
49
50         store = camel_folder_get_parent_store ((CamelFolder *) pop3_folder);
51         pop3_store = CAMEL_POP3_STORE (store);
52
53         g_hash_table_remove (pop3_folder->uids_id, GINT_TO_POINTER (fi->id));
54         if (fi->cmd) {
55                 camel_pop3_engine_command_free (pop3_store->engine, fi->cmd);
56                 fi->cmd = NULL;
57         }
58         g_free (fi->uid);
59         g_free (fi);
60
61 }
62 static void
63 cmd_uidl (CamelPOP3Engine *pe,
64           CamelPOP3Stream *stream,
65           GCancellable *cancellable,
66           gpointer data)
67 {
68         gint ret;
69         guint len;
70         guchar *line;
71         gchar uid[1025];
72         guint id;
73         CamelPOP3FolderInfo *fi;
74         CamelPOP3Folder *folder = data;
75
76         do {
77                 ret = camel_pop3_stream_line (stream, &line, &len, cancellable, NULL);
78                 if (ret >= 0) {
79                         if (strlen ((gchar *) line) > 1024)
80                                 line[1024] = 0;
81                         if (sscanf((gchar *) line, "%u %s", &id, uid) == 2) {
82                                 fi = g_hash_table_lookup (folder->uids_id, GINT_TO_POINTER (id));
83                                 if (fi) {
84                                         camel_operation_progress (NULL, (fi->index + 1) * 100 / folder->uids->len);
85                                         fi->uid = g_strdup (uid);
86                                         g_hash_table_insert (folder->uids_fi, fi->uid, fi);
87                                 } else {
88                                         g_warning("ID %u (uid: %s) not in previous LIST output", id, uid);
89                                 }
90                         }
91                 }
92         } while (ret > 0);
93 }
94
95 /* create a uid from md5 of 'top' output */
96 static void
97 cmd_builduid (CamelPOP3Engine *pe,
98               CamelPOP3Stream *stream,
99               GCancellable *cancellable,
100               gpointer data)
101 {
102         GChecksum *checksum;
103         CamelPOP3FolderInfo *fi = data;
104         struct _camel_header_raw *h;
105         CamelMimeParser *mp;
106         guint8 *digest;
107         gsize length;
108
109         length = g_checksum_type_get_length (G_CHECKSUM_MD5);
110         digest = g_alloca (length);
111
112         /* TODO; somehow work out the limit and use that for proper progress reporting
113          * We need a pointer to the folder perhaps? */
114         camel_operation_progress (NULL, fi->id);
115
116         checksum = g_checksum_new (G_CHECKSUM_MD5);
117         mp = camel_mime_parser_new ();
118         camel_mime_parser_init_with_stream (mp, (CamelStream *) stream, NULL);
119         switch (camel_mime_parser_step (mp, NULL, NULL)) {
120         case CAMEL_MIME_PARSER_STATE_HEADER:
121         case CAMEL_MIME_PARSER_STATE_MESSAGE:
122         case CAMEL_MIME_PARSER_STATE_MULTIPART:
123                 h = camel_mime_parser_headers_raw (mp);
124                 while (h) {
125                         if (g_ascii_strcasecmp(h->name, "status") != 0
126                             && g_ascii_strcasecmp(h->name, "x-status") != 0) {
127                                 g_checksum_update (checksum, (guchar *) h->name, -1);
128                                 g_checksum_update (checksum, (guchar *) h->value, -1);
129                         }
130                         h = h->next;
131                 }
132         default:
133                 break;
134         }
135         g_object_unref (mp);
136         g_checksum_get_digest (checksum, digest, &length);
137         g_checksum_free (checksum);
138
139         fi->uid = g_base64_encode ((guchar *) digest, length);
140
141         d(printf("building uid for id '%d' = '%s'\n", fi->id, fi->uid));
142 }
143
144 static void
145 cmd_list (CamelPOP3Engine *pe,
146           CamelPOP3Stream *stream,
147           GCancellable *cancellable,
148           gpointer data)
149 {
150         gint ret;
151         guint len, id, size;
152         guchar *line;
153         CamelFolder *folder = data;
154         CamelStore *parent_store;
155         CamelPOP3Store *pop3_store;
156         CamelPOP3FolderInfo *fi;
157         gint i = 0, total, last_uid=-1;
158         CamelPOP3Folder *pop3_folder;
159         CamelService *service;
160         CamelSettings *settings;
161         gint batch_fetch_count;
162
163         parent_store = camel_folder_get_parent_store (folder);
164         pop3_store = CAMEL_POP3_STORE (parent_store);
165         pop3_folder = (CamelPOP3Folder *) folder;
166         service = (CamelService *) parent_store;
167
168         settings = camel_service_ref_settings (service);
169
170         batch_fetch_count = camel_pop3_settings_get_batch_fetch_count (
171                 CAMEL_POP3_SETTINGS (settings));
172
173         g_object_unref (settings);
174
175         do {
176                 ret = camel_pop3_stream_line (stream, &line, &len, cancellable, NULL);
177                 if (ret >= 0) {
178                         if (sscanf((gchar *) line, "%u %u", &id, &size) == 2) {
179                                 fi = g_malloc0 (sizeof (*fi));
180                                 fi->size = size;
181                                 fi->id = id;
182                                 fi->index = ((CamelPOP3Folder *) folder)->uids->len;
183                                 if ((pop3_store->engine->capa & CAMEL_POP3_CAP_UIDL) == 0)
184                                         fi->cmd = camel_pop3_engine_command_new(pe, CAMEL_POP3_COMMAND_MULTI, cmd_builduid, fi, cancellable, NULL, "TOP %u 0\r\n", id);
185                                 g_ptr_array_add (pop3_folder->uids, fi);
186                                 g_hash_table_insert (pop3_folder->uids_id, GINT_TO_POINTER (id), fi);
187                         }
188                 }
189         } while (ret > 0);
190
191         /* Trim the list for mobile devices*/
192         if (pop3_folder->mobile_mode && pop3_folder->uids->len) {
193                 gint y = 0;
194                 gboolean save_uid = FALSE;
195
196                 /* Preserve the first message's ID */
197                 fi = pop3_folder->uids->pdata[0];
198                 pop3_folder->first_id = fi->id;
199
200                 total = pop3_folder->uids->len;
201                 if (pop3_folder->key_file) {
202                         last_uid = g_key_file_get_integer (pop3_folder->key_file, "UIDConfig", "last-saved-uid", NULL);
203                         if (!last_uid) {
204                                 /* First time downloading the POP folder, lets just download only a batch. */
205                                 last_uid = -1;
206                         }
207                         d(printf("Last stored' first uid: %d\n", last_uid));
208                 }
209
210                 if (last_uid == -1)
211                         save_uid = TRUE;
212
213                 for (i = total - 1; i >= 0; i--) {
214                         fi = pop3_folder->uids->pdata[i];
215
216                         if ((last_uid != -1 && last_uid >= fi->id) || (last_uid == -1 && i == total - batch_fetch_count)) {
217                                 if (last_uid != -1 && last_uid < fi->id)
218                                         i++; /* if the last uid was deleted on the server, then we need the last but 1 */
219                                 break;
220                         }
221
222                 }
223                 if (i> 0 && pop3_folder->fetch_type == CAMEL_FETCH_OLD_MESSAGES && pop3_folder->fetch_more) {
224                         gint k = 0;
225                         /* Lets pull another window of old messages */
226                         save_uid = TRUE;
227                         /* Decrement 'i' by another batch count or till we reach the first message */
228                         d(printf("Fetch more (%d): from %d", pop3_folder->fetch_more, i));
229                         for (k = 0; k< pop3_folder->fetch_more && i>= 0; k++, i--);
230                         d(printf(" to %d\n", i));
231
232                         /* Don't load messages newer than the latest we already had. We had to just get old messages and not 
233                          * new messages. */
234                         for (y = i; y < total; y++) {
235                                 fi = pop3_folder->uids->pdata[y];
236                                 if (fi->id == pop3_folder->latest_id) {
237                                         /* Delete everything after this. */
238
239                                         for (y = k + 1; y < total; y++) {
240                                                 fi = pop3_folder->uids->pdata[y];
241                                                 free_fi (pop3_folder, fi);
242                                         }
243                                         g_ptr_array_remove_range (pop3_folder->uids, k + 1, total - k - 1);
244                                         break;
245                                 }
246                         }
247
248                 } else if (pop3_folder->fetch_more == CAMEL_FETCH_NEW_MESSAGES && pop3_folder->fetch_more) {
249                         /* We need to download new messages. */
250                         gint k = 0;
251
252                         for (k = i; k < total; k++) {
253                                 fi = pop3_folder->uids->pdata[k];
254                                 if (fi->id == pop3_folder->latest_id) {
255                                         /* We need to just download the specified number of messages. */
256                                         y= (k + pop3_folder->fetch_more) < total ? (k + pop3_folder->fetch_more) : total - 1;
257                                         break;
258                                 }
259                         }
260
261                 }
262
263                 /* Delete the unnecessary old messages */
264                 if (i > 0) {
265                         gint j = 0;
266                         /* i is the start of the last fetch UID, so remove everything else from 0 to i */
267                         for (; j < i; j++) {
268                                 fi = pop3_folder->uids->pdata[j];
269                                 free_fi (pop3_folder, fi);
270                         }
271                         g_ptr_array_remove_range (pop3_folder->uids, 0, i);
272                         d(printf("Removing %d uids that are old\n", i));
273
274                 }
275
276                 /* Delete the unnecessary new message references. */
277                 if (y + 1 < total) {
278                         gint k;
279
280                         for (k = y + 1; k < total; k++) {
281                                 fi = pop3_folder->uids->pdata[k];
282                                 free_fi (pop3_folder, fi);
283                         }
284                         g_ptr_array_remove_range (pop3_folder->uids, y + 1, total - y - 1);
285                 }
286
287                 if (save_uid) {
288                         gchar *contents;
289                         gsize len;
290                         const gchar *root;
291                         gchar *path;
292
293                         /* Save the last fetched UID */
294                         fi = pop3_folder->uids->pdata[0];
295                         g_key_file_set_integer (pop3_folder->key_file, "UIDConfig", "last-saved-uid", fi->id);
296                         contents = g_key_file_to_data (pop3_folder->key_file, &len, NULL);
297                         root = camel_service_get_user_cache_dir (service);
298                         path = g_build_filename (root, "uidconfig", NULL);
299                         g_file_set_contents (path, contents, len, NULL);
300                         g_key_file_load_from_file (pop3_folder->key_file, path, G_KEY_FILE_NONE, NULL);
301                         g_free (contents);
302                         g_free (path);
303                         d(printf("Saving last uid %d\n", fi->id));
304
305                 }
306
307         }
308
309 }
310
311 static void
312 cmd_tocache (CamelPOP3Engine *pe,
313              CamelPOP3Stream *stream,
314              GCancellable *cancellable,
315              gpointer data)
316 {
317         CamelPOP3FolderInfo *fi = data;
318         gchar buffer[2048];
319         gint w = 0, n;
320         GError *error = NULL;
321
322         /* What if it fails? */
323
324         /* We write an '*' to the start of the stream to say its not complete yet */
325         /* This should probably be part of the cache code */
326         if ((n = camel_stream_write (fi->stream, "*", 1, cancellable, &error)) == -1)
327                 goto done;
328
329         while ((n = camel_stream_read ((CamelStream *) stream, buffer, sizeof (buffer), cancellable, &error)) > 0) {
330                 n = camel_stream_write (fi->stream, buffer, n, cancellable, &error);
331                 if (n == -1)
332                         break;
333
334                 w += n;
335                 if (w > fi->size)
336                         w = fi->size;
337                 if (fi->size != 0)
338                         camel_operation_progress (NULL, (w * 100) / fi->size);
339         }
340
341         /* it all worked, output a '#' to say we're a-ok */
342         if (error == NULL) {
343                 g_seekable_seek (
344                         G_SEEKABLE (fi->stream),
345                         0, G_SEEK_SET, cancellable, NULL);
346                 camel_stream_write (fi->stream, "#", 1, cancellable, &error);
347         }
348
349 done:
350         if (error != NULL) {
351                 g_warning ("POP3 retrieval failed: %s", error->message);
352                 g_error_free (error);
353         }
354
355         g_object_unref (fi->stream);
356         fi->stream = NULL;
357 }
358
359 static void
360 pop3_folder_dispose (GObject *object)
361 {
362         CamelPOP3Folder *pop3_folder = CAMEL_POP3_FOLDER (object);
363         CamelPOP3Store *pop3_store = NULL;
364         CamelStore *parent_store;
365
366         parent_store = camel_folder_get_parent_store (CAMEL_FOLDER (object));
367         if (parent_store)
368                 pop3_store = CAMEL_POP3_STORE (parent_store);
369
370         if (pop3_folder->uids) {
371                 gint i;
372                 CamelPOP3FolderInfo **fi = (CamelPOP3FolderInfo **) pop3_folder->uids->pdata;
373                 gboolean is_online = camel_service_get_connection_status (CAMEL_SERVICE (parent_store)) == CAMEL_SERVICE_CONNECTED;
374
375                 for (i = 0; i < pop3_folder->uids->len; i++, fi++) {
376                         if (fi[0]->cmd && pop3_store && is_online) {
377                                 while (camel_pop3_engine_iterate (pop3_store->engine, fi[0]->cmd, NULL, NULL) > 0)
378                                         ;
379                                 camel_pop3_engine_command_free (pop3_store->engine, fi[0]->cmd);
380                         }
381
382                         g_free (fi[0]->uid);
383                         g_free (fi[0]);
384                 }
385
386                 g_ptr_array_free (pop3_folder->uids, TRUE);
387                 pop3_folder->uids = NULL;
388         }
389
390         if (pop3_folder->uids_fi) {
391                 g_hash_table_destroy (pop3_folder->uids_fi);
392                 pop3_folder->uids_fi = NULL;
393         }
394
395         /* Chain up to parent's dispose() method. */
396         G_OBJECT_CLASS (camel_pop3_folder_parent_class)->dispose (object);
397 }
398
399 static gint
400 pop3_folder_get_message_count (CamelFolder *folder)
401 {
402         CamelPOP3Folder *pop3_folder = CAMEL_POP3_FOLDER (folder);
403
404         return pop3_folder->uids->len;
405 }
406
407 static GPtrArray *
408 pop3_folder_get_uids (CamelFolder *folder)
409 {
410         CamelPOP3Folder *pop3_folder = CAMEL_POP3_FOLDER (folder);
411         GPtrArray *uids = g_ptr_array_new ();
412         CamelPOP3FolderInfo **fi = (CamelPOP3FolderInfo **) pop3_folder->uids->pdata;
413         gint i;
414
415         for (i = 0; i < pop3_folder->uids->len; i++,fi++) {
416                 if (fi[0]->uid)
417                         g_ptr_array_add (uids, fi[0]->uid);
418         }
419
420         return uids;
421 }
422
423 static gchar *
424 pop3_folder_get_filename (CamelFolder *folder,
425                           const gchar *uid,
426                           GError **error)
427 {
428         CamelStore *parent_store;
429         CamelPOP3Folder *pop3_folder;
430         CamelPOP3Store *pop3_store;
431         CamelPOP3FolderInfo *fi;
432
433         parent_store = camel_folder_get_parent_store (folder);
434
435         pop3_folder = CAMEL_POP3_FOLDER (folder);
436         pop3_store = CAMEL_POP3_STORE (parent_store);
437
438         fi = g_hash_table_lookup (pop3_folder->uids_fi, uid);
439         if (fi == NULL) {
440                 g_set_error (
441                         error, CAMEL_FOLDER_ERROR,
442                         CAMEL_FOLDER_ERROR_INVALID_UID,
443                         _("No message with UID %s"), uid);
444                 return NULL;
445         }
446
447         return camel_data_cache_get_filename (
448                 pop3_store->cache, "cache", fi->uid);
449 }
450
451 static gboolean
452 pop3_folder_set_message_flags (CamelFolder *folder,
453                                const gchar *uid,
454                                CamelMessageFlags flags,
455                                CamelMessageFlags set)
456 {
457         CamelPOP3Folder *pop3_folder = CAMEL_POP3_FOLDER (folder);
458         CamelPOP3FolderInfo *fi;
459         gboolean res = FALSE;
460
461         fi = g_hash_table_lookup (pop3_folder->uids_fi, uid);
462         if (fi) {
463                 guint32 new = (fi->flags & ~flags) | (set & flags);
464
465                 if (fi->flags != new) {
466                         fi->flags = new;
467                         res = TRUE;
468                 }
469         }
470
471         return res;
472 }
473
474 static CamelMimeMessage *
475 pop3_folder_get_message_sync (CamelFolder *folder,
476                               const gchar *uid,
477                               GCancellable *cancellable,
478                               GError **error)
479 {
480         CamelStore *parent_store;
481         CamelMimeMessage *message = NULL;
482         CamelPOP3Store *pop3_store;
483         CamelPOP3Folder *pop3_folder;
484         CamelPOP3Command *pcr;
485         CamelPOP3FolderInfo *fi;
486         gchar buffer[1];
487         gint i, last;
488         CamelStream *stream = NULL;
489         CamelService *service;
490         CamelSettings *settings;
491         gboolean auto_fetch;
492
493         g_return_val_if_fail (uid != NULL, NULL);
494
495         parent_store = camel_folder_get_parent_store (folder);
496
497         pop3_folder = CAMEL_POP3_FOLDER (folder);
498         pop3_store = CAMEL_POP3_STORE (parent_store);
499
500         service = CAMEL_SERVICE (parent_store);
501
502         settings = camel_service_ref_settings (service);
503
504         g_object_get (
505                 settings,
506                 "auto-fetch", &auto_fetch,
507                 NULL);
508
509         g_object_unref (settings);
510
511         fi = g_hash_table_lookup (pop3_folder->uids_fi, uid);
512         if (fi == NULL) {
513                 g_set_error (
514                         error, CAMEL_FOLDER_ERROR,
515                         CAMEL_FOLDER_ERROR_INVALID_UID,
516                         _("No message with UID %s"), uid);
517                 return NULL;
518         }
519
520         if (camel_service_get_connection_status (CAMEL_SERVICE (parent_store)) != CAMEL_SERVICE_CONNECTED) {
521                 g_set_error (
522                         error, CAMEL_SERVICE_ERROR,
523                         CAMEL_SERVICE_ERROR_UNAVAILABLE,
524                         _("You must be working online to complete this operation"));
525                 return FALSE;
526         }
527
528         /* Sigh, most of the crap in this function is so that the cancel button
529          * returns the proper exception code.  Sigh. */
530
531         camel_operation_push_message (
532                 cancellable, _("Retrieving POP message %d"), fi->id);
533
534         /* If we have an oustanding retrieve message running, wait for that to complete
535          * & then retrieve from cache, otherwise, start a new one, and similar */
536
537         if (fi->cmd != NULL) {
538                 while ((i = camel_pop3_engine_iterate (pop3_store->engine, fi->cmd, cancellable, error)) > 0)
539                         ;
540
541                 /* getting error code? */
542                 /*g_assert (fi->cmd->state == CAMEL_POP3_COMMAND_DATA);*/
543                 camel_pop3_engine_command_free (pop3_store->engine, fi->cmd);
544                 fi->cmd = NULL;
545
546                 if (i == -1) {
547                         g_prefix_error (
548                                 error, _("Cannot get message %s: "), uid);
549                         goto fail;
550                 }
551         }
552
553         /* check to see if we have safely written flag set */
554         if (pop3_store->cache == NULL
555             || (stream = camel_data_cache_get(pop3_store->cache, "cache", fi->uid, NULL)) == NULL
556             || camel_stream_read (stream, buffer, 1, cancellable, NULL) != 1
557             || buffer[0] != '#') {
558
559                 /* Initiate retrieval, if disk backing fails, use a memory backing */
560                 if (pop3_store->cache == NULL
561                     || (stream = camel_data_cache_add(pop3_store->cache, "cache", fi->uid, NULL)) == NULL)
562                         stream = camel_stream_mem_new ();
563
564                 /* ref it, the cache storage routine unref's when done */
565                 fi->stream = g_object_ref (stream);
566                 pcr = camel_pop3_engine_command_new(pop3_store->engine, CAMEL_POP3_COMMAND_MULTI, cmd_tocache, fi, cancellable, NULL, "RETR %u\r\n", fi->id);
567
568                 /* Also initiate retrieval of some of the following messages, assume we'll be receiving them */
569                 if (auto_fetch && pop3_store->cache != NULL) {
570                         /* This should keep track of the last one retrieved, also how many are still
571                          * oustanding incase of random access on large folders */
572                         i = fi->index + 1;
573                         last = MIN (i + 10, pop3_folder->uids->len);
574                         for (; i < last; i++) {
575                                 CamelPOP3FolderInfo *pfi = pop3_folder->uids->pdata[i];
576
577                                 if (pfi->uid && pfi->cmd == NULL) {
578                                         pfi->stream = camel_data_cache_add(pop3_store->cache, "cache", pfi->uid, NULL);
579                                         if (pfi->stream) {
580                                                 pfi->cmd = camel_pop3_engine_command_new (pop3_store->engine, CAMEL_POP3_COMMAND_MULTI,
581                                                                                          cmd_tocache, pfi, cancellable, NULL, "RETR %u\r\n", pfi->id);
582                                         }
583                                 }
584                         }
585                 }
586
587                 /* now wait for the first one to finish */
588                 while ((i = camel_pop3_engine_iterate (pop3_store->engine, pcr, cancellable, error)) > 0)
589                         ;
590
591                 /* getting error code? */
592                 /*g_assert (pcr->state == CAMEL_POP3_COMMAND_DATA);*/
593                 camel_pop3_engine_command_free (pop3_store->engine, pcr);
594                 g_seekable_seek (
595                         G_SEEKABLE (stream), 0, G_SEEK_SET, NULL, NULL);
596
597                 /* Check to see we have safely written flag set */
598                 if (i == -1) {
599                         g_prefix_error (
600                                 error, _("Cannot get message %s: "), uid);
601                         goto done;
602                 }
603
604                 if (camel_stream_read (stream, buffer, 1, cancellable, error) == -1)
605                         goto done;
606
607                 if (buffer[0] != '#') {
608                         g_set_error (
609                                 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
610                                 _("Cannot get message %s: %s"), uid,
611                                 _("Unknown reason"));
612                         goto done;
613                 }
614         }
615
616         message = camel_mime_message_new ();
617         if (!camel_data_wrapper_construct_from_stream_sync (
618                 CAMEL_DATA_WRAPPER (message), stream, cancellable, error)) {
619                 g_prefix_error (error, _("Cannot get message %s: "), uid);
620                 g_object_unref (message);
621                 message = NULL;
622         } else {
623                 /* because the UID in the local store doesn't match with the UID in the pop3 store */
624                 camel_medium_add_header (CAMEL_MEDIUM (message), "X-Evolution-POP3-UID", uid);
625         }
626 done:
627         g_object_unref (stream);
628 fail:
629         camel_operation_pop_message (cancellable);
630
631         return message;
632 }
633
634 static gboolean
635 pop3_folder_refresh_info_sync (CamelFolder *folder,
636                                GCancellable *cancellable,
637                                GError **error)
638 {
639         CamelStore *parent_store;
640         CamelPOP3Store *pop3_store;
641         CamelPOP3Folder *pop3_folder = (CamelPOP3Folder *) folder;
642         CamelPOP3Command *pcl, *pcu = NULL;
643         gboolean success = TRUE;
644         GError *local_error = NULL;
645         gint i;
646
647         parent_store = camel_folder_get_parent_store (folder);
648         pop3_store = CAMEL_POP3_STORE (parent_store);
649
650         if (camel_service_get_connection_status (CAMEL_SERVICE (parent_store)) != CAMEL_SERVICE_CONNECTED) {
651                 g_set_error (
652                         error, CAMEL_SERVICE_ERROR,
653                         CAMEL_SERVICE_ERROR_UNAVAILABLE,
654                         _("You must be working online to complete this operation"));
655                 return FALSE;
656         }
657
658         camel_operation_push_message (
659                 cancellable, _("Retrieving POP summary"));
660
661         /* Get rid of the old cache */
662         if (pop3_folder->uids) {
663                 gint i;
664                 CamelPOP3FolderInfo *last_fi;
665
666                 if (pop3_folder->uids->len) {
667                         last_fi = pop3_folder->uids->pdata[pop3_folder->uids->len - 1];
668                         if (last_fi)
669                                 pop3_folder->latest_id = last_fi->id;
670                         else
671                                 pop3_folder->latest_id = -1;
672                 } else
673                         pop3_folder->latest_id = -1;
674
675                 for (i = 0; i < pop3_folder->uids->len; i++) {
676                         CamelPOP3FolderInfo *fi = pop3_folder->uids->pdata[i];
677                         if (fi->cmd) {
678                                 camel_pop3_engine_command_free (pop3_store->engine, fi->cmd);
679                                 fi->cmd = NULL;
680                         }
681                         g_free (fi->uid);
682                         g_free (fi);
683                 }
684
685                 g_ptr_array_free (pop3_folder->uids, TRUE);
686         }
687
688         if (pop3_folder->uids_fi) {
689                 g_hash_table_destroy (pop3_folder->uids_fi);
690                 pop3_folder->uids_fi = NULL;
691         }
692
693         /* Get a new working set. */
694         pop3_folder->uids = g_ptr_array_new ();
695         pop3_folder->uids_fi = g_hash_table_new (g_str_hash, g_str_equal);
696
697         /* only used during setup */
698         pop3_folder->uids_id = g_hash_table_new (NULL, NULL);
699
700         pcl = camel_pop3_engine_command_new(pop3_store->engine, CAMEL_POP3_COMMAND_MULTI, cmd_list, folder, cancellable, &local_error, "LIST\r\n");
701         if (!local_error && (pop3_store->engine->capa & CAMEL_POP3_CAP_UIDL) != 0)
702                 pcu = camel_pop3_engine_command_new(pop3_store->engine, CAMEL_POP3_COMMAND_MULTI, cmd_uidl, folder, cancellable, &local_error, "UIDL\r\n");
703         while ((i = camel_pop3_engine_iterate (pop3_store->engine, NULL, cancellable, error)) > 0)
704                 ;
705
706         if (local_error) {
707                 g_propagate_error (error, local_error);
708                 success = FALSE;
709         } else if (i == -1) {
710                 g_prefix_error (error, _("Cannot get POP summary: "));
711                 success = FALSE;
712         }
713
714         /* TODO: check every id has a uid & commands returned OK too? */
715
716         if (pcl)
717                 camel_pop3_engine_command_free (pop3_store->engine, pcl);
718
719         if (pcu) {
720                 camel_pop3_engine_command_free (pop3_store->engine, pcu);
721         } else {
722                 for (i = 0; i < pop3_folder->uids->len; i++) {
723                         CamelPOP3FolderInfo *fi = pop3_folder->uids->pdata[i];
724                         if (fi->cmd) {
725                                 camel_pop3_engine_command_free (pop3_store->engine, fi->cmd);
726                                 fi->cmd = NULL;
727                         }
728                         if (fi->uid) {
729                                 g_hash_table_insert (pop3_folder->uids_fi, fi->uid, fi);
730                         }
731                 }
732         }
733
734         /* dont need this anymore */
735         g_hash_table_destroy (pop3_folder->uids_id);
736         pop3_folder->uids_id = NULL;
737
738         camel_operation_pop_message (cancellable);
739
740         return success;
741 }
742
743 static gboolean
744 pop3_fetch_messages_sync (CamelFolder *folder,
745                           CamelFetchType type,
746                           gint limit,
747                           GCancellable *cancellable,
748                           GError **error)
749 {
750         CamelPOP3FolderInfo *fi;
751         CamelPOP3Folder *pop3_folder = (CamelPOP3Folder *) folder;
752         gint old_len;
753         CamelStore *parent_store;
754         CamelService *service;
755         CamelSettings *settings;
756         gint batch_fetch_count;
757
758         parent_store = camel_folder_get_parent_store (folder);
759         service = (CamelService *) parent_store;
760
761         settings = camel_service_ref_settings (service);
762
763         batch_fetch_count = camel_pop3_settings_get_batch_fetch_count (
764                 CAMEL_POP3_SETTINGS (settings));
765
766         g_object_unref (settings);
767
768         old_len = pop3_folder->uids->len;
769
770         /* If we have the first message already, then return FALSE */
771         fi = pop3_folder->uids->pdata[0];
772         if (type == CAMEL_FETCH_OLD_MESSAGES && fi->id == pop3_folder->first_id)
773                 return FALSE;
774
775         pop3_folder->fetch_type = type;
776         pop3_folder->fetch_more = (limit > 0) ? limit : batch_fetch_count;
777         pop3_folder_refresh_info_sync (folder, cancellable, error);
778         pop3_folder->fetch_more = 0;
779
780         /* Even if we downloaded the first/oldest message, just now, return TRUE so that we wont waste another cycle */
781         fi = pop3_folder->uids->pdata[0];
782         if (type == CAMEL_FETCH_OLD_MESSAGES && fi->id == pop3_folder->first_id)
783                 return FALSE;
784         else if (type == CAMEL_FETCH_NEW_MESSAGES && old_len == pop3_folder->uids->len)
785                 return FALSE; /* We didnt fetch any new messages as there were none probably. */
786
787         return TRUE;
788 }
789
790 static gboolean
791 pop3_folder_synchronize_sync (CamelFolder *folder,
792                               gboolean expunge,
793                               GCancellable *cancellable,
794                               GError **error)
795 {
796         CamelService *service;
797         CamelSettings *settings;
798         CamelStore *parent_store;
799         CamelPOP3Folder *pop3_folder;
800         CamelPOP3Store *pop3_store;
801         CamelPOP3FolderInfo *fi;
802         gint delete_after_days;
803         gboolean delete_expunged;
804         gboolean keep_on_server;
805         gboolean is_online;
806         gint i;
807
808         parent_store = camel_folder_get_parent_store (folder);
809
810         pop3_folder = CAMEL_POP3_FOLDER (folder);
811         pop3_store = CAMEL_POP3_STORE (parent_store);
812
813         service = CAMEL_SERVICE (parent_store);
814         is_online = camel_service_get_connection_status (service) == CAMEL_SERVICE_CONNECTED;
815
816         settings = camel_service_ref_settings (service);
817
818         g_object_get (
819                 settings,
820                 "delete-after-days", &delete_after_days,
821                 "delete-expunged", &delete_expunged,
822                 "keep-on-server", &keep_on_server,
823                 NULL);
824
825         g_object_unref (settings);
826
827         if (is_online && delete_after_days > 0 && !expunge) {
828                 camel_operation_push_message (
829                         cancellable, _("Expunging old messages"));
830
831                 camel_pop3_delete_old (
832                         folder, delete_after_days, cancellable, error);
833
834                 camel_operation_pop_message (cancellable);
835         }
836
837         if (!expunge || (keep_on_server && !delete_expunged))
838                 return TRUE;
839
840         if (!is_online) {
841                 g_set_error (
842                         error, CAMEL_SERVICE_ERROR,
843                         CAMEL_SERVICE_ERROR_UNAVAILABLE,
844                         _("You must be working online to complete this operation"));
845                 return FALSE;
846         }
847
848         camel_operation_push_message (
849                 cancellable, _("Expunging deleted messages"));
850
851         for (i = 0; i < pop3_folder->uids->len; i++) {
852                 fi = pop3_folder->uids->pdata[i];
853                 /* busy already?  wait for that to finish first */
854                 if (fi->cmd) {
855                         while (camel_pop3_engine_iterate (pop3_store->engine, fi->cmd, cancellable, NULL) > 0)
856                                 ;
857                         camel_pop3_engine_command_free (pop3_store->engine, fi->cmd);
858                         fi->cmd = NULL;
859                 }
860
861                 if (fi->flags & CAMEL_MESSAGE_DELETED) {
862                         fi->cmd = camel_pop3_engine_command_new (pop3_store->engine,
863                                                                 0,
864                                                                 NULL,
865                                                                 NULL,
866                                                                 cancellable, NULL,
867                                                                 "DELE %u\r\n",
868                                                                 fi->id);
869
870                         /* also remove from cache */
871                         if (pop3_store->cache && fi->uid)
872                                 camel_data_cache_remove(pop3_store->cache, "cache", fi->uid, NULL);
873                 }
874         }
875
876         for (i = 0; i < pop3_folder->uids->len; i++) {
877                 fi = pop3_folder->uids->pdata[i];
878                 /* wait for delete commands to finish */
879                 if (fi->cmd) {
880                         while (camel_pop3_engine_iterate (pop3_store->engine, fi->cmd, cancellable, NULL) > 0)
881                                 ;
882                         camel_pop3_engine_command_free (pop3_store->engine, fi->cmd);
883                         fi->cmd = NULL;
884                 }
885                 camel_operation_progress (
886                         cancellable, (i + 1) * 100 / pop3_folder->uids->len);
887         }
888
889         camel_operation_pop_message (cancellable);
890
891         return camel_pop3_store_expunge (pop3_store, cancellable, error);
892 }
893
894 static void
895 camel_pop3_folder_class_init (CamelPOP3FolderClass *class)
896 {
897         GObjectClass *object_class;
898         CamelFolderClass *folder_class;
899
900         object_class = G_OBJECT_CLASS (class);
901         object_class->dispose = pop3_folder_dispose;
902
903         folder_class = CAMEL_FOLDER_CLASS (class);
904         folder_class->fetch_messages_sync = pop3_fetch_messages_sync;
905         folder_class->get_message_count = pop3_folder_get_message_count;
906         folder_class->get_uids = pop3_folder_get_uids;
907         folder_class->free_uids = camel_folder_free_shallow;
908         folder_class->get_filename = pop3_folder_get_filename;
909         folder_class->set_message_flags = pop3_folder_set_message_flags;
910         folder_class->get_message_sync = pop3_folder_get_message_sync;
911         folder_class->refresh_info_sync = pop3_folder_refresh_info_sync;
912         folder_class->synchronize_sync = pop3_folder_synchronize_sync;
913 }
914
915 static void
916 camel_pop3_folder_init (CamelPOP3Folder *pop3_folder)
917 {
918         pop3_folder->uids = g_ptr_array_new ();
919         pop3_folder->uids_fi = g_hash_table_new (g_str_hash, g_str_equal);
920 }
921
922 CamelFolder *
923 camel_pop3_folder_new (CamelStore *parent,
924                        GCancellable *cancellable,
925                        GError **error)
926 {
927         CamelFolder *folder;
928         CamelService *service;
929         CamelSettings *settings;
930         CamelPOP3Folder *pop3_folder;
931
932         service = CAMEL_SERVICE (parent);
933
934         d(printf("opening pop3 INBOX folder\n"));
935
936         folder = g_object_new (
937                 CAMEL_TYPE_POP3_FOLDER,
938                 "full-name", "inbox", "display-name", "inbox",
939                 "parent-store", parent, NULL);
940
941         settings = camel_service_ref_settings (service);
942
943         pop3_folder = (CamelPOP3Folder *) folder;
944         pop3_folder->mobile_mode = camel_pop3_settings_get_mobile_mode (
945                 CAMEL_POP3_SETTINGS (settings));
946
947         g_object_unref (settings);
948
949         pop3_folder->fetch_more = 0;
950         if (pop3_folder->mobile_mode) {
951                 /* Setup Keyfile */
952                 gchar *path;
953                 const gchar *root;
954
955                 pop3_folder->key_file = g_key_file_new ();
956                 root = camel_service_get_user_cache_dir (service);
957                 path = g_build_filename (root, "uidconfig", NULL);
958                 g_key_file_load_from_file (pop3_folder->key_file, path, G_KEY_FILE_NONE, NULL);
959
960                 g_free (path);
961         }
962
963         if (camel_service_get_connection_status (CAMEL_SERVICE (parent)) != CAMEL_SERVICE_CONNECTED)
964                 return folder;
965
966         /* mt-ok, since we dont have the folder-lock for new() */
967         if (!camel_folder_refresh_info_sync (folder, cancellable, error)) {
968                 g_object_unref (folder);
969                 folder = NULL;
970         }
971
972         return folder;
973 }
974
975 static gboolean
976 pop3_get_message_time_from_cache (CamelFolder *folder,
977                                   const gchar *uid,
978                                   time_t *message_time)
979 {
980         CamelStore *parent_store;
981         CamelPOP3Store *pop3_store;
982         CamelStream *stream = NULL;
983         gchar buffer[1];
984         gboolean res = FALSE;
985
986         g_return_val_if_fail (folder != NULL, FALSE);
987         g_return_val_if_fail (uid != NULL, FALSE);
988         g_return_val_if_fail (message_time != NULL, FALSE);
989
990         parent_store = camel_folder_get_parent_store (folder);
991         pop3_store = CAMEL_POP3_STORE (parent_store);
992
993         g_return_val_if_fail (pop3_store->cache != NULL, FALSE);
994
995         if ((stream = camel_data_cache_get (pop3_store->cache, "cache", uid, NULL)) != NULL
996             && camel_stream_read (stream, buffer, 1, NULL, NULL) == 1
997             && buffer[0] == '#') {
998                 CamelMimeMessage *message;
999                 GError *error = NULL;
1000
1001                 message = camel_mime_message_new ();
1002                 camel_data_wrapper_construct_from_stream_sync (
1003                         (CamelDataWrapper *) message, stream, NULL, &error);
1004                 if (error != NULL) {
1005                         g_warning (_("Cannot get message %s: %s"), uid, error->message);
1006                         g_error_free (error);
1007
1008                         g_object_unref (message);
1009                         message = NULL;
1010                 }
1011
1012                 if (message) {
1013                         res = TRUE;
1014                         *message_time = message->date + message->date_offset;
1015
1016                         g_object_unref (message);
1017                 }
1018         }
1019
1020         if (stream) {
1021                 g_object_unref (stream);
1022         }
1023         return res;
1024 }
1025
1026 gboolean
1027 camel_pop3_delete_old (CamelFolder *folder,
1028                        gint days_to_delete,
1029                        GCancellable *cancellable,
1030                        GError **error)
1031 {
1032         CamelStore *parent_store;
1033         CamelPOP3Folder *pop3_folder;
1034         CamelPOP3FolderInfo *fi;
1035         gint i;
1036         CamelPOP3Store *pop3_store;
1037         CamelMimeMessage *message;
1038         time_t temp, message_time;
1039
1040         parent_store = camel_folder_get_parent_store (folder);
1041
1042         if (camel_service_get_connection_status (CAMEL_SERVICE (parent_store)) != CAMEL_SERVICE_CONNECTED) {
1043                 g_set_error (
1044                         error, CAMEL_SERVICE_ERROR,
1045                         CAMEL_SERVICE_ERROR_UNAVAILABLE,
1046                         _("You must be working online to complete this operation"));
1047                 return FALSE;
1048         }
1049
1050         pop3_folder = CAMEL_POP3_FOLDER (folder);
1051         pop3_store = CAMEL_POP3_STORE (parent_store);
1052         temp = time (&temp);
1053
1054         d(printf("%s(%d): pop3_folder->uids->len=[%d]\n", __FILE__, __LINE__, pop3_folder->uids->len));
1055         for (i = 0; i < pop3_folder->uids->len; i++) {
1056                 message_time = 0;
1057                 fi = pop3_folder->uids->pdata[i];
1058
1059                 if (fi->cmd) {
1060                         while (camel_pop3_engine_iterate (pop3_store->engine, fi->cmd, cancellable, NULL) > 0) {
1061                                 ; /* do nothing - iterating until end */
1062                         }
1063
1064                         camel_pop3_engine_command_free (pop3_store->engine, fi->cmd);
1065                         fi->cmd = NULL;
1066                 }
1067
1068                 /* continue, if message wasn't received yet */
1069                 if (!fi->uid)
1070                         continue;
1071
1072                 d(printf("%s(%d): fi->uid=[%s]\n", __FILE__, __LINE__, fi->uid));
1073                 if (!pop3_get_message_time_from_cache (folder, fi->uid, &message_time)) {
1074                         d(printf("could not get message time from cache, trying from pop3\n"));
1075                         message = pop3_folder_get_message_sync (
1076                                 folder, fi->uid, cancellable, error);
1077                         if (message) {
1078                                 message_time = message->date + message->date_offset;
1079                                 g_object_unref (message);
1080                         }
1081                 }
1082
1083                 if (message_time) {
1084                         gdouble time_diff = difftime (temp,message_time);
1085                         gint day_lag = time_diff / (60 * 60 * 24);
1086
1087                         d(printf("%s(%d): message_time= [%ld]\n", __FILE__, __LINE__, message_time));
1088                         d(printf("%s(%d): day_lag=[%d] \t days_to_delete=[%d]\n",
1089                                 __FILE__, __LINE__, day_lag, days_to_delete));
1090
1091                         if (day_lag > days_to_delete) {
1092                                 if (fi->cmd) {
1093                                         while (camel_pop3_engine_iterate (pop3_store->engine, fi->cmd, cancellable, NULL) > 0) {
1094                                                 ; /* do nothing - iterating until end */
1095                                         }
1096
1097                                         camel_pop3_engine_command_free (pop3_store->engine, fi->cmd);
1098                                         fi->cmd = NULL;
1099                                 }
1100                                 d(printf("%s(%d): Deleting old messages\n", __FILE__, __LINE__));
1101                                 fi->cmd = camel_pop3_engine_command_new (pop3_store->engine,
1102                                                                         0,
1103                                                                         NULL,
1104                                                                         NULL,
1105                                                                         cancellable, NULL,
1106                                                                         "DELE %u\r\n",
1107                                                                         fi->id);
1108                                 /* also remove from cache */
1109                                 if (pop3_store->cache && fi->uid) {
1110                                         camel_data_cache_remove(pop3_store->cache, "cache", fi->uid, NULL);
1111                                 }
1112                         }
1113                 }
1114         }
1115
1116         for (i = 0; i < pop3_folder->uids->len; i++) {
1117                 fi = pop3_folder->uids->pdata[i];
1118                 /* wait for delete commands to finish */
1119                 if (fi->cmd) {
1120                         while (camel_pop3_engine_iterate (pop3_store->engine, fi->cmd, cancellable, NULL) > 0)
1121                                 ;
1122                         camel_pop3_engine_command_free (pop3_store->engine, fi->cmd);
1123                         fi->cmd = NULL;
1124                 }
1125                 camel_operation_progress (
1126                         cancellable, (i + 1) * 100 / pop3_folder->uids->len);
1127         }
1128
1129         return camel_pop3_store_expunge (pop3_store, cancellable, error);
1130 }