Rename camel_service_get_settings().
[platform/upstream/evolution-data-server.git] / camel / providers / nntp / camel-nntp-summary.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
2 /*
3  *  Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
4  *
5  *  Authors: Michael Zucchi <notzed@ximian.com>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of version 2 of the GNU Lesser General Public
9  * License as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <ctype.h>
27 #include <errno.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <sys/stat.h>
32
33 #include <glib/gi18n-lib.h>
34
35 #include "camel-nntp-folder.h"
36 #include "camel-nntp-store.h"
37 #include "camel-nntp-stream.h"
38 #include "camel-nntp-summary.h"
39
40 #define w(x)
41 #define io(x)
42 #define d(x) /*(printf ("%s (%d): ", __FILE__, __LINE__),(x))*/
43 #define dd(x) (camel_debug ("nntp")?(x):0)
44
45 #define CAMEL_NNTP_SUMMARY_VERSION (1)
46
47 #define CAMEL_NNTP_SUMMARY_GET_PRIVATE(obj) \
48         (G_TYPE_INSTANCE_GET_PRIVATE \
49         ((obj), CAMEL_TYPE_NNTP_SUMMARY, CamelNNTPSummaryPrivate))
50
51 struct _CamelNNTPSummaryPrivate {
52         gchar *uid;
53
54         struct _xover_header *xover; /* xoverview format */
55         gint xover_setup;
56 };
57
58 static CamelMessageInfo * message_info_new_from_header (CamelFolderSummary *, struct _camel_header_raw *);
59 static gboolean summary_header_from_db (CamelFolderSummary *s, CamelFIRecord *mir);
60 static CamelFIRecord * summary_header_to_db (CamelFolderSummary *s, GError **error);
61
62 G_DEFINE_TYPE (CamelNNTPSummary, camel_nntp_summary, CAMEL_TYPE_FOLDER_SUMMARY)
63
64 static void
65 camel_nntp_summary_class_init (CamelNNTPSummaryClass *class)
66 {
67         CamelFolderSummaryClass *folder_summary_class;
68
69         g_type_class_add_private (class, sizeof (CamelNNTPSummaryPrivate));
70
71         folder_summary_class = CAMEL_FOLDER_SUMMARY_CLASS (class);
72         folder_summary_class->message_info_size = sizeof (CamelMessageInfoBase);
73         folder_summary_class->content_info_size = sizeof (CamelMessageContentInfo);
74         folder_summary_class->message_info_new_from_header = message_info_new_from_header;
75         folder_summary_class->summary_header_from_db = summary_header_from_db;
76         folder_summary_class->summary_header_to_db = summary_header_to_db;
77 }
78
79 static void
80 camel_nntp_summary_init (CamelNNTPSummary *nntp_summary)
81 {
82         CamelFolderSummary *summary = CAMEL_FOLDER_SUMMARY (nntp_summary);
83
84         nntp_summary->priv = CAMEL_NNTP_SUMMARY_GET_PRIVATE (nntp_summary);
85
86         /* and a unique file version */
87         summary->version += CAMEL_NNTP_SUMMARY_VERSION;
88 }
89
90 CamelNNTPSummary *
91 camel_nntp_summary_new (CamelFolder *folder)
92 {
93         CamelNNTPSummary *cns;
94
95         cns = g_object_new (CAMEL_TYPE_NNTP_SUMMARY, "folder", folder, NULL);
96
97         camel_folder_summary_set_build_content ((CamelFolderSummary *) cns, FALSE);
98
99         return cns;
100 }
101
102 static CamelMessageInfo *
103 message_info_new_from_header (CamelFolderSummary *s,
104                               struct _camel_header_raw *h)
105 {
106         CamelMessageInfoBase *mi;
107         CamelNNTPSummary *cns = (CamelNNTPSummary *) s;
108
109         /* error to call without this setup */
110         if (cns->priv->uid == NULL)
111                 return NULL;
112
113         mi = (CamelMessageInfoBase *) CAMEL_FOLDER_SUMMARY_CLASS (camel_nntp_summary_parent_class)->message_info_new_from_header (s, h);
114         if (mi) {
115                 camel_pstring_free (mi->uid);
116                 mi->uid = camel_pstring_strdup (cns->priv->uid);
117                 g_free (cns->priv->uid);
118                 cns->priv->uid = NULL;
119         }
120
121         return (CamelMessageInfo *) mi;
122 }
123
124 static gboolean
125 summary_header_from_db (CamelFolderSummary *s,
126                         CamelFIRecord *mir)
127 {
128         CamelNNTPSummary *cns = CAMEL_NNTP_SUMMARY (s);
129         gchar *part;
130
131         if (!CAMEL_FOLDER_SUMMARY_CLASS (camel_nntp_summary_parent_class)->summary_header_from_db (s, mir))
132                 return FALSE;
133
134         part = mir->bdata;
135
136         cns->version = bdata_extract_digit (&part);
137         cns->high = bdata_extract_digit (&part);
138         cns->low = bdata_extract_digit (&part);
139
140         return TRUE;
141 }
142
143 static CamelFIRecord *
144 summary_header_to_db (CamelFolderSummary *s,
145                       GError **error)
146 {
147         CamelNNTPSummary *cns = CAMEL_NNTP_SUMMARY (s);
148         struct _CamelFIRecord *fir;
149
150         fir = CAMEL_FOLDER_SUMMARY_CLASS (camel_nntp_summary_parent_class)->summary_header_to_db (s, error);
151         if (!fir)
152                 return NULL;
153         fir->bdata = g_strdup_printf ("%d %d %d", CAMEL_NNTP_SUMMARY_VERSION, cns->high, cns->low);
154
155         return fir;
156 }
157
158 /* ********************************************************************** */
159
160 /* Note: This will be called from camel_nntp_command, so only use camel_nntp_raw_command */
161 static gint
162 add_range_xover (CamelNNTPSummary *cns,
163                  CamelNNTPStore *store,
164                  guint high,
165                  guint low,
166                  CamelFolderChangeInfo *changes,
167                  GCancellable *cancellable,
168                  GError **error)
169 {
170         CamelNetworkSettings *network_settings;
171         CamelSettings *settings;
172         CamelService *service;
173         CamelFolderSummary *s;
174         CamelMessageInfoBase *mi;
175         struct _camel_header_raw *headers = NULL;
176         gchar *line, *tab;
177         gchar *host;
178         guint len;
179         gint ret;
180         guint n, count, total, size;
181         gboolean folder_filter_recent;
182         struct _xover_header *xover;
183
184         s = (CamelFolderSummary *) cns;
185         folder_filter_recent = camel_folder_summary_get_folder (s) &&
186                 (camel_folder_summary_get_folder (s)->folder_flags & CAMEL_FOLDER_FILTER_RECENT) != 0;
187
188         service = CAMEL_SERVICE (store);
189
190         settings = camel_service_ref_settings (service);
191
192         network_settings = CAMEL_NETWORK_SETTINGS (settings);
193         host = camel_network_settings_dup_host (network_settings);
194
195         g_object_unref (settings);
196
197         camel_operation_push_message (
198                 cancellable, _("%s: Scanning new messages"), host);
199
200         g_free (host);
201
202         if ((store->capabilities & NNTP_CAPABILITY_OVER) != 0)
203                 ret = camel_nntp_raw_command_auth (store, cancellable, error, &line, "over %r", low, high);
204         else
205                 ret = -1;
206         if (ret != 224) {
207                 store->capabilities = store->capabilities & (~NNTP_CAPABILITY_OVER);
208                 ret = camel_nntp_raw_command_auth (store, cancellable, error, &line, "xover %r", low, high);
209         }
210
211         if (ret != 224) {
212                 camel_operation_pop_message (cancellable);
213                 if (ret != -1)
214                         g_set_error (
215                                 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
216                                 _("Unexpected server response from xover: %s"), line);
217                 return -1;
218         }
219
220         count = 0;
221         total = high - low + 1;
222         while ((ret = camel_nntp_stream_line (store->stream, (guchar **) &line, &len, cancellable, error)) > 0) {
223                 camel_operation_progress (cancellable, (count * 100) / total);
224                 count++;
225                 n = strtoul (line, &tab, 10);
226                 if (*tab != '\t')
227                         continue;
228                 tab++;
229                 xover = store->xover;
230                 size = 0;
231                 for (; tab[0] && xover; xover = xover->next) {
232                         line = tab;
233                         tab = strchr (line, '\t');
234                         if (tab)
235                                 *tab++ = 0;
236                         else
237                                 tab = line + strlen (line);
238
239                         /* do we care about this column? */
240                         if (xover->name) {
241                                 line += xover->skip;
242                                 if (line < tab) {
243                                         camel_header_raw_append (&headers, xover->name, line, -1);
244                                         switch (xover->type) {
245                                         case XOVER_STRING:
246                                                 break;
247                                         case XOVER_MSGID:
248                                                 cns->priv->uid = g_strdup_printf ("%u,%s", n, line);
249                                                 break;
250                                         case XOVER_SIZE:
251                                                 size = strtoul (line, NULL, 10);
252                                                 break;
253                                         }
254                                 }
255                         }
256                 }
257
258                 /* skip headers we don't care about, incase the server doesn't actually send some it said it would. */
259                 while (xover && xover->name == NULL)
260                         xover = xover->next;
261
262                 /* truncated line? ignore? */
263                 if (xover == NULL) {
264                         if (!camel_folder_summary_check_uid (s, cns->priv->uid)) {
265                                 mi = (CamelMessageInfoBase *)
266                                         camel_folder_summary_add_from_header (s, headers);
267                                 if (mi) {
268                                         mi->size = size;
269                                         cns->high = n;
270                                         camel_folder_change_info_add_uid (changes, camel_message_info_uid (mi));
271                                         if (folder_filter_recent)
272                                                 camel_folder_change_info_recent_uid (changes, camel_message_info_uid (mi));
273                                 }
274                         }
275                 }
276
277                 if (cns->priv->uid) {
278                         g_free (cns->priv->uid);
279                         cns->priv->uid = NULL;
280                 }
281
282                 camel_header_raw_clear (&headers);
283         }
284
285         camel_operation_pop_message (cancellable);
286
287         return ret;
288 }
289
290 /* Note: This will be called from camel_nntp_command, so only use camel_nntp_raw_command */
291 static gint
292 add_range_head (CamelNNTPSummary *cns,
293                 CamelNNTPStore *store,
294                 guint high,
295                 guint low,
296                 CamelFolderChangeInfo *changes,
297                 GCancellable *cancellable,
298                 GError **error)
299 {
300         CamelNetworkSettings *network_settings;
301         CamelSettings *settings;
302         CamelService *service;
303         CamelFolderSummary *s;
304         gint ret = -1;
305         gchar *line, *msgid;
306         guint i, n, count, total;
307         CamelMessageInfo *mi;
308         CamelMimeParser *mp;
309         gchar *host;
310         gboolean folder_filter_recent;
311
312         s = (CamelFolderSummary *) cns;
313         folder_filter_recent = camel_folder_summary_get_folder (s) &&
314                 (camel_folder_summary_get_folder (s)->folder_flags & CAMEL_FOLDER_FILTER_RECENT) != 0;
315
316         mp = camel_mime_parser_new ();
317
318         service = CAMEL_SERVICE (store);
319
320         settings = camel_service_ref_settings (service);
321
322         network_settings = CAMEL_NETWORK_SETTINGS (settings);
323         host = camel_network_settings_dup_host (network_settings);
324
325         g_object_unref (settings);
326
327         camel_operation_push_message (
328                 cancellable, _("%s: Scanning new messages"), host);
329
330         g_free (host);
331
332         count = 0;
333         total = high - low + 1;
334         for (i = low; i < high + 1; i++) {
335                 camel_operation_progress (cancellable, (count * 100) / total);
336                 count++;
337                 ret = camel_nntp_raw_command_auth (store, cancellable, error, &line, "head %u", i);
338                 /* unknown article, ignore */
339                 if (ret == 423)
340                         continue;
341                 else if (ret == -1)
342                         goto ioerror;
343                 else if (ret != 221) {
344                         g_set_error (
345                                 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
346                                 _("Unexpected server response from head: %s"),
347                                 line);
348                         goto ioerror;
349                 }
350                 line += 3;
351                 n = strtoul (line, &line, 10);
352                 if (n != i)
353                         g_warning ("retrieved message '%u' when i expected '%u'?\n", n, i);
354
355                 /* FIXME: use camel-mime-utils.c function for parsing msgid? */
356                 if ((msgid = strchr (line, '<')) && (line = strchr (msgid + 1, '>'))) {
357                         line[1] = 0;
358                         cns->priv->uid = g_strdup_printf ("%u,%s\n", n, msgid);
359                         if (!camel_folder_summary_check_uid (s, cns->priv->uid)) {
360                                 if (camel_mime_parser_init_with_stream (mp, (CamelStream *) store->stream, error) == -1)
361                                         goto error;
362                                 mi = camel_folder_summary_add_from_parser (s, mp);
363                                 while (camel_mime_parser_step (mp, NULL, NULL) != CAMEL_MIME_PARSER_STATE_EOF)
364                                         ;
365                                 if (mi == NULL) {
366                                         goto error;
367                                 }
368                                 cns->high = i;
369                                 camel_folder_change_info_add_uid (changes, camel_message_info_uid (mi));
370                                 if (folder_filter_recent)
371                                         camel_folder_change_info_recent_uid (changes, camel_message_info_uid (mi));
372                         }
373                         if (cns->priv->uid) {
374                                 g_free (cns->priv->uid);
375                                 cns->priv->uid = NULL;
376                         }
377                 }
378         }
379
380         ret = 0;
381
382 error:
383         if (ret == -1) {
384                 if (errno == EINTR)
385                         g_set_error (
386                                 error, G_IO_ERROR,
387                                 G_IO_ERROR_CANCELLED,
388                                 _("Cancelled"));
389                 else
390                         g_set_error (
391                                 error, G_IO_ERROR,
392                                 g_io_error_from_errno (errno),
393                                 _("Operation failed: %s"),
394                                 g_strerror (errno));
395         }
396
397 ioerror:
398         if (cns->priv->uid) {
399                 g_free (cns->priv->uid);
400                 cns->priv->uid = NULL;
401         }
402         g_object_unref (mp);
403
404         camel_operation_pop_message (cancellable);
405
406         return ret;
407 }
408
409 /* Assumes we have the stream */
410 /* Note: This will be called from camel_nntp_command, so only use camel_nntp_raw_command */
411 gint
412 camel_nntp_summary_check (CamelNNTPSummary *cns,
413                           CamelNNTPStore *store,
414                           gchar *line,
415                           CamelFolderChangeInfo *changes,
416                           GCancellable *cancellable,
417                           GError **error)
418 {
419         CamelFolderSummary *s;
420         gint ret = 0, i;
421         guint n, f, l;
422         gint count;
423         gchar *folder = NULL;
424         CamelNNTPStoreInfo *si;
425         CamelStore *parent_store;
426         GList *del = NULL;
427         const gchar *full_name;
428
429         s = (CamelFolderSummary *) cns;
430
431         full_name = camel_folder_get_full_name (camel_folder_summary_get_folder (s));
432         parent_store = camel_folder_get_parent_store (camel_folder_summary_get_folder (s));
433
434         line +=3;
435         n = strtoul (line, &line, 10);
436         f = strtoul (line, &line, 10);
437         l = strtoul (line, &line, 10);
438         if (line[0] == ' ') {
439                 gchar *tmp;
440
441                 folder = line + 1;
442                 tmp = strchr (folder, ' ');
443                 if (tmp)
444                         *tmp = 0;
445                 tmp = g_alloca (strlen (folder) + 1);
446                 strcpy (tmp, folder);
447                 folder = tmp;
448         }
449
450         if (cns->low == f && cns->high == l) {
451                 dd (printf ("nntp_summary: no work to do!\n"));
452                 goto update;
453         }
454
455         /* Need to work out what to do with our messages */
456
457         /* Check for messages no longer on the server */
458         if (cns->low != f) {
459                 GPtrArray *known_uids;
460
461                 known_uids = camel_folder_summary_get_array (s);
462                 if (known_uids) {
463                         for (i = 0; i < known_uids->len; i++) {
464                                 const gchar *uid;
465                                 const gchar *msgid;
466
467                                 uid  = g_ptr_array_index (known_uids, i);
468                                 n = strtoul (uid, NULL, 10);
469
470                                 if (n < f || n > l) {
471                                         CamelMessageInfo *mi;
472
473                                         dd (printf ("nntp_summary: %u is lower/higher than lowest/highest article, removed\n", n));
474                                         /* Since we use a global cache this could prematurely remove
475                                          * a cached message that might be in another folder - not that important as
476                                          * it is a true cache */
477                                         msgid = strchr (uid, ',');
478                                         if (msgid)
479                                                 camel_data_cache_remove (store->cache, "cache", msgid + 1, NULL);
480                                         camel_folder_change_info_remove_uid (changes, uid);
481                                         del = g_list_prepend (del, (gpointer) camel_pstring_strdup (uid));
482
483                                         mi = camel_folder_summary_peek_loaded (s, uid);
484                                         if (mi) {
485                                                 camel_folder_summary_remove (s, mi);
486                                                 camel_message_info_free (mi);
487                                         } else {
488                                                 camel_folder_summary_remove_uid (s, uid);
489                                         }
490                                 }
491                         }
492                         camel_folder_summary_free_array (known_uids);
493                 }
494                 cns->low = f;
495         }
496
497         camel_db_delete_uids (parent_store->cdb_w, full_name, del, NULL);
498         g_list_foreach (del, (GFunc) camel_pstring_free, NULL);
499         g_list_free (del);
500
501         if (cns->high < l) {
502                 if (cns->high < f)
503                         cns->high = f - 1;
504
505                 if (store->xover)
506                         ret = add_range_xover (
507                                 cns, store, l, cns->high + 1,
508                                 changes, cancellable, error);
509                 else
510                         ret = add_range_head (
511                                 cns, store, l, cns->high + 1,
512                                 changes, cancellable, error);
513         }
514
515         /* TODO: not from here */
516         camel_folder_summary_touch (s);
517         camel_folder_summary_save_to_db (s, NULL);
518
519 update:
520         /* update store summary if we have it */
521         if (folder
522             && (si = (CamelNNTPStoreInfo *) camel_store_summary_path ((CamelStoreSummary *) store->summary, folder))) {
523                 guint32 unread = 0;
524
525                 count = camel_folder_summary_count (s);
526                 camel_db_count_unread_message_info (parent_store->cdb_r, full_name, &unread, NULL);
527
528                 if (si->info.unread != unread
529                     || si->info.total != count
530                     || si->first != f
531                     || si->last != l) {
532                         si->info.unread = unread;
533                         si->info.total = count;
534                         si->first = f;
535                         si->last = l;
536                         camel_store_summary_touch ((CamelStoreSummary *) store->summary);
537                         camel_store_summary_save ((CamelStoreSummary *) store->summary);
538                 }
539                 camel_store_summary_info_free ((CamelStoreSummary *) store->summary, (CamelStoreInfo *) si);
540         } else {
541                 if (folder)
542                         g_warning ("Group '%s' not present in summary", folder);
543                 else
544                         g_warning ("Missing group from group response");
545         }
546
547         return ret;
548 }