1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-nntp-folder.c : Abstract class for an email folder */
5 * Author : Chris Toshok <toshok@helixcode.com>
7 * Copyright (C) 2000 Helix Code .
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as
11 * published by the Free Software Foundation; either version 2 of the
12 * License, or (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
29 #include <sys/types.h>
37 #include "camel-folder-summary.h"
38 #include "camel-nntp-store.h"
39 #include "camel-nntp-folder.h"
40 #include "camel-nntp-store.h"
41 #include "camel-nntp-utils.h"
43 #include "string-utils.h"
44 #include "camel-stream-mem.h"
45 #include "camel-stream-buffer.h"
46 #include "camel-data-wrapper.h"
47 #include "camel-mime-message.h"
48 #include "camel-folder-summary.h"
50 #include "camel-exception.h"
52 static CamelFolderClass *parent_class=NULL;
54 /* Returns the class for a CamelNNTPFolder */
55 #define CNNTPF_CLASS(so) CAMEL_NNTP_FOLDER_CLASS (GTK_OBJECT(so)->klass)
56 #define CF_CLASS(so) CAMEL_FOLDER_CLASS (GTK_OBJECT(so)->klass)
57 #define CNNTPS_CLASS(so) CAMEL_STORE_CLASS (GTK_OBJECT(so)->klass)
60 static void _init (CamelFolder *folder, CamelStore *parent_store,
61 CamelFolder *parent_folder, const gchar *name,
62 gchar separator, CamelException *ex);
64 static void _open (CamelFolder *folder, CamelFolderOpenMode mode, CamelException *ex);
65 static void _close (CamelFolder *folder, gboolean expunge, CamelException *ex);
66 static gboolean _exists (CamelFolder *folder, CamelException *ex);
67 static gboolean _create(CamelFolder *folder, CamelException *ex);
68 static gboolean _delete (CamelFolder *folder, gboolean recurse, CamelException *ex);
69 static gboolean _delete_messages (CamelFolder *folder, CamelException *ex);
70 static GList *_list_subfolders (CamelFolder *folder, CamelException *ex);
71 static CamelMimeMessage *_get_message_by_number (CamelFolder *folder, gint number, CamelException *ex);
72 static gint _get_message_count (CamelFolder *folder, CamelException *ex);
73 static void _append_message (CamelFolder *folder, CamelMimeMessage *message, CamelException *ex);
74 static GPtrArray *_get_uid_array (CamelFolder *folder, CamelException *ex);
75 static CamelMimeMessage *_get_message_by_uid (CamelFolder *folder, const gchar *uid, CamelException *ex);
77 static void _expunge (CamelFolder *folder, CamelException *ex);
78 static void _copy_message_to (CamelFolder *folder, CamelMimeMessage *message, CamelFolder *dest_folder, CamelException *ex);
80 static const gchar *_get_message_uid (CamelFolder *folder, CamelMimeMessage *message, CamelException *ex);
82 static void _finalize (GtkObject *object);
85 camel_nntp_folder_class_init (CamelNNTPFolderClass *camel_nntp_folder_class)
87 CamelFolderClass *camel_folder_class = CAMEL_FOLDER_CLASS (camel_nntp_folder_class);
88 GtkObjectClass *gtk_object_class = GTK_OBJECT_CLASS (camel_folder_class);
90 parent_class = gtk_type_class (camel_folder_get_type ());
92 /* virtual method definition */
94 /* virtual method overload */
95 camel_folder_class->init = _init;
96 camel_folder_class->open = _open;
97 camel_folder_class->close = _close;
98 camel_folder_class->exists = _exists;
99 camel_folder_class->create = _create;
100 camel_folder_class->delete = _delete;
101 camel_folder_class->delete_messages = _delete_messages;
102 camel_folder_class->list_subfolders = _list_subfolders;
103 camel_folder_class->get_message_count = _get_message_count;
104 camel_folder_class->get_uid_array = _get_uid_array;
105 camel_folder_class->get_message_by_uid = _get_message_by_uid;
107 camel_folder_class->append_message = _append_message;
108 camel_folder_class->expunge = _expunge;
109 camel_folder_class->copy_message_to = _copy_message_to;
110 camel_folder_class->get_message_uid = _get_message_uid;
113 gtk_object_class->finalize = _finalize;
118 _finalize (GtkObject *object)
120 CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (object);
122 g_free (nntp_folder->summary_file_path);
124 GTK_OBJECT_CLASS (parent_class)->finalize (object);
128 camel_nntp_folder_get_type (void)
130 static GtkType camel_nntp_folder_type = 0;
132 if (!camel_nntp_folder_type) {
133 GtkTypeInfo camel_nntp_folder_info =
136 sizeof (CamelNNTPFolder),
137 sizeof (CamelNNTPFolderClass),
138 (GtkClassInitFunc) camel_nntp_folder_class_init,
139 (GtkObjectInitFunc) NULL,
140 /* reserved_1 */ NULL,
141 /* reserved_2 */ NULL,
142 (GtkClassInitFunc) NULL,
145 camel_nntp_folder_type = gtk_type_unique (CAMEL_FOLDER_TYPE, &camel_nntp_folder_info);
148 return camel_nntp_folder_type;
152 _init (CamelFolder *folder, CamelStore *parent_store,
153 CamelFolder *parent_folder, const gchar *name, gchar separator,
156 /* call parent method */
157 parent_class->init (folder, parent_store, parent_folder,
158 name, separator, ex);
159 if (camel_exception_get_id (ex)) return;
161 /* we assume that the parent init
162 method checks for the existance of @folder */
164 if (!strcmp(name, "/"))
166 folder->has_summary_capability = FALSE;
167 folder->can_hold_messages = FALSE;
168 folder->can_hold_folders = TRUE;
172 folder->has_summary_capability = TRUE;
173 folder->can_hold_messages = TRUE;
174 folder->can_hold_folders = TRUE;
177 folder->has_uid_capability = TRUE;
178 folder->has_search_capability = FALSE;
181 /* internal method used to :
182 - test for the existence of a summary file
183 - test the sync between the summary and the newsgroup
184 - load the summary or create it if necessary
187 _check_get_or_maybe_generate_summary_file (CamelNNTPFolder *nntp_folder,
190 CamelFolder *folder = CAMEL_FOLDER (nntp_folder);
192 nntp_folder->summary = camel_folder_summary_new ();
193 camel_folder_summary_set_filename (nntp_folder->summary, nntp_folder->summary_file_path);
195 if (-1 == camel_folder_summary_load (nntp_folder->summary)) {
196 /* Bad or nonexistant summary file */
197 camel_nntp_get_headers (CAMEL_FOLDER( folder )->parent_store, nntp_folder, ex);
198 if (camel_exception_get_id (ex))
201 /* XXX check return value */
202 camel_folder_summary_save (nntp_folder->summary);
208 _open (CamelFolder *folder, CamelFolderOpenMode mode, CamelException *ex)
210 CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (folder);
211 const gchar *root_dir_path;
213 /* call parent class */
214 parent_class->open (folder, mode, ex);
215 if (camel_exception_get_id(ex))
219 /* get (or create) uid list */
220 if (!(nntp_load_uid_list (nntp_folder) > 0))
221 nntp_generate_uid_list (nntp_folder);
224 root_dir_path = camel_nntp_store_get_toplevel_dir (CAMEL_NNTP_STORE(folder->parent_store));
226 nntp_folder->summary_file_path = g_strdup_printf ("%s/%s-ev-summary", root_dir_path, folder->name);
228 _check_get_or_maybe_generate_summary_file (nntp_folder, ex);
233 _close (CamelFolder *folder, gboolean expunge, CamelException *ex)
235 CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (folder);
236 CamelFolderSummary *summary = nntp_folder->summary;
238 /* call parent implementation */
239 parent_class->close (folder, expunge, ex);
241 /* XXX only if dirty? */
242 camel_folder_summary_save (summary);
246 _exists (CamelFolder *folder, CamelException *ex)
249 CamelNNTPFolder *nntp_folder;
250 struct stat stat_buf;
254 g_assert(folder != NULL);
256 nntp_folder = CAMEL_NNTP_FOLDER (folder);
258 /* check if the nntp summary path is determined */
259 if (!nntp_folder->summary_file_path) {
260 camel_exception_set (ex,
261 CAMEL_EXCEPTION_FOLDER_INVALID,
262 "undetermined folder summary path. Maybe use set_name ?");
266 /* check if the nntp file exists */
267 stat_error = stat (nntp_folder->summary_file_path, &stat_buf);
268 if (stat_error == -1)
271 exists = S_ISREG (stat_buf.st_mode);
272 /* we should check the rights here */
280 _create (CamelFolder *folder, CamelException *ex)
283 CamelNNTPSummary *summary;
284 g_assert(folder != NULL);
286 /* call default implementation */
287 parent_class->create (folder, ex);
289 /* create the summary object */
290 summary = CAMEL_NNTP_SUMMARY (gtk_object_new (camel_nntp_summary_get_type (), NULL));
291 summary->nb_message = 0;
292 summary->next_uid = 1;
293 summary->nntp_file_size = 0;
294 summary->message_info = g_array_new (FALSE, FALSE, sizeof (CamelNNTPSummaryInformation));
301 _delete (CamelFolder *folder, gboolean recurse, CamelException *ex)
304 gboolean folder_already_exists;
306 g_assert(folder != NULL);
308 /* check if the folder object exists */
310 /* in the case where the folder does not exist,
312 folder_already_exists = camel_folder_exists (folder, ex);
313 if (camel_exception_get_id (ex))
316 if (!folder_already_exists)
320 /* call default implementation.
321 It should delete the messages in the folder
322 and recurse the operation to subfolders */
323 parent_class->delete (folder, recurse, ex);
329 _delete_messages (CamelFolder *folder, CamelException *ex)
332 gboolean folder_already_exists;
334 g_assert(folder!=NULL);
336 /* in the case where the folder does not exist,
338 folder_already_exists = camel_folder_exists (folder, ex);
339 if (camel_exception_get_id (ex)) return FALSE;
341 if (!folder_already_exists) return TRUE;
347 _list_subfolders (CamelFolder *folder, CamelException *ex)
349 /* newsgroups don't have subfolders */
354 _get_message_count (CamelFolder *folder, CamelException *ex)
356 CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER(folder);
359 g_assert (nntp_folder->summary);
361 return camel_folder_summary_count(nntp_folder->summary);
366 _append_message (CamelFolder *folder, CamelMimeMessage *message, CamelException *ex)
368 CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (folder);
370 CamelNNTPSummary *summary = CAMEL_NNTP_SUMMARY (folder->summary);
372 CamelStream *output_stream;
373 guint32 tmp_file_size;
376 GArray *message_info_array;
378 GArray *nntp_summary_info;
380 gchar *tmp_message_filename;
384 /* write the message itself */
385 output_stream = camel_stream_fs_new_with_name (tmp_message_filename,
386 CAMEL_STREAM_FS_WRITE);
387 if (output_stream != NULL) {
388 camel_stream_write_string (output_stream, "From - \n");
389 camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), output_stream);
391 camel_stream_flush (output_stream);
392 gtk_object_unref (GTK_OBJECT (output_stream));
394 /* at this point we have saved the message to a
395 temporary file, now, we have to add the x-evolution
396 field and also update the main summary */
399 First : parse the nntp file, but only from the
400 position where the message has been added,
401 wich happens to be the last postion in the
402 nntp file before we added the message.
403 This position is still stored in the summary
406 next_uid = summary->next_uid;
407 tmp_file_fd = open (tmp_message_filename, O_RDONLY);
409 camel_nntp_parse_file (tmp_file_fd, "From - ", 0,
410 &tmp_file_size, &next_uid, TRUE,
415 /* get the value of the last available UID
416 as saved in the summary file, again */
417 next_uid = summary->next_uid;
419 /* make sure all our of message info's have 0 uid - ignore any
421 for (i=0;i<message_info_array->len;i++) {
422 g_array_index(message_info_array, CamelNNTPParserMessageInfo, i).uid = 0;
426 OK, this is not very efficient, we should not use the same
427 method as for parsing an entire mail file,
428 but I have no time to write a simpler parser
431 next_uid = camel_nntp_write_xev (nntp_folder, tmp_message_filename,
432 message_info_array, &tmp_file_size, next_uid, ex);
435 if (camel_exception_get_id (ex)) {
436 /* ** FIXME : free the preparsed information */
442 parsed_information_to_nntp_summary (message_info_array);
445 /* store the number of messages as well as the summary array */
446 summary->nb_message += 1;
447 summary->next_uid = next_uid;
449 ((CamelNNTPSummaryInformation *)(nntp_summary_info->data))->position +=
450 summary->nntp_file_size;
451 summary->nntp_file_size += tmp_file_size;
453 camel_nntp_summary_append_entries (summary, nntp_summary_info);
454 g_array_free (nntp_summary_info, TRUE);
457 /* append the temporary file message to the nntp file */
458 fd1 = open (tmp_message_filename, O_RDONLY);
459 fd2 = open (nntp_folder->folder_file_path,
460 O_WRONLY | O_CREAT | O_APPEND,
464 camel_exception_setv (ex,
465 CAMEL_EXCEPTION_FOLDER_INSUFFICIENT_PERMISSION,
466 "could not open the nntp folder file for appending the message\n"
468 "Full error is : %s\n",
469 nntp_folder->folder_file_path,
474 camel_nntp_copy_file_chunk (fd1,
481 /* remove the temporary file */
482 unlink (tmp_message_filename);
484 g_free (tmp_message_filename);
491 _get_uid_array (CamelFolder *folder, CamelException *ex)
493 CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (folder);
494 GPtrArray *message_info_array, *out;
495 CamelMessageInfo *message_info;
498 message_info_array = nntp_folder->summary->messages;
500 out = g_ptr_array_new ();
501 g_ptr_array_set_size (out, message_info_array->len);
503 for (i=0; i<message_info_array->len; i++) {
504 message_info = (CamelMessageInfo *)(message_info_array->pdata) + i;
505 out->pdata[i] = g_strdup (message_info->uid);
511 static CamelMimeMessage *
512 _get_message_by_uid (CamelFolder *folder, const gchar *uid, CamelException *ex)
514 CamelStream *nntp_istream;
515 CamelStream *message_stream;
516 CamelMimeMessage *message = NULL;
517 CamelStore *parent_store;
524 /* get the parent store */
525 parent_store = camel_folder_get_parent_store (folder, ex);
526 if (camel_exception_get_id (ex)) {
530 status = camel_nntp_command (CAMEL_NNTP_STORE( parent_store ), NULL, "ARTICLE %s", uid);
532 nntp_istream = CAMEL_NNTP_STORE (parent_store)->istream;
534 /* if the uid was not found, raise an exception and return */
535 if (status != CAMEL_NNTP_OK) {
536 camel_exception_setv (ex,
537 CAMEL_EXCEPTION_FOLDER_INVALID_UID,
538 "message %s not found.",
543 /* XXX ick ick ick. read the entire message into a buffer and
544 then create a stream_mem for it. */
547 buf = malloc(buf_alloc);
553 char *line = camel_stream_buffer_read_line ( CAMEL_STREAM_BUFFER ( nntp_istream ), ex);
556 /* XXX check exception */
558 line_length = strlen ( line );
560 if (!strcmp(line, ".")) {
565 if (buf_len + line_length > buf_alloc) {
567 buf = realloc (buf, buf_alloc);
571 buf_len += strlen(line) + 1;
576 /* create a stream bound to the message */
577 message_stream = camel_stream_mem_new_with_buffer(buf, buf_len);
579 message = camel_mime_message_new ();
580 if (camel_data_wrapper_construct_from_stream ((CamelDataWrapper *)message, message_stream) == -1) {
581 gtk_object_unref ((GtkObject *)message);
582 gtk_object_unref ((GtkObject *)message_stream);
583 camel_exception_setv (ex,
584 CAMEL_EXCEPTION_FOLDER_INVALID_UID, /* XXX */
585 "Could not create message for uid %s.", uid);
589 gtk_object_unref ((GtkObject *)message_stream);
591 /* init other fields? */
592 message->folder = folder;
593 gtk_object_ref((GtkObject *)folder);
594 message->message_uid = g_strdup(uid);
597 gtk_signal_connect((GtkObject *)message, "message_changed", message_changed, folder);
603 /* get message info for a range of messages */
605 summary_get_message_info (CamelFolder *folder, int first, int count)
607 GPtrArray *array = g_ptr_array_new();
609 CamelNNTPFolder *nntp_folder = (CamelNNTPFolder *)folder;
611 maxcount = camel_folder_summary_count(nntp_folder->summary);
612 maxcount = MIN(first + count, maxcount);
613 for (i=first;i<maxcount;i++)
614 g_ptr_array_add(array, g_ptr_array_index(nntp_folder->summary->messages, i));