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) 2001-2003 Ximian, Inc. (www.ximian.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
32 #include <sys/types.h>
34 #include <glib/gi18n-lib.h>
36 #include <libedataserver/e-data-server-util.h>
38 #include "camel/camel-data-cache.h"
39 #include "camel/camel-data-wrapper.h"
40 #include "camel/camel-exception.h"
41 #include "camel/camel-file-utils.h"
42 #include "camel/camel-folder-search.h"
43 #include "camel/camel-mime-filter-crlf.h"
44 #include "camel/camel-mime-message.h"
45 #include "camel/camel-mime-message.h"
46 #include "camel/camel-mime-part.h"
47 #include "camel/camel-multipart.h"
48 #include "camel/camel-private.h"
49 #include "camel/camel-session.h"
50 #include "camel/camel-stream-buffer.h"
51 #include "camel/camel-stream-filter.h"
52 #include "camel/camel-stream-mem.h"
54 #include "camel-nntp-folder.h"
55 #include "camel-nntp-private.h"
56 #include "camel-nntp-store.h"
57 #include "camel-nntp-store.h"
58 #include "camel-nntp-summary.h"
60 static CamelFolderClass *folder_class = NULL;
61 static CamelDiscoFolderClass *parent_class = NULL;
63 /* Returns the class for a CamelNNTPFolder */
64 #define CNNTPF_CLASS(so) CAMEL_NNTP_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
65 #define CF_CLASS(so) CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
66 #define CNNTPS_CLASS(so) CAMEL_STORE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
69 camel_nntp_folder_selected(CamelNNTPFolder *folder, char *line, CamelException *ex)
71 camel_nntp_summary_check((CamelNNTPSummary *)((CamelFolder *)folder)->summary,
72 (CamelNNTPStore *)((CamelFolder *)folder)->parent_store,
73 line, folder->changes, ex);
77 nntp_folder_refresh_info_online (CamelFolder *folder, CamelException *ex)
79 CamelNNTPStore *nntp_store;
80 CamelFolderChangeInfo *changes = NULL;
81 CamelNNTPFolder *nntp_folder;
84 nntp_store = (CamelNNTPStore *) folder->parent_store;
85 nntp_folder = (CamelNNTPFolder *) folder;
87 CAMEL_SERVICE_REC_LOCK(nntp_store, connect_lock);
89 camel_nntp_command(nntp_store, ex, nntp_folder, &line, NULL);
91 if (camel_folder_change_info_changed(nntp_folder->changes)) {
92 changes = nntp_folder->changes;
93 nntp_folder->changes = camel_folder_change_info_new();
96 CAMEL_SERVICE_REC_UNLOCK(nntp_store, connect_lock);
99 camel_object_trigger_event ((CamelObject *) folder, "folder_changed", changes);
100 camel_folder_change_info_free (changes);
105 nntp_folder_sync_online (CamelFolder *folder, CamelException *ex)
107 CAMEL_SERVICE_REC_LOCK(folder->parent_store, connect_lock);
108 camel_folder_summary_save (folder->summary);
109 CAMEL_SERVICE_REC_UNLOCK(folder->parent_store, connect_lock);
113 nntp_folder_sync_offline (CamelFolder *folder, CamelException *ex)
115 CAMEL_SERVICE_REC_LOCK(folder->parent_store, connect_lock);
116 camel_folder_summary_save (folder->summary);
117 CAMEL_SERVICE_REC_UNLOCK(folder->parent_store, connect_lock);
121 nntp_folder_set_message_flags (CamelFolder *folder, const char *uid, guint32 flags, guint32 set)
123 return ((CamelFolderClass *) folder_class)->set_message_flags (folder, uid, flags, set);
127 nntp_folder_download_message (CamelNNTPFolder *nntp_folder, const char *id, const char *msgid, CamelException *ex)
129 CamelNNTPStore *nntp_store = (CamelNNTPStore *) ((CamelFolder *) nntp_folder)->parent_store;
130 CamelStream *stream = NULL;
134 ret = camel_nntp_command (nntp_store, ex, nntp_folder, &line, "article %s", id);
136 stream = camel_data_cache_add (nntp_store->cache, "cache", msgid, NULL);
138 if (camel_stream_write_to_stream ((CamelStream *) nntp_store->stream, stream) == -1)
140 if (camel_stream_reset (stream) == -1)
143 stream = (CamelStream *) nntp_store->stream;
144 camel_object_ref (stream);
146 } else if (ret == 423 || ret == 430) {
147 camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID_UID, _("Cannot get message %s: %s"), msgid, line);
148 } else if (ret != -1) {
149 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot get message %s: %s"), msgid, line);
156 camel_exception_setv (ex, CAMEL_EXCEPTION_USER_CANCEL, _("User canceled"));
158 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot get message %s: %s"), msgid, g_strerror (errno));
165 nntp_folder_cache_message (CamelDiscoFolder *disco_folder, const char *uid, CamelException *ex)
167 CamelNNTPStore *nntp_store = (CamelNNTPStore *)((CamelFolder *) disco_folder)->parent_store;
169 char *article, *msgid;
171 article = alloca(strlen(uid)+1);
172 strcpy(article, uid);
173 msgid = strchr(article, ',');
175 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
176 _("Internal error: UID in invalid format: %s"), uid);
181 CAMEL_SERVICE_REC_LOCK(nntp_store, connect_lock);
183 stream = nntp_folder_download_message ((CamelNNTPFolder *) disco_folder, article, msgid, ex);
185 camel_object_unref (stream);
187 CAMEL_SERVICE_REC_UNLOCK(nntp_store, connect_lock);
190 static CamelMimeMessage *
191 nntp_folder_get_message (CamelFolder *folder, const char *uid, CamelException *ex)
193 CamelMimeMessage *message = NULL;
194 CamelNNTPStore *nntp_store;
195 CamelFolderChangeInfo *changes;
196 CamelNNTPFolder *nntp_folder;
197 CamelStream *stream = NULL;
198 char *article, *msgid;
200 nntp_store = (CamelNNTPStore *) folder->parent_store;
201 nntp_folder = (CamelNNTPFolder *) folder;
203 article = alloca(strlen(uid)+1);
204 strcpy(article, uid);
205 msgid = strchr (article, ',');
207 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
208 _("Internal error: UID in invalid format: %s"), uid);
213 CAMEL_SERVICE_REC_LOCK(nntp_store, connect_lock);
215 /* Lookup in cache, NEWS is global messageid's so use a global cache path */
216 stream = camel_data_cache_get (nntp_store->cache, "cache", msgid, NULL);
217 if (stream == NULL) {
218 if (camel_disco_store_status ((CamelDiscoStore *) nntp_store) == CAMEL_DISCO_STORE_OFFLINE) {
219 camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
220 _("This message is not currently available"));
224 stream = nntp_folder_download_message (nntp_folder, article, msgid, ex);
229 message = camel_mime_message_new ();
230 if (camel_data_wrapper_construct_from_stream ((CamelDataWrapper *) message, stream) == -1) {
232 camel_exception_setv (ex, CAMEL_EXCEPTION_USER_CANCEL, _("User canceled"));
234 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot get message %s: %s"), uid, g_strerror (errno));
235 camel_object_unref(message);
239 camel_object_unref (stream);
241 if (camel_folder_change_info_changed (nntp_folder->changes)) {
242 changes = nntp_folder->changes;
243 nntp_folder->changes = camel_folder_change_info_new ();
248 CAMEL_SERVICE_REC_UNLOCK(nntp_store, connect_lock);
251 camel_object_trigger_event ((CamelObject *) folder, "folder_changed", changes);
252 camel_folder_change_info_free (changes);
259 nntp_folder_search_by_expression (CamelFolder *folder, const char *expression, CamelException *ex)
261 CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (folder);
264 CAMEL_NNTP_FOLDER_LOCK(nntp_folder, search_lock);
266 if (nntp_folder->search == NULL)
267 nntp_folder->search = camel_folder_search_new ();
269 camel_folder_search_set_folder (nntp_folder->search, folder);
270 matches = camel_folder_search_search(nntp_folder->search, expression, NULL, ex);
272 CAMEL_NNTP_FOLDER_UNLOCK(nntp_folder, search_lock);
278 nntp_folder_search_by_uids (CamelFolder *folder, const char *expression, GPtrArray *uids, CamelException *ex)
280 CamelNNTPFolder *nntp_folder = (CamelNNTPFolder *) folder;
284 return g_ptr_array_new();
286 CAMEL_NNTP_FOLDER_LOCK(folder, search_lock);
288 if (nntp_folder->search == NULL)
289 nntp_folder->search = camel_folder_search_new ();
291 camel_folder_search_set_folder (nntp_folder->search, folder);
292 matches = camel_folder_search_search(nntp_folder->search, expression, uids, ex);
294 CAMEL_NNTP_FOLDER_UNLOCK(folder, search_lock);
300 nntp_folder_search_free (CamelFolder *folder, GPtrArray *result)
302 CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (folder);
304 camel_folder_search_free_result (nntp_folder->search, result);
308 nntp_folder_append_message_online (CamelFolder *folder, CamelMimeMessage *mime_message,
309 const CamelMessageInfo *info, char **appended_uid,
312 CamelNNTPStore *nntp_store = (CamelNNTPStore *) folder->parent_store;
313 CamelStream *stream = (CamelStream*)nntp_store->stream;
314 CamelStreamFilter *filtered_stream;
315 CamelMimeFilter *crlffilter;
318 struct _camel_header_raw *header, *savedhdrs, *n, *tail;
321 CAMEL_SERVICE_REC_LOCK(nntp_store, connect_lock);
323 /* send 'POST' command */
324 ret = camel_nntp_command (nntp_store, ex, NULL, &line, "post");
327 camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INSUFFICIENT_PERMISSION,
328 _("Posting failed: %s"), line);
330 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
331 _("Posting failed: %s"), line);
332 CAMEL_SERVICE_REC_UNLOCK(nntp_store, connect_lock);
336 /* the 'Newsgroups: ' header */
337 group = g_strdup_printf ("Newsgroups: %s\r\n", folder->full_name);
339 /* setup stream filtering */
340 crlffilter = camel_mime_filter_crlf_new (CAMEL_MIME_FILTER_CRLF_ENCODE, CAMEL_MIME_FILTER_CRLF_MODE_CRLF_DOTS);
341 filtered_stream = camel_stream_filter_new_with_stream (stream);
342 camel_stream_filter_add (filtered_stream, crlffilter);
343 camel_object_unref (crlffilter);
345 /* remove mail 'To', 'CC', and 'BCC' headers */
347 tail = (struct _camel_header_raw *) &savedhdrs;
349 header = (struct _camel_header_raw *) &CAMEL_MIME_PART (mime_message)->headers;
352 if (!g_ascii_strcasecmp (n->name, "To") || !g_ascii_strcasecmp (n->name, "Cc") || !g_ascii_strcasecmp (n->name, "Bcc")) {
353 header->next = n->next;
364 /* write the message */
365 if (camel_stream_write(stream, group, strlen(group)) == -1
366 || camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (mime_message), CAMEL_STREAM (filtered_stream)) == -1
367 || camel_stream_flush (CAMEL_STREAM (filtered_stream)) == -1
368 || camel_stream_write (stream, "\r\n.\r\n", 5) == -1
369 || (ret = camel_nntp_stream_line (nntp_store->stream, (unsigned char **)&line, &u)) == -1) {
371 camel_exception_setv (ex, CAMEL_EXCEPTION_USER_CANCEL, _("User canceled"));
373 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Posting failed: %s"), g_strerror (errno));
374 } else if (atoi(line) != 240) {
375 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Posting failed: %s"), line);
378 camel_object_unref (filtered_stream);
380 header->next = savedhdrs;
382 CAMEL_SERVICE_REC_UNLOCK(nntp_store, connect_lock);
388 nntp_folder_append_message_offline (CamelFolder *folder, CamelMimeMessage *mime_message,
389 const CamelMessageInfo *info, char **appended_uid,
392 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
393 _("You cannot post NNTP messages while working offline!"));
396 /* I do not know what to do this exactly. Looking at the IMAP implementation for this, it
397 seems to assume the message is copied to a folder on the same store. In that case, an
398 NNTP implementation doesn't seem to make any sense. */
400 nntp_folder_transfer_message (CamelFolder *source, GPtrArray *uids, CamelFolder *dest,
401 GPtrArray **transferred_uids, gboolean delete_orig, CamelException *ex)
403 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
404 _("You cannot copy messages from a NNTP folder!"));
408 nntp_folder_init (CamelNNTPFolder *nntp_folder, CamelNNTPFolderClass *klass)
410 struct _CamelNNTPFolderPrivate *p;
412 nntp_folder->changes = camel_folder_change_info_new ();
413 p = nntp_folder->priv = g_malloc0 (sizeof (*nntp_folder->priv));
414 p->search_lock = g_mutex_new ();
415 p->cache_lock = g_mutex_new ();
419 nntp_folder_finalise (CamelNNTPFolder *nntp_folder)
421 struct _CamelNNTPFolderPrivate *p;
423 camel_folder_summary_save (((CamelFolder*) nntp_folder)->summary);
425 p = nntp_folder->priv;
426 g_mutex_free (p->search_lock);
427 g_mutex_free (p->cache_lock);
432 nntp_folder_class_init (CamelNNTPFolderClass *camel_nntp_folder_class)
434 CamelDiscoFolderClass *camel_disco_folder_class = CAMEL_DISCO_FOLDER_CLASS (camel_nntp_folder_class);
435 CamelFolderClass *camel_folder_class = CAMEL_FOLDER_CLASS (camel_nntp_folder_class);
437 parent_class = CAMEL_DISCO_FOLDER_CLASS (camel_type_get_global_classfuncs (camel_disco_folder_get_type ()));
438 folder_class = CAMEL_FOLDER_CLASS (camel_type_get_global_classfuncs (camel_folder_get_type ()));
440 /* virtual method definition */
442 /* virtual method overload */
443 camel_disco_folder_class->sync_online = nntp_folder_sync_online;
444 camel_disco_folder_class->sync_resyncing = nntp_folder_sync_offline;
445 camel_disco_folder_class->sync_offline = nntp_folder_sync_offline;
446 camel_disco_folder_class->cache_message = nntp_folder_cache_message;
447 camel_disco_folder_class->append_online = nntp_folder_append_message_online;
448 camel_disco_folder_class->append_resyncing = nntp_folder_append_message_online;
449 camel_disco_folder_class->append_offline = nntp_folder_append_message_offline;
450 camel_disco_folder_class->transfer_online = nntp_folder_transfer_message;
451 camel_disco_folder_class->transfer_resyncing = nntp_folder_transfer_message;
452 camel_disco_folder_class->transfer_offline = nntp_folder_transfer_message;
453 camel_disco_folder_class->refresh_info_online = nntp_folder_refresh_info_online;
455 camel_folder_class->set_message_flags = nntp_folder_set_message_flags;
456 camel_folder_class->get_message = nntp_folder_get_message;
457 camel_folder_class->search_by_expression = nntp_folder_search_by_expression;
458 camel_folder_class->search_by_uids = nntp_folder_search_by_uids;
459 camel_folder_class->search_free = nntp_folder_search_free;
463 camel_nntp_folder_get_type (void)
465 static CamelType camel_nntp_folder_type = CAMEL_INVALID_TYPE;
467 if (camel_nntp_folder_type == CAMEL_INVALID_TYPE) {
468 camel_nntp_folder_type = camel_type_register (CAMEL_DISCO_FOLDER_TYPE, "CamelNNTPFolder",
469 sizeof (CamelNNTPFolder),
470 sizeof (CamelNNTPFolderClass),
471 (CamelObjectClassInitFunc) nntp_folder_class_init,
473 (CamelObjectInitFunc) nntp_folder_init,
474 (CamelObjectFinalizeFunc) nntp_folder_finalise);
477 return camel_nntp_folder_type;
481 camel_nntp_folder_new (CamelStore *parent, const char *folder_name, CamelException *ex)
484 CamelNNTPFolder *nntp_folder;
486 CamelService *service;
488 gboolean subscribed = TRUE;
490 service = (CamelService *) parent;
491 root = camel_session_get_storage_path (service->session, service, ex);
495 /* If this doesn't work, stuff wont save, but let it continue anyway */
496 g_mkdir_with_parents (root, 0777);
498 folder = (CamelFolder *) camel_object_new (CAMEL_NNTP_FOLDER_TYPE);
499 nntp_folder = (CamelNNTPFolder *)folder;
501 camel_folder_construct (folder, parent, folder_name, folder_name);
502 folder->folder_flags |= CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY|CAMEL_FOLDER_HAS_SEARCH_CAPABILITY;
504 nntp_folder->storage_path = g_build_filename (root, folder->full_name, NULL);
507 root = g_strdup_printf ("%s.cmeta", nntp_folder->storage_path);
508 camel_object_set(nntp_folder, NULL, CAMEL_OBJECT_STATE_FILE, root, NULL);
509 camel_object_state_read(nntp_folder);
512 root = g_strdup_printf("%s.ev-summary", nntp_folder->storage_path);
513 folder->summary = (CamelFolderSummary *) camel_nntp_summary_new (folder, root);
515 camel_folder_summary_load (folder->summary);
517 si = camel_store_summary_path ((CamelStoreSummary *) ((CamelNNTPStore*) parent)->summary, folder_name);
519 subscribed = (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) != 0;
520 camel_store_summary_info_free ((CamelStoreSummary *) ((CamelNNTPStore*) parent)->summary, si);
524 camel_folder_refresh_info(folder, ex);
525 if (camel_exception_is_set(ex)) {
526 camel_object_unref (folder);