/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* camel-nntp-folder.c : Abstract class for an email folder */ /* * Author : Chris Toshok * * Copyright (C) 2000 Helix Code . * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA */ #include #include #include #include #include #include #include #include #include #include "camel-folder-summary.h" #include "camel-nntp-store.h" #include "camel-nntp-folder.h" #include "camel-nntp-store.h" #include "camel-nntp-utils.h" #include "string-utils.h" #include "camel-stream-mem.h" #include "camel-stream-buffer.h" #include "camel-data-wrapper.h" #include "camel-mime-message.h" #include "camel-folder-summary.h" #include "camel-exception.h" static CamelFolderClass *parent_class=NULL; /* Returns the class for a CamelNNTPFolder */ #define CNNTPF_CLASS(so) CAMEL_NNTP_FOLDER_CLASS (GTK_OBJECT(so)->klass) #define CF_CLASS(so) CAMEL_FOLDER_CLASS (GTK_OBJECT(so)->klass) #define CNNTPS_CLASS(so) CAMEL_STORE_CLASS (GTK_OBJECT(so)->klass) static void _init (CamelFolder *folder, CamelStore *parent_store, CamelFolder *parent_folder, const gchar *name, gchar separator, CamelException *ex); static void _open (CamelFolder *folder, CamelFolderOpenMode mode, CamelException *ex); static void _close (CamelFolder *folder, gboolean expunge, CamelException *ex); static gboolean _exists (CamelFolder *folder, CamelException *ex); static gboolean _create(CamelFolder *folder, CamelException *ex); static gboolean _delete (CamelFolder *folder, gboolean recurse, CamelException *ex); static gboolean _delete_messages (CamelFolder *folder, CamelException *ex); static GList *_list_subfolders (CamelFolder *folder, CamelException *ex); static CamelMimeMessage *_get_message_by_number (CamelFolder *folder, gint number, CamelException *ex); static gint _get_message_count (CamelFolder *folder, CamelException *ex); static void _append_message (CamelFolder *folder, CamelMimeMessage *message, CamelException *ex); static GPtrArray *_get_uid_array (CamelFolder *folder, CamelException *ex); static CamelMimeMessage *_get_message_by_uid (CamelFolder *folder, const gchar *uid, CamelException *ex); #if 0 static void _expunge (CamelFolder *folder, CamelException *ex); static void _copy_message_to (CamelFolder *folder, CamelMimeMessage *message, CamelFolder *dest_folder, CamelException *ex); #endif static const gchar *_get_message_uid (CamelFolder *folder, CamelMimeMessage *message, CamelException *ex); static void _finalize (GtkObject *object); static void camel_nntp_folder_class_init (CamelNNTPFolderClass *camel_nntp_folder_class) { CamelFolderClass *camel_folder_class = CAMEL_FOLDER_CLASS (camel_nntp_folder_class); GtkObjectClass *gtk_object_class = GTK_OBJECT_CLASS (camel_folder_class); parent_class = gtk_type_class (camel_folder_get_type ()); /* virtual method definition */ /* virtual method overload */ camel_folder_class->init = _init; camel_folder_class->open = _open; camel_folder_class->close = _close; camel_folder_class->exists = _exists; camel_folder_class->create = _create; camel_folder_class->delete = _delete; camel_folder_class->delete_messages = _delete_messages; camel_folder_class->list_subfolders = _list_subfolders; camel_folder_class->get_message_count = _get_message_count; camel_folder_class->get_uid_array = _get_uid_array; camel_folder_class->get_message_by_uid = _get_message_by_uid; #if 0 camel_folder_class->append_message = _append_message; camel_folder_class->expunge = _expunge; camel_folder_class->copy_message_to = _copy_message_to; camel_folder_class->get_message_uid = _get_message_uid; #endif gtk_object_class->finalize = _finalize; } static void _finalize (GtkObject *object) { CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (object); g_free (nntp_folder->summary_file_path); GTK_OBJECT_CLASS (parent_class)->finalize (object); } GtkType camel_nntp_folder_get_type (void) { static GtkType camel_nntp_folder_type = 0; if (!camel_nntp_folder_type) { GtkTypeInfo camel_nntp_folder_info = { "CamelNNTPFolder", sizeof (CamelNNTPFolder), sizeof (CamelNNTPFolderClass), (GtkClassInitFunc) camel_nntp_folder_class_init, (GtkObjectInitFunc) NULL, /* reserved_1 */ NULL, /* reserved_2 */ NULL, (GtkClassInitFunc) NULL, }; camel_nntp_folder_type = gtk_type_unique (CAMEL_FOLDER_TYPE, &camel_nntp_folder_info); } return camel_nntp_folder_type; } static void _init (CamelFolder *folder, CamelStore *parent_store, CamelFolder *parent_folder, const gchar *name, gchar separator, CamelException *ex) { /* call parent method */ parent_class->init (folder, parent_store, parent_folder, name, separator, ex); if (camel_exception_get_id (ex)) return; /* we assume that the parent init method checks for the existance of @folder */ if (!strcmp(name, "/")) { folder->has_summary_capability = FALSE; folder->can_hold_messages = FALSE; folder->can_hold_folders = TRUE; } else { folder->has_summary_capability = TRUE; folder->can_hold_messages = TRUE; folder->can_hold_folders = TRUE; } folder->has_uid_capability = TRUE; folder->has_search_capability = FALSE; } /* internal method used to : - test for the existence of a summary file - test the sync between the summary and the newsgroup - load the summary or create it if necessary */ static void _check_get_or_maybe_generate_summary_file (CamelNNTPFolder *nntp_folder, CamelException *ex) { CamelFolder *folder = CAMEL_FOLDER (nntp_folder); nntp_folder->summary = camel_folder_summary_new (); camel_folder_summary_set_filename (nntp_folder->summary, nntp_folder->summary_file_path); if (-1 == camel_folder_summary_load (nntp_folder->summary)) { /* Bad or nonexistant summary file */ camel_nntp_get_headers (CAMEL_FOLDER( folder )->parent_store, nntp_folder, ex); if (camel_exception_get_id (ex)) return; /* XXX check return value */ camel_folder_summary_save (nntp_folder->summary); } } static void _open (CamelFolder *folder, CamelFolderOpenMode mode, CamelException *ex) { CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (folder); const gchar *root_dir_path; /* call parent class */ parent_class->open (folder, mode, ex); if (camel_exception_get_id(ex)) return; #if 0 /* get (or create) uid list */ if (!(nntp_load_uid_list (nntp_folder) > 0)) nntp_generate_uid_list (nntp_folder); #endif root_dir_path = camel_nntp_store_get_toplevel_dir (CAMEL_NNTP_STORE(folder->parent_store)); nntp_folder->summary_file_path = g_strdup_printf ("%s/%s-ev-summary", root_dir_path, folder->name); _check_get_or_maybe_generate_summary_file (nntp_folder, ex); } static void _close (CamelFolder *folder, gboolean expunge, CamelException *ex) { CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (folder); CamelFolderSummary *summary = nntp_folder->summary; /* call parent implementation */ parent_class->close (folder, expunge, ex); /* XXX only if dirty? */ camel_folder_summary_save (summary); } static gboolean _exists (CamelFolder *folder, CamelException *ex) { #if 0 CamelNNTPFolder *nntp_folder; struct stat stat_buf; gint stat_error; gboolean exists; g_assert(folder != NULL); nntp_folder = CAMEL_NNTP_FOLDER (folder); /* check if the nntp summary path is determined */ if (!nntp_folder->summary_file_path) { camel_exception_set (ex, CAMEL_EXCEPTION_FOLDER_INVALID, "undetermined folder summary path. Maybe use set_name ?"); return FALSE; } /* check if the nntp file exists */ stat_error = stat (nntp_folder->summary_file_path, &stat_buf); if (stat_error == -1) return FALSE; exists = S_ISREG (stat_buf.st_mode); /* we should check the rights here */ return exists; #endif return TRUE; } static gboolean _create (CamelFolder *folder, CamelException *ex) { #if 0 CamelNNTPSummary *summary; g_assert(folder != NULL); /* call default implementation */ parent_class->create (folder, ex); /* create the summary object */ summary = CAMEL_NNTP_SUMMARY (gtk_object_new (camel_nntp_summary_get_type (), NULL)); summary->nb_message = 0; summary->next_uid = 1; summary->nntp_file_size = 0; summary->message_info = g_array_new (FALSE, FALSE, sizeof (CamelNNTPSummaryInformation)); #endif return TRUE; } static gboolean _delete (CamelFolder *folder, gboolean recurse, CamelException *ex) { #if 0 gboolean folder_already_exists; g_assert(folder != NULL); /* check if the folder object exists */ /* in the case where the folder does not exist, return immediatly */ folder_already_exists = camel_folder_exists (folder, ex); if (camel_exception_get_id (ex)) return FALSE; if (!folder_already_exists) return TRUE; /* call default implementation. It should delete the messages in the folder and recurse the operation to subfolders */ parent_class->delete (folder, recurse, ex); #endif return TRUE; } gboolean _delete_messages (CamelFolder *folder, CamelException *ex) { gboolean folder_already_exists; g_assert(folder!=NULL); /* in the case where the folder does not exist, return immediatly */ folder_already_exists = camel_folder_exists (folder, ex); if (camel_exception_get_id (ex)) return FALSE; if (!folder_already_exists) return TRUE; return TRUE; } static GList * _list_subfolders (CamelFolder *folder, CamelException *ex) { /* newsgroups don't have subfolders */ return NULL; } static gint _get_message_count (CamelFolder *folder, CamelException *ex) { CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER(folder); g_assert (folder); g_assert (nntp_folder->summary); return camel_folder_summary_count(nntp_folder->summary); } #if 0 static void _append_message (CamelFolder *folder, CamelMimeMessage *message, CamelException *ex) { CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (folder); #if 0 CamelNNTPSummary *summary = CAMEL_NNTP_SUMMARY (folder->summary); #endif CamelStream *output_stream; guint32 tmp_file_size; guint32 next_uid; gint tmp_file_fd; GArray *message_info_array; #if 0 GArray *nntp_summary_info; #endif gchar *tmp_message_filename; gint fd1, fd2; int i; /* write the message itself */ output_stream = camel_stream_fs_new_with_name (tmp_message_filename, CAMEL_STREAM_FS_WRITE); if (output_stream != NULL) { camel_stream_write_string (output_stream, "From - \n"); camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), output_stream); } camel_stream_flush (output_stream); gtk_object_unref (GTK_OBJECT (output_stream)); /* at this point we have saved the message to a temporary file, now, we have to add the x-evolution field and also update the main summary */ /* First : parse the nntp file, but only from the position where the message has been added, wich happens to be the last postion in the nntp file before we added the message. This position is still stored in the summary for the moment */ next_uid = summary->next_uid; tmp_file_fd = open (tmp_message_filename, O_RDONLY); message_info_array = camel_nntp_parse_file (tmp_file_fd, "From - ", 0, &tmp_file_size, &next_uid, TRUE, NULL, 0, ex); close (tmp_file_fd); /* get the value of the last available UID as saved in the summary file, again */ next_uid = summary->next_uid; /* make sure all our of message info's have 0 uid - ignore any set elsewhere */ for (i=0;ilen;i++) { g_array_index(message_info_array, CamelNNTPParserMessageInfo, i).uid = 0; } /* OK, this is not very efficient, we should not use the same method as for parsing an entire mail file, but I have no time to write a simpler parser */ #if 0 next_uid = camel_nntp_write_xev (nntp_folder, tmp_message_filename, message_info_array, &tmp_file_size, next_uid, ex); #endif if (camel_exception_get_id (ex)) { /* ** FIXME : free the preparsed information */ return; } #if 0 nntp_summary_info = parsed_information_to_nntp_summary (message_info_array); #endif /* store the number of messages as well as the summary array */ summary->nb_message += 1; summary->next_uid = next_uid; ((CamelNNTPSummaryInformation *)(nntp_summary_info->data))->position += summary->nntp_file_size; summary->nntp_file_size += tmp_file_size; camel_nntp_summary_append_entries (summary, nntp_summary_info); g_array_free (nntp_summary_info, TRUE); /* append the temporary file message to the nntp file */ fd1 = open (tmp_message_filename, O_RDONLY); fd2 = open (nntp_folder->folder_file_path, O_WRONLY | O_CREAT | O_APPEND, 0600); if (fd2 == -1) { camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INSUFFICIENT_PERMISSION, "could not open the nntp folder file for appending the message\n" "\t%s\n" "Full error is : %s\n", nntp_folder->folder_file_path, strerror (errno)); return; } camel_nntp_copy_file_chunk (fd1, fd2, tmp_file_size, ex); close (fd1); close (fd2); /* remove the temporary file */ unlink (tmp_message_filename); g_free (tmp_message_filename); } #endif static GPtrArray * _get_uid_array (CamelFolder *folder, CamelException *ex) { CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (folder); GPtrArray *message_info_array, *out; CamelMessageInfo *message_info; int i; message_info_array = nntp_folder->summary->messages; out = g_ptr_array_new (); g_ptr_array_set_size (out, message_info_array->len); for (i=0; ilen; i++) { message_info = (CamelMessageInfo *)(message_info_array->pdata) + i; out->pdata[i] = g_strdup (message_info->uid); } return out; } static CamelMimeMessage * _get_message_by_uid (CamelFolder *folder, const gchar *uid, CamelException *ex) { CamelStream *nntp_istream; CamelStream *message_stream; CamelMimeMessage *message = NULL; CamelStore *parent_store; char *buf; int buf_len; int buf_alloc; int status; gboolean done; /* get the parent store */ parent_store = camel_folder_get_parent_store (folder, ex); if (camel_exception_get_id (ex)) { return NULL; } status = camel_nntp_command (CAMEL_NNTP_STORE( parent_store ), NULL, "ARTICLE %s", uid); nntp_istream = CAMEL_NNTP_STORE (parent_store)->istream; /* if the uid was not found, raise an exception and return */ if (status != CAMEL_NNTP_OK) { camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID_UID, "message %s not found.", uid); return NULL; } /* XXX ick ick ick. read the entire message into a buffer and then create a stream_mem for it. */ buf_alloc = 2048; buf_len = 0; buf = malloc(buf_alloc); done = FALSE; buf[0] = 0; while (!done) { char *line = camel_stream_buffer_read_line ( CAMEL_STREAM_BUFFER ( nntp_istream ), ex); int line_length; /* XXX check exception */ line_length = strlen ( line ); if (!strcmp(line, ".")) { done = TRUE; g_free (line); } else { if (buf_len + line_length > buf_alloc) { buf_alloc *= 2; buf = realloc (buf, buf_alloc); } strcat(buf, line); strcat(buf, "\n"); buf_len += strlen(line) + 1; g_free (line); } } /* create a stream bound to the message */ message_stream = camel_stream_mem_new_with_buffer(buf, buf_len); message = camel_mime_message_new (); if (camel_data_wrapper_construct_from_stream ((CamelDataWrapper *)message, message_stream) == -1) { gtk_object_unref ((GtkObject *)message); gtk_object_unref ((GtkObject *)message_stream); camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID_UID, /* XXX */ "Could not create message for uid %s.", uid); return NULL; } gtk_object_unref ((GtkObject *)message_stream); /* init other fields? */ message->folder = folder; gtk_object_ref((GtkObject *)folder); message->message_uid = g_strdup(uid); #if 0 gtk_signal_connect((GtkObject *)message, "message_changed", message_changed, folder); #endif return message; } /* get message info for a range of messages */ static GPtrArray * summary_get_message_info (CamelFolder *folder, int first, int count) { GPtrArray *array = g_ptr_array_new(); int i, maxcount; CamelNNTPFolder *nntp_folder = (CamelNNTPFolder *)folder; maxcount = camel_folder_summary_count(nntp_folder->summary); maxcount = MIN(first + count, maxcount); for (i=first;isummary->messages, i)); return array; }