1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
3 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
5 * Authors: Michael Zucchi <notzed@ximian.com>
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.
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.
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.
33 #include <glib/gi18n-lib.h>
35 #include "camel-nntp-folder.h"
36 #include "camel-nntp-store.h"
37 #include "camel-nntp-stream.h"
38 #include "camel-nntp-summary.h"
42 #define d(x) /*(printf ("%s (%d): ", __FILE__, __LINE__),(x))*/
43 #define dd(x) (camel_debug ("nntp")?(x):0)
45 #define CAMEL_NNTP_SUMMARY_VERSION (1)
47 #define CAMEL_NNTP_SUMMARY_GET_PRIVATE(obj) \
48 (G_TYPE_INSTANCE_GET_PRIVATE \
49 ((obj), CAMEL_TYPE_NNTP_SUMMARY, CamelNNTPSummaryPrivate))
51 struct _CamelNNTPSummaryPrivate {
54 struct _xover_header *xover; /* xoverview format */
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);
62 G_DEFINE_TYPE (CamelNNTPSummary, camel_nntp_summary, CAMEL_TYPE_FOLDER_SUMMARY)
65 camel_nntp_summary_class_init (CamelNNTPSummaryClass *class)
67 CamelFolderSummaryClass *folder_summary_class;
69 g_type_class_add_private (class, sizeof (CamelNNTPSummaryPrivate));
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;
80 camel_nntp_summary_init (CamelNNTPSummary *nntp_summary)
82 CamelFolderSummary *summary = CAMEL_FOLDER_SUMMARY (nntp_summary);
84 nntp_summary->priv = CAMEL_NNTP_SUMMARY_GET_PRIVATE (nntp_summary);
86 /* and a unique file version */
87 summary->version += CAMEL_NNTP_SUMMARY_VERSION;
91 camel_nntp_summary_new (CamelFolder *folder)
93 CamelNNTPSummary *cns;
95 cns = g_object_new (CAMEL_TYPE_NNTP_SUMMARY, "folder", folder, NULL);
97 camel_folder_summary_set_build_content ((CamelFolderSummary *) cns, FALSE);
102 static CamelMessageInfo *
103 message_info_new_from_header (CamelFolderSummary *s,
104 struct _camel_header_raw *h)
106 CamelMessageInfoBase *mi;
107 CamelNNTPSummary *cns = (CamelNNTPSummary *) s;
109 /* error to call without this setup */
110 if (cns->priv->uid == NULL)
113 mi = (CamelMessageInfoBase *) CAMEL_FOLDER_SUMMARY_CLASS (camel_nntp_summary_parent_class)->message_info_new_from_header (s, h);
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;
121 return (CamelMessageInfo *) mi;
125 summary_header_from_db (CamelFolderSummary *s,
128 CamelNNTPSummary *cns = CAMEL_NNTP_SUMMARY (s);
131 if (!CAMEL_FOLDER_SUMMARY_CLASS (camel_nntp_summary_parent_class)->summary_header_from_db (s, mir))
136 cns->version = bdata_extract_digit (&part);
137 cns->high = bdata_extract_digit (&part);
138 cns->low = bdata_extract_digit (&part);
143 static CamelFIRecord *
144 summary_header_to_db (CamelFolderSummary *s,
147 CamelNNTPSummary *cns = CAMEL_NNTP_SUMMARY (s);
148 struct _CamelFIRecord *fir;
150 fir = CAMEL_FOLDER_SUMMARY_CLASS (camel_nntp_summary_parent_class)->summary_header_to_db (s, error);
153 fir->bdata = g_strdup_printf ("%d %d %d", CAMEL_NNTP_SUMMARY_VERSION, cns->high, cns->low);
158 /* ********************************************************************** */
160 /* Note: This will be called from camel_nntp_command, so only use camel_nntp_raw_command */
162 add_range_xover (CamelNNTPSummary *cns,
163 CamelNNTPStore *store,
166 CamelFolderChangeInfo *changes,
167 GCancellable *cancellable,
170 CamelNetworkSettings *network_settings;
171 CamelSettings *settings;
172 CamelService *service;
173 CamelFolderSummary *s;
174 CamelMessageInfoBase *mi;
175 struct _camel_header_raw *headers = NULL;
180 guint n, count, total, size;
181 gboolean folder_filter_recent;
182 struct _xover_header *xover;
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;
188 service = CAMEL_SERVICE (store);
190 settings = camel_service_ref_settings (service);
192 network_settings = CAMEL_NETWORK_SETTINGS (settings);
193 host = camel_network_settings_dup_host (network_settings);
195 g_object_unref (settings);
197 camel_operation_push_message (
198 cancellable, _("%s: Scanning new messages"), host);
202 if ((store->capabilities & NNTP_CAPABILITY_OVER) != 0)
203 ret = camel_nntp_raw_command_auth (store, cancellable, error, &line, "over %r", low, high);
207 store->capabilities = store->capabilities & (~NNTP_CAPABILITY_OVER);
208 ret = camel_nntp_raw_command_auth (store, cancellable, error, &line, "xover %r", low, high);
212 camel_operation_pop_message (cancellable);
215 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
216 _("Unexpected server response from xover: %s"), line);
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);
225 n = strtoul (line, &tab, 10);
229 xover = store->xover;
231 for (; tab[0] && xover; xover = xover->next) {
233 tab = strchr (line, '\t');
237 tab = line + strlen (line);
239 /* do we care about this column? */
243 camel_header_raw_append (&headers, xover->name, line, -1);
244 switch (xover->type) {
248 cns->priv->uid = g_strdup_printf ("%u,%s", n, line);
251 size = strtoul (line, NULL, 10);
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)
262 /* truncated line? ignore? */
264 if (!camel_folder_summary_check_uid (s, cns->priv->uid)) {
265 mi = (CamelMessageInfoBase *)
266 camel_folder_summary_add_from_header (s, headers);
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));
277 if (cns->priv->uid) {
278 g_free (cns->priv->uid);
279 cns->priv->uid = NULL;
282 camel_header_raw_clear (&headers);
285 camel_operation_pop_message (cancellable);
290 /* Note: This will be called from camel_nntp_command, so only use camel_nntp_raw_command */
292 add_range_head (CamelNNTPSummary *cns,
293 CamelNNTPStore *store,
296 CamelFolderChangeInfo *changes,
297 GCancellable *cancellable,
300 CamelNetworkSettings *network_settings;
301 CamelSettings *settings;
302 CamelService *service;
303 CamelFolderSummary *s;
306 guint i, n, count, total;
307 CamelMessageInfo *mi;
310 gboolean folder_filter_recent;
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;
316 mp = camel_mime_parser_new ();
318 service = CAMEL_SERVICE (store);
320 settings = camel_service_ref_settings (service);
322 network_settings = CAMEL_NETWORK_SETTINGS (settings);
323 host = camel_network_settings_dup_host (network_settings);
325 g_object_unref (settings);
327 camel_operation_push_message (
328 cancellable, _("%s: Scanning new messages"), host);
333 total = high - low + 1;
334 for (i = low; i < high + 1; i++) {
335 camel_operation_progress (cancellable, (count * 100) / total);
337 ret = camel_nntp_raw_command_auth (store, cancellable, error, &line, "head %u", i);
338 /* unknown article, ignore */
343 else if (ret != 221) {
345 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
346 _("Unexpected server response from head: %s"),
351 n = strtoul (line, &line, 10);
353 g_warning ("retrieved message '%u' when i expected '%u'?\n", n, i);
355 /* FIXME: use camel-mime-utils.c function for parsing msgid? */
356 if ((msgid = strchr (line, '<')) && (line = strchr (msgid + 1, '>'))) {
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)
362 mi = camel_folder_summary_add_from_parser (s, mp);
363 while (camel_mime_parser_step (mp, NULL, NULL) != CAMEL_MIME_PARSER_STATE_EOF)
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));
373 if (cns->priv->uid) {
374 g_free (cns->priv->uid);
375 cns->priv->uid = NULL;
387 G_IO_ERROR_CANCELLED,
392 g_io_error_from_errno (errno),
393 _("Operation failed: %s"),
398 if (cns->priv->uid) {
399 g_free (cns->priv->uid);
400 cns->priv->uid = NULL;
404 camel_operation_pop_message (cancellable);
409 /* Assumes we have the stream */
410 /* Note: This will be called from camel_nntp_command, so only use camel_nntp_raw_command */
412 camel_nntp_summary_check (CamelNNTPSummary *cns,
413 CamelNNTPStore *store,
415 CamelFolderChangeInfo *changes,
416 GCancellable *cancellable,
419 CamelFolderSummary *s;
423 gchar *folder = NULL;
424 CamelNNTPStoreInfo *si;
425 CamelStore *parent_store;
427 const gchar *full_name;
429 s = (CamelFolderSummary *) cns;
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));
435 n = strtoul (line, &line, 10);
436 f = strtoul (line, &line, 10);
437 l = strtoul (line, &line, 10);
438 if (line[0] == ' ') {
442 tmp = strchr (folder, ' ');
445 tmp = g_alloca (strlen (folder) + 1);
446 strcpy (tmp, folder);
450 if (cns->low == f && cns->high == l) {
451 dd (printf ("nntp_summary: no work to do!\n"));
455 /* Need to work out what to do with our messages */
457 /* Check for messages no longer on the server */
459 GPtrArray *known_uids;
461 known_uids = camel_folder_summary_get_array (s);
463 for (i = 0; i < known_uids->len; i++) {
467 uid = g_ptr_array_index (known_uids, i);
468 n = strtoul (uid, NULL, 10);
470 if (n < f || n > l) {
471 CamelMessageInfo *mi;
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, ',');
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));
483 mi = camel_folder_summary_peek_loaded (s, uid);
485 camel_folder_summary_remove (s, mi);
486 camel_message_info_free (mi);
488 camel_folder_summary_remove_uid (s, uid);
492 camel_folder_summary_free_array (known_uids);
497 camel_db_delete_uids (parent_store->cdb_w, full_name, del, NULL);
498 g_list_foreach (del, (GFunc) camel_pstring_free, NULL);
506 ret = add_range_xover (
507 cns, store, l, cns->high + 1,
508 changes, cancellable, error);
510 ret = add_range_head (
511 cns, store, l, cns->high + 1,
512 changes, cancellable, error);
515 /* TODO: not from here */
516 camel_folder_summary_touch (s);
517 camel_folder_summary_save_to_db (s, NULL);
520 /* update store summary if we have it */
522 && (si = (CamelNNTPStoreInfo *) camel_store_summary_path ((CamelStoreSummary *) store->summary, folder))) {
525 count = camel_folder_summary_count (s);
526 camel_db_count_unread_message_info (parent_store->cdb_r, full_name, &unread, NULL);
528 if (si->info.unread != unread
529 || si->info.total != count
532 si->info.unread = unread;
533 si->info.total = count;
536 camel_store_summary_touch ((CamelStoreSummary *) store->summary);
537 camel_store_summary_save ((CamelStoreSummary *) store->summary);
539 camel_store_summary_info_free ((CamelStoreSummary *) store->summary, (CamelStoreInfo *) si);
542 g_warning ("Group '%s' not present in summary", folder);
544 g_warning ("Missing group from group response");