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