1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-nntp-folder.c : Class for a news folder
4 * Authors : Chris Toshok <toshok@ximian.com>
5 * Michael Zucchi <notzed@ximian.com>
7 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of version 2 of the GNU Lesser General Public
11 * License as published by the Free Software Foundation.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
31 #include <sys/types.h>
33 #include <glib/gi18n-lib.h>
35 #include "camel-nntp-folder.h"
36 #include "camel-nntp-private.h"
37 #include "camel-nntp-store.h"
38 #include "camel-nntp-summary.h"
40 /* The custom property ID is a CamelArg artifact.
41 * It still identifies the property in state files. */
44 PROP_APPLY_FILTERS = 0x2501
47 #define CAMEL_NNTP_FOLDER_GET_PRIVATE(obj) \
48 (G_TYPE_INSTANCE_GET_PRIVATE \
49 ((obj), CAMEL_TYPE_NNTP_FOLDER, CamelNNTPFolderPrivate))
51 G_DEFINE_TYPE (CamelNNTPFolder, camel_nntp_folder, CAMEL_TYPE_DISCO_FOLDER)
54 nntp_folder_get_apply_filters (CamelNNTPFolder *folder)
56 g_return_val_if_fail (folder != NULL, FALSE);
57 g_return_val_if_fail (CAMEL_IS_NNTP_FOLDER (folder), FALSE);
59 return folder->priv->apply_filters;
63 nntp_folder_set_apply_filters (CamelNNTPFolder *folder,
64 gboolean apply_filters)
66 g_return_if_fail (folder != NULL);
67 g_return_if_fail (CAMEL_IS_NNTP_FOLDER (folder));
69 if ((folder->priv->apply_filters ? 1 : 0) == (apply_filters ? 1 : 0))
72 folder->priv->apply_filters = apply_filters;
74 g_object_notify (G_OBJECT (folder), "apply-filters");
78 nntp_folder_set_property (GObject *object,
83 switch (property_id) {
84 case PROP_APPLY_FILTERS:
85 nntp_folder_set_apply_filters (
86 CAMEL_NNTP_FOLDER (object),
87 g_value_get_boolean (value));
91 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
95 nntp_folder_get_property (GObject *object,
100 switch (property_id) {
101 case PROP_APPLY_FILTERS:
102 g_value_set_boolean (
103 value, nntp_folder_get_apply_filters (
104 CAMEL_NNTP_FOLDER (object)));
108 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
112 nntp_folder_dispose (GObject *object)
114 CamelStore *parent_store;
115 CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (object);
117 camel_folder_summary_save_to_db (
118 CAMEL_FOLDER (nntp_folder)->summary, NULL);
120 parent_store = camel_folder_get_parent_store (CAMEL_FOLDER (nntp_folder));
122 camel_store_summary_disconnect_folder_summary (
123 (CamelStoreSummary *) ((CamelNNTPStore *) parent_store)->summary,
124 CAMEL_FOLDER (nntp_folder)->summary);
127 /* Chain up to parent's dispose() method. */
128 G_OBJECT_CLASS (camel_nntp_folder_parent_class)->dispose (object);
132 nntp_folder_finalize (GObject *object)
134 CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (object);
136 g_mutex_free (nntp_folder->priv->search_lock);
137 g_mutex_free (nntp_folder->priv->cache_lock);
139 /* Chain up to parent's finalize() method. */
140 G_OBJECT_CLASS (camel_nntp_folder_parent_class)->finalize (object);
144 camel_nntp_folder_selected (CamelNNTPFolder *nntp_folder,
146 GCancellable *cancellable,
150 CamelStore *parent_store;
153 folder = CAMEL_FOLDER (nntp_folder);
154 parent_store = camel_folder_get_parent_store (folder);
156 res = camel_nntp_summary_check (
157 CAMEL_NNTP_SUMMARY (folder->summary),
158 CAMEL_NNTP_STORE (parent_store),
159 line, nntp_folder->changes,
162 if (camel_folder_change_info_changed (nntp_folder->changes)) {
163 CamelFolderChangeInfo *changes;
165 changes = nntp_folder->changes;
166 nntp_folder->changes = camel_folder_change_info_new ();
168 camel_folder_changed (CAMEL_FOLDER (nntp_folder), changes);
169 camel_folder_change_info_free (changes);
176 nntp_folder_refresh_info_online (CamelFolder *folder,
177 GCancellable *cancellable,
180 CamelStore *parent_store;
181 CamelNNTPStore *nntp_store;
182 CamelFolderChangeInfo *changes = NULL;
183 CamelNNTPFolder *nntp_folder;
187 parent_store = camel_folder_get_parent_store (folder);
189 nntp_folder = CAMEL_NNTP_FOLDER (folder);
190 nntp_store = CAMEL_NNTP_STORE (parent_store);
192 /* When invoked with no fmt, camel_nntp_command() just selects the folder
193 * and should return zero. */
194 success = !camel_nntp_command (
195 nntp_store, cancellable, error, nntp_folder, &line, NULL);
197 if (camel_folder_change_info_changed (nntp_folder->changes)) {
198 changes = nntp_folder->changes;
199 nntp_folder->changes = camel_folder_change_info_new ();
203 camel_folder_changed (folder, changes);
204 camel_folder_change_info_free (changes);
211 unset_flagged_flag (const gchar *uid,
212 CamelFolderSummary *summary)
214 CamelMessageInfo *info;
216 info = camel_folder_summary_get (summary, uid);
218 CamelMessageInfoBase *base = (CamelMessageInfoBase *) info;
220 if ((base->flags & CAMEL_MESSAGE_FOLDER_FLAGGED) != 0) {
221 base->flags &= ~CAMEL_MESSAGE_FOLDER_FLAGGED;
225 camel_message_info_free (info);
230 nntp_folder_sync (CamelFolder *folder,
235 changed = camel_folder_summary_get_changed (folder->summary);
237 g_ptr_array_foreach (changed, (GFunc) unset_flagged_flag, folder->summary);
238 g_ptr_array_foreach (changed, (GFunc) camel_pstring_free, NULL);
239 g_ptr_array_free (changed, TRUE);
240 camel_folder_summary_touch (folder->summary);
243 return camel_folder_summary_save_to_db (folder->summary, error);
247 nntp_folder_sync_online (CamelFolder *folder,
250 return nntp_folder_sync (folder, error);
254 nntp_folder_sync_offline (CamelFolder *folder,
257 return nntp_folder_sync (folder, error);
261 nntp_get_filename (CamelFolder *folder,
265 CamelStore *parent_store;
266 CamelNNTPStore *nntp_store;
267 gchar *article, *msgid;
269 parent_store = camel_folder_get_parent_store (folder);
270 nntp_store = CAMEL_NNTP_STORE (parent_store);
272 article = alloca (strlen (uid) + 1);
273 strcpy (article, uid);
274 msgid = strchr (article, ',');
277 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
278 _("Internal error: UID in invalid format: %s"), uid);
283 return camel_data_cache_get_filename (nntp_store->cache, "cache", msgid);
287 nntp_folder_download_message (CamelNNTPFolder *nntp_folder,
290 GCancellable *cancellable,
294 CamelStore *parent_store;
295 CamelNNTPStore *nntp_store;
296 CamelStream *stream = NULL;
300 folder = CAMEL_FOLDER (nntp_folder);
301 parent_store = camel_folder_get_parent_store (folder);
302 nntp_store = CAMEL_NNTP_STORE (parent_store);
304 ret = camel_nntp_command (nntp_store, cancellable, error, nntp_folder, &line, "article %s", id);
306 stream = camel_data_cache_add (nntp_store->cache, "cache", msgid, NULL);
310 if (camel_stream_write_to_stream ((CamelStream *) nntp_store->stream, stream, cancellable, error) == -1)
313 if ((error && *error) || g_cancellable_set_error_if_cancelled (cancellable, error))
316 success = g_seekable_seek (
317 G_SEEKABLE (stream), 0,
318 G_SEEK_SET, cancellable, error);
322 stream = g_object_ref (nntp_store->stream);
324 } else if (ret == 423 || ret == 430) {
326 error, CAMEL_FOLDER_ERROR,
327 CAMEL_FOLDER_ERROR_INVALID_UID,
328 _("Cannot get message %s: %s"), msgid, line);
329 } else if (ret != -1) {
331 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
332 _("Cannot get message %s: %s"), msgid, line);
338 camel_data_cache_remove (nntp_store->cache, "cache", msgid, NULL);
339 g_prefix_error (error, _("Cannot get message %s: "), msgid);
345 nntp_folder_cache_message (CamelDiscoFolder *disco_folder,
347 GCancellable *cancellable,
351 gchar *article, *msgid;
352 gboolean success = TRUE;
354 article = alloca (strlen (uid) + 1);
355 strcpy (article, uid);
356 msgid = strchr (article, ',');
359 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
360 _("Internal error: UID in invalid format: %s"), uid);
365 stream = nntp_folder_download_message (
366 (CamelNNTPFolder *) disco_folder, article, msgid, cancellable, error);
368 g_object_unref (stream);
376 nntp_folder_search_by_expression (CamelFolder *folder,
377 const gchar *expression,
378 GCancellable *cancellable,
381 CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (folder);
384 CAMEL_NNTP_FOLDER_LOCK (nntp_folder, search_lock);
386 if (nntp_folder->search == NULL)
387 nntp_folder->search = camel_folder_search_new ();
389 camel_folder_search_set_folder (nntp_folder->search, folder);
390 matches = camel_folder_search_search (nntp_folder->search, expression, NULL, cancellable, error);
392 CAMEL_NNTP_FOLDER_UNLOCK (nntp_folder, search_lock);
398 nntp_folder_count_by_expression (CamelFolder *folder,
399 const gchar *expression,
400 GCancellable *cancellable,
403 CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (folder);
406 CAMEL_NNTP_FOLDER_LOCK (nntp_folder, search_lock);
408 if (nntp_folder->search == NULL)
409 nntp_folder->search = camel_folder_search_new ();
411 camel_folder_search_set_folder (nntp_folder->search, folder);
412 count = camel_folder_search_count (nntp_folder->search, expression, cancellable, error);
414 CAMEL_NNTP_FOLDER_UNLOCK (nntp_folder, search_lock);
420 nntp_folder_search_by_uids (CamelFolder *folder,
421 const gchar *expression,
423 GCancellable *cancellable,
426 CamelNNTPFolder *nntp_folder = (CamelNNTPFolder *) folder;
430 return g_ptr_array_new ();
432 CAMEL_NNTP_FOLDER_LOCK (folder, search_lock);
434 if (nntp_folder->search == NULL)
435 nntp_folder->search = camel_folder_search_new ();
437 camel_folder_search_set_folder (nntp_folder->search, folder);
438 matches = camel_folder_search_search (nntp_folder->search, expression, uids, cancellable, error);
440 CAMEL_NNTP_FOLDER_UNLOCK (folder, search_lock);
446 nntp_folder_search_free (CamelFolder *folder,
449 CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (folder);
451 CAMEL_NNTP_FOLDER_LOCK (nntp_folder, search_lock);
452 camel_folder_search_free_result (nntp_folder->search, result);
453 CAMEL_NNTP_FOLDER_UNLOCK (nntp_folder, search_lock);
456 static CamelMimeMessage *
457 nntp_folder_get_message_sync (CamelFolder *folder,
459 GCancellable *cancellable,
462 CamelStore *parent_store;
463 CamelMimeMessage *message = NULL;
464 CamelNNTPStore *nntp_store;
465 CamelFolderChangeInfo *changes;
466 CamelNNTPFolder *nntp_folder;
467 CamelStream *stream = NULL;
468 gchar *article, *msgid;
470 parent_store = camel_folder_get_parent_store (folder);
472 nntp_folder = CAMEL_NNTP_FOLDER (folder);
473 nntp_store = CAMEL_NNTP_STORE (parent_store);
475 article = alloca (strlen (uid) + 1);
476 strcpy (article, uid);
477 msgid = strchr (article, ',');
480 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
481 _("Internal error: UID in invalid format: %s"), uid);
486 /* Lookup in cache, NEWS is global messageid's so use a global cache path */
487 stream = camel_data_cache_get (nntp_store->cache, "cache", msgid, NULL);
488 if (stream == NULL) {
489 if (camel_disco_store_status ((CamelDiscoStore *) nntp_store) == CAMEL_DISCO_STORE_OFFLINE) {
491 error, CAMEL_SERVICE_ERROR,
492 CAMEL_SERVICE_ERROR_UNAVAILABLE,
493 _("This message is not currently available"));
497 stream = nntp_folder_download_message (nntp_folder, article, msgid, cancellable, error);
502 message = camel_mime_message_new ();
503 if (!camel_data_wrapper_construct_from_stream_sync ((CamelDataWrapper *) message, stream, cancellable, error)) {
504 g_prefix_error (error, _("Cannot get message %s: "), uid);
505 g_object_unref (message);
509 g_object_unref (stream);
511 if (camel_folder_change_info_changed (nntp_folder->changes)) {
512 changes = nntp_folder->changes;
513 nntp_folder->changes = camel_folder_change_info_new ();
519 camel_folder_changed (folder, changes);
520 camel_folder_change_info_free (changes);
527 nntp_folder_append_message_online (CamelFolder *folder,
528 CamelMimeMessage *mime_message,
529 const CamelMessageInfo *info,
530 gchar **appended_uid,
531 GCancellable *cancellable,
534 CamelStore *parent_store;
535 CamelNNTPStore *nntp_store;
536 CamelStream *filtered_stream;
538 CamelMimeFilter *crlffilter;
541 struct _camel_header_raw *header, *savedhdrs, *n, *tail;
542 const gchar *full_name;
544 gboolean success = TRUE;
546 full_name = camel_folder_get_full_name (folder);
547 parent_store = camel_folder_get_parent_store (folder);
549 nntp_store = CAMEL_NNTP_STORE (parent_store);
550 stream = CAMEL_STREAM (nntp_store->stream);
552 /* send 'POST' command */
553 ret = camel_nntp_command (nntp_store, cancellable, error, NULL, &line, "post");
557 error, CAMEL_FOLDER_ERROR,
558 CAMEL_FOLDER_ERROR_INSUFFICIENT_PERMISSION,
559 _("Posting failed: %s"), line);
561 } else if (ret != -1) {
565 _("Posting failed: %s"), line);
571 /* the 'Newsgroups: ' header */
572 group = g_strdup_printf ("Newsgroups: %s\r\n", full_name);
574 /* setup stream filtering */
575 crlffilter = camel_mime_filter_crlf_new (CAMEL_MIME_FILTER_CRLF_ENCODE, CAMEL_MIME_FILTER_CRLF_MODE_CRLF_DOTS);
576 filtered_stream = camel_stream_filter_new (stream);
577 camel_stream_filter_add (
578 CAMEL_STREAM_FILTER (filtered_stream), crlffilter);
579 g_object_unref (crlffilter);
581 /* remove mail 'To', 'CC', and 'BCC' headers */
583 tail = (struct _camel_header_raw *) &savedhdrs;
585 header = (struct _camel_header_raw *) &CAMEL_MIME_PART (mime_message)->headers;
588 if (!g_ascii_strcasecmp (n->name, "To") || !g_ascii_strcasecmp (n->name, "Cc") || !g_ascii_strcasecmp (n->name, "Bcc")) {
589 header->next = n->next;
600 /* write the message */
601 if (camel_stream_write (stream, group, strlen (group), cancellable, error) == -1
602 || camel_data_wrapper_write_to_stream_sync (CAMEL_DATA_WRAPPER (mime_message), filtered_stream, cancellable, error) == -1
603 || camel_stream_flush (filtered_stream, cancellable, error) == -1
604 || camel_stream_write (stream, "\r\n.\r\n", 5, cancellable, error) == -1
605 || camel_nntp_stream_line (nntp_store->stream, (guchar **) &line, &u, cancellable, error) == -1) {
606 g_prefix_error (error, _("Posting failed: "));
608 } else if (atoi (line) != 240) {
610 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
611 _("Posting failed: %s"), line);
615 g_object_unref (filtered_stream);
617 header->next = savedhdrs;
623 nntp_folder_append_message_offline (CamelFolder *folder,
624 CamelMimeMessage *mime_message,
625 const CamelMessageInfo *info,
626 gchar **appended_uid,
627 GCancellable *cancellable,
631 error, CAMEL_SERVICE_ERROR,
632 CAMEL_SERVICE_ERROR_UNAVAILABLE,
633 _("You cannot post NNTP messages while working offline!"));
638 /* I do not know what to do this exactly. Looking at the IMAP implementation for this, it
639 * seems to assume the message is copied to a folder on the same store. In that case, an
640 * NNTP implementation doesn't seem to make any sense. */
642 nntp_folder_transfer_message (CamelFolder *source,
645 GPtrArray **transferred_uids,
646 gboolean delete_orig,
647 GCancellable *cancellable,
651 error, CAMEL_SERVICE_ERROR,
652 CAMEL_SERVICE_ERROR_UNAVAILABLE,
653 _("You cannot copy messages from a NNTP folder!"));
659 nntp_folder_expunge_uids_offline (CamelFolder *folder,
663 CamelFolderChangeInfo *changes;
666 g_return_val_if_fail (folder != NULL, FALSE);
667 g_return_val_if_fail (CAMEL_IS_NNTP_FOLDER (folder), FALSE);
668 g_return_val_if_fail (uids != NULL, FALSE);
669 g_return_val_if_fail (folder->summary != NULL, FALSE);
671 /* can only remove deleted messages from a local cache */
673 changes = camel_folder_change_info_new ();
674 for (ii = 0; ii < uids->len; ii++) {
675 CamelMessageInfo *mi = camel_folder_summary_peek_loaded (folder->summary, uids->pdata[ii]);
677 camel_folder_summary_remove (folder->summary, mi);
678 camel_message_info_free (mi);
680 camel_folder_summary_remove_uid (folder->summary, uids->pdata[ii]);
683 camel_folder_change_info_remove_uid (changes, uids->pdata[ii]);
686 camel_folder_summary_save_to_db (folder->summary, NULL);
687 camel_folder_changed (folder, changes);
688 camel_folder_change_info_free (changes);
694 camel_nntp_folder_class_init (CamelNNTPFolderClass *class)
696 GObjectClass *object_class;
697 CamelFolderClass *folder_class;
698 CamelDiscoFolderClass *disco_folder_class;
700 g_type_class_add_private (class, sizeof (CamelNNTPFolderPrivate));
702 object_class = G_OBJECT_CLASS (class);
703 object_class->set_property = nntp_folder_set_property;
704 object_class->get_property = nntp_folder_get_property;
705 object_class->dispose = nntp_folder_dispose;
706 object_class->finalize = nntp_folder_finalize;
708 folder_class = CAMEL_FOLDER_CLASS (class);
709 folder_class->search_by_expression = nntp_folder_search_by_expression;
710 folder_class->count_by_expression = nntp_folder_count_by_expression;
711 folder_class->search_by_uids = nntp_folder_search_by_uids;
712 folder_class->search_free = nntp_folder_search_free;
713 folder_class->get_filename = nntp_get_filename;
714 folder_class->get_message_sync = nntp_folder_get_message_sync;
716 disco_folder_class = CAMEL_DISCO_FOLDER_CLASS (class);
717 disco_folder_class->sync_online = nntp_folder_sync_online;
718 disco_folder_class->sync_resyncing = nntp_folder_sync_offline;
719 disco_folder_class->sync_offline = nntp_folder_sync_offline;
720 disco_folder_class->cache_message = nntp_folder_cache_message;
721 disco_folder_class->append_online = nntp_folder_append_message_online;
722 disco_folder_class->append_resyncing = nntp_folder_append_message_online;
723 disco_folder_class->append_offline = nntp_folder_append_message_offline;
724 disco_folder_class->transfer_online = nntp_folder_transfer_message;
725 disco_folder_class->transfer_resyncing = nntp_folder_transfer_message;
726 disco_folder_class->transfer_offline = nntp_folder_transfer_message;
727 disco_folder_class->refresh_info_online = nntp_folder_refresh_info_online;
728 disco_folder_class->expunge_uids_online = nntp_folder_expunge_uids_offline;
729 disco_folder_class->expunge_uids_offline = nntp_folder_expunge_uids_offline;
730 disco_folder_class->expunge_uids_resyncing = nntp_folder_expunge_uids_offline;
732 g_object_class_install_property (
735 g_param_spec_boolean (
738 _("Apply message _filters to this folder"),
741 CAMEL_PARAM_PERSISTENT));
745 camel_nntp_folder_init (CamelNNTPFolder *nntp_folder)
747 nntp_folder->priv = CAMEL_NNTP_FOLDER_GET_PRIVATE (nntp_folder);
749 nntp_folder->changes = camel_folder_change_info_new ();
750 nntp_folder->priv->search_lock = g_mutex_new ();
751 nntp_folder->priv->cache_lock = g_mutex_new ();
755 camel_nntp_folder_new (CamelStore *parent,
756 const gchar *folder_name,
757 GCancellable *cancellable,
761 CamelNNTPFolder *nntp_folder;
763 CamelService *service;
764 CamelSettings *settings;
766 const gchar *user_cache_dir;
767 gboolean subscribed = TRUE;
770 service = CAMEL_SERVICE (parent);
771 user_cache_dir = camel_service_get_user_cache_dir (service);
773 settings = camel_service_ref_settings (service);
777 "filter-all", &filter_all,
780 g_object_unref (settings);
782 folder = g_object_new (
783 CAMEL_TYPE_NNTP_FOLDER,
784 "display-name", folder_name,
785 "full-name", folder_name,
786 "parent-store", parent, NULL);
787 nntp_folder = (CamelNNTPFolder *) folder;
789 folder->folder_flags |= CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY;
791 nntp_folder->storage_path =
792 g_build_filename (user_cache_dir, folder_name, NULL);
794 root = g_strdup_printf ("%s.cmeta", nntp_folder->storage_path);
795 camel_object_set_state_filename (CAMEL_OBJECT (nntp_folder), root);
796 camel_object_state_read (CAMEL_OBJECT (nntp_folder));
799 folder->summary = (CamelFolderSummary *) camel_nntp_summary_new (folder);
801 if (filter_all || nntp_folder_get_apply_filters (nntp_folder))
802 folder->folder_flags |= CAMEL_FOLDER_FILTER_RECENT;
804 camel_folder_summary_load_from_db (folder->summary, NULL);
806 si = camel_store_summary_path ((CamelStoreSummary *) ((CamelNNTPStore *) parent)->summary, folder_name);
808 subscribed = (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) != 0;
809 camel_store_summary_info_free ((CamelStoreSummary *) ((CamelNNTPStore *) parent)->summary, si);
812 camel_store_summary_connect_folder_summary (
813 (CamelStoreSummary *) ((CamelNNTPStore *) parent)->summary,
814 folder_name, folder->summary);
816 if (subscribed && !camel_folder_refresh_info_sync (
817 folder, cancellable, error)) {
818 g_object_unref (folder);