Evolution NNTP patch -- annotated

Complete version of evolution-nntp-patch-10 with some comments added.

Directories:

evolution

--- /home/meilof/cvs/orig/evolution//configure.in	2004-01-01 13:22:33.000000000 -0500
+++ configure.in 2004-01-04 16:17:27.000000000 -0500
@@ -336,9 +336,9 @@
dnl NNTP support.
dnl **************************************************
AC_ARG_ENABLE(nntp,
-[ --enable-nntp=[no/yes] Attempt to compile incomplete, unsupported NNTP code],,enable_nntp=no)
+[ --enable-nntp=[no/yes] Build Usenet news (NNTP) backend],,enable_nntp=yes)
if test "x$enable_nntp" = "xyes"; then
- AC_DEFINE(ENABLE_NNTP,1,[Don't try this at home])
+ AC_DEFINE(ENABLE_NNTP,1,[Build NNTP backend])
msg_nntp=yes
else
msg_nntp=no
Makes sure NNTP support is compiled in by default.

evolution/camel/providers/nntp

--- /home/meilof/cvs/orig/evolution//camel/providers/nntp/camel-nntp-folder.c	2003-07-09 15:21:58.000000000 -0400
+++ camel/providers/nntp/camel-nntp-folder.c 2004-01-04 17:05:19.000000000 -0500
@@ -44,13 +44,21 @@
#include "camel/camel-session.h"
#include "camel/camel-data-cache.h"

+#include "camel/camel-mime-filter-crlf.h"
+#include "camel/camel-stream-filter.h"
+#include "camel/camel-mime-message.h"
+#include "camel/camel-multipart.h"
+#include "camel/camel-mime-part.h"
+#include "camel/camel-stream-buffer.h"
+
#include "camel-nntp-summary.h"
#include "camel-nntp-store.h"
#include "camel-nntp-folder.h"
#include "camel-nntp-store.h"
#include "camel-nntp-private.h"

-static CamelFolderClass *parent_class = NULL;
+static CamelFolderClass *folder_class = NULL;
+static CamelDiscoFolderClass *parent_class = NULL;

/* Returns the class for a CamelNNTPFolder */
#define CNNTPF_CLASS(so) CAMEL_NNTP_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
@@ -58,7 +66,7 @@
#define CNNTPS_CLASS(so) CAMEL_STORE_CLASS (CAMEL_OBJECT_GET_CLASS(so))

static void
-nntp_folder_sync (CamelFolder *folder, gboolean expunge, CamelException *ex)
+nntp_folder_sync_online (CamelFolder *folder, /*gboolean expunge, */CamelException *ex)
{
CamelNNTPStore *nntp_store;
CamelFolderChangeInfo *changes = NULL;
@@ -86,9 +94,82 @@
}

static void
+nntp_folder_sync_offline (CamelFolder *folder, CamelException *ex)
+{
+ camel_folder_summary_save (folder->summary);
+}
+
+static void
nntp_folder_set_message_flags(CamelFolder *folder, const char *uid, guint32 flags, guint32 set)
{
- ((CamelFolderClass *)parent_class)->set_message_flags(folder, uid, flags, set);
+ ((CamelFolderClass *)folder_class)->set_message_flags(folder, uid, flags, set);
+}
+
+static enum { fail, error, success }
+nntp_folder_download_message(CamelStream **retstream, CamelNNTPStore *nntp_store,
+ CamelNNTPFolder *nntp_folder, const char *msgid, CamelException *ex)
+{
+ CamelStream *stream = NULL;
+ int ret;
+ char *line;
+
+ if (camel_nntp_store_set_folder(nntp_store, (CamelFolder*)nntp_folder, nntp_folder->changes, ex) == -1)
+ return fail;
+
+ ret = camel_nntp_command(nntp_store, &line, "article %s", msgid);
+ if (ret == -1)
+ return error;
+
+ if (ret == 220) {
+ stream = camel_data_cache_add(nntp_store->cache, "cache", msgid, NULL);
+ if (stream) {
+ if (camel_stream_write_to_stream((CamelStream *)nntp_store->stream, stream) == -1)
+ return error;
+ if (camel_stream_reset(stream) == -1)
+ return error;
+ } else {
+ stream = (CamelStream *)nntp_store->stream;
+ camel_object_ref((CamelObject *)stream);
+ }
+ }
+
+ *retstream = stream;
+ return success;
+}
+
+
+static void
+nntp_folder_cache_message(CamelDiscoFolder *disco_folder,
+ const char *uid, CamelException *ex)
+{
+ CamelStream *stream;
+ const char *msgid;
+ CamelNNTPStore *nntp_store = (CamelNNTPStore *)((CamelFolder *)disco_folder)->parent_store;
+
+ msgid = strchr(uid, ',');
+ if (msgid == 0) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Internal error: uid in invalid format: %s"), uid);
+ return;
+ }
+ msgid++;
+
+
+ CAMEL_NNTP_STORE_LOCK(nntp_store, command_lock);
+
+ switch (nntp_folder_download_message(&stream, nntp_store,
+ (CamelNNTPFolder*) disco_folder, msgid, ex)) {
+ case success: break;
+ default:
+ /* failed to download message! */
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Could not get article %s from NNTP server"), uid);
+ }
+
+ if (stream)
+ camel_object_unref((CamelObject *)stream);
+
+ CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock);
}
This code was splitted from get_message() so that it could also be used for caching messages without returning them.
 
static CamelMimeMessage *
@@ -99,8 +180,7 @@
CamelFolderChangeInfo *changes;
CamelNNTPFolder *nntp_folder;
CamelStream *stream = NULL;
- int ret;
- char *line;
+ char *line = NULL;
const char *msgid;

nntp_store = (CamelNNTPStore *)folder->parent_store;
@@ -119,25 +199,16 @@
/* Lookup in cache, NEWS is global messageid's so use a global cache path */
stream = camel_data_cache_get(nntp_store->cache, "cache", msgid, NULL);
if (stream == NULL) {
- /* Not in cache, retrieve and put in cache */
- if (camel_nntp_store_set_folder(nntp_store, folder, nntp_folder->changes, ex) == -1)
+ if (camel_disco_store_status (CAMEL_DISCO_STORE (nntp_store)) == CAMEL_DISCO_STORE_OFFLINE) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("This message is not currently available"));
goto fail;
-
- ret = camel_nntp_command(nntp_store, &line, "article %s", msgid);
- if (ret == -1)
- goto error;
-
- if (ret == 220) {
- stream = camel_data_cache_add(nntp_store->cache, "cache", msgid, NULL);
- if (stream) {
- if (camel_stream_write_to_stream((CamelStream *)nntp_store->stream, stream) == -1)
- goto error;
- if (camel_stream_reset(stream) == -1)
- goto error;
- } else {
- stream = (CamelStream *)nntp_store->stream;
- camel_object_ref((CamelObject *)stream);
}
+
+ switch (nntp_folder_download_message(&stream, nntp_store, nntp_folder, msgid, ex)) {
+ case error: goto error;
+ case fail: goto fail;
+ default: break;
}
}

@@ -259,6 +330,138 @@
}

static void
+nntp_folder_append_message_online(CamelFolder *folder, CamelMimeMessage *mime_message,
+ const CamelMessageInfo *info, char **appended_uid,
+ CamelException *ex) {
+ CamelNNTPStore *nntp_store = (CamelNNTPStore *)folder->parent_store;
+ CamelStream *stream = (CamelStream*)nntp_store->stream;
+ CamelStreamFilter *filtered_stream;
+ CamelMimeFilter *crlffilter;
+ int ret;
+ unsigned int u;
+ struct _camel_header_raw *header, *savedhdrs, *n, *tail;
+
+ unsigned char *line;
+ char *cmdbuf = NULL, *respbuf = NULL;
+
+ CAMEL_NNTP_STORE_LOCK(nntp_store, command_lock);
+
+ /* send 'POST' command */
+ ret = camel_nntp_command(nntp_store, (char **)&line, "post");
+
+ if (ret != 340) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INSUFFICIENT_PERMISSION,
+ _("Posting not allowed by news server"));
+ CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock);
+ return;
+ }
+
+ /* send the 'Newsgroups: ' header */
+ cmdbuf = g_strdup_printf ("Newsgroups: %s\r\n", folder->full_name);
+
+ if (camel_stream_write (stream, cmdbuf, strlen (cmdbuf)) == -1) {
+ g_free (cmdbuf);
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to send newsgroups header: %s: message not posted"),
+ g_strerror (errno));
+ CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock);
+ return;
+ }
+ g_free (cmdbuf);
+
+ /* setup stream filtering */
+ crlffilter = camel_mime_filter_crlf_new (CAMEL_MIME_FILTER_CRLF_ENCODE, CAMEL_MIME_FILTER_CRLF_MODE_CRLF_DOTS);
+ filtered_stream = camel_stream_filter_new_with_stream (stream);
+ camel_stream_filter_add (filtered_stream, CAMEL_MIME_FILTER (crlffilter));
+ camel_object_unref (crlffilter);
+
+ /* remove mail 'To', 'CC', and 'BCC' headers */
+ savedhdrs = NULL;
+ tail = (struct _camel_header_raw *) &savedhdrs;
+
+ header = (struct _camel_header_raw *) &CAMEL_MIME_PART (mime_message)->headers;
+ n = header->next;
+ while (n != NULL) {
+ if (!strcasecmp (n->name, "To") || !strcasecmp (n->name, "CC") || !strcasecmp(n->name, "BCC")) {
+ header->next = n->next;
+ tail->next = n;
+ n->next = NULL;
+ tail = n;
+ } else {
+ header = n;
+ }
+
+ n = header->next;
+ }
+
+ /* write the message */
+ ret = camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (mime_message), CAMEL_STREAM (filtered_stream));
+
+ /* restore the mail headers */
+ header->next = savedhdrs;
+
+ if (ret == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Error posting to newsgroup: "
+ "%s: message not posted"),
+ g_strerror (errno));
+ camel_object_unref (filtered_stream);
+ CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock);
+ return;
+ }
+
+ camel_stream_flush (CAMEL_STREAM (filtered_stream));
+ camel_object_unref (filtered_stream);
+
+ /* terminate the message body */
+ if (camel_stream_write (stream, "\r\n.\r\n", 5) == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Error posting to newsgroup: "
+ "%s: message not posted"),
+ g_strerror (errno));
+ CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock);
+ return;
+ }
+
+ if (camel_nntp_stream_line(nntp_store->stream, (unsigned char **)&respbuf, &u) == -1)
+ respbuf = NULL;
+
+ if (!respbuf || strncmp (respbuf, "240", 3)) {
+ if (!respbuf)
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Error reading response to posted message: message not posted"));
+ else
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Error posting message: %s: message not posted"), respbuf);
+ CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock);
+ return;
+ }
+
+ CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock);
+
+ return;
+}
Posting of NNTP messages. Basically just the code from the SMTP backend. Removes mail headers, and add "Newsgroups: " header from the CamelFolder strucure.
+
+static void
+nntp_folder_append_message_offline(CamelFolder *folder, CamelMimeMessage *mime_message,
+ const CamelMessageInfo *info, char **appended_uid,
+ CamelException *ex) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("You cannot post NNTP messages while working offline!"));
+}
+
+/* I do not know what to do this exactly. Looking at the IMAP implementation for this, it
+ seems to assume the message is copied to a folder on the same store. In that case, an
+ NNTP implementation doesn't seem to make any sense. */
+static void
+nntp_folder_transfer_message(CamelFolder *source, GPtrArray *uids, CamelFolder *dest,
+ GPtrArray **transferred_uids, gboolean delete_orig, CamelException *ex)
+{
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("You cannot copy messages from a NNTP folder!"));
+}
As the comment says, I do not know what this does exactly, but I do not assume it is useful for NNTP.
+
+static void
nntp_folder_init(CamelNNTPFolder *nntp_folder, CamelNNTPFolderClass *klass)
{
struct _CamelNNTPFolderPrivate *p;
@@ -274,7 +477,7 @@
{
struct _CamelNNTPFolderPrivate *p;

- g_free(nntp_folder->storage_path);
+ camel_folder_summary_save(((CamelFolder*)nntp_folder)->summary);

p = nntp_folder->priv;
g_mutex_free(p->search_lock);
@@ -285,14 +488,26 @@
static void
nntp_folder_class_init (CamelNNTPFolderClass *camel_nntp_folder_class)
{
+ CamelDiscoFolderClass *camel_disco_folder_class = CAMEL_DISCO_FOLDER_CLASS (camel_nntp_folder_class);
CamelFolderClass *camel_folder_class = CAMEL_FOLDER_CLASS (camel_nntp_folder_class);

- parent_class = CAMEL_FOLDER_CLASS (camel_type_get_global_classfuncs (camel_folder_get_type ()));
+ parent_class = CAMEL_DISCO_FOLDER_CLASS (camel_type_get_global_classfuncs (camel_disco_folder_get_type ()));
+ folder_class = CAMEL_FOLDER_CLASS (camel_type_get_global_classfuncs (camel_folder_get_type ()));

/* virtual method definition */

/* virtual method overload */
- camel_folder_class->sync = nntp_folder_sync;
+ camel_disco_folder_class->sync_online = nntp_folder_sync_online;
+ camel_disco_folder_class->sync_resyncing = nntp_folder_sync_offline;
+ camel_disco_folder_class->sync_offline = nntp_folder_sync_offline;
+ camel_disco_folder_class->cache_message = nntp_folder_cache_message;
+ camel_disco_folder_class->append_online = nntp_folder_append_message_online;
+ camel_disco_folder_class->append_resyncing = nntp_folder_append_message_online;
+ camel_disco_folder_class->append_offline = nntp_folder_append_message_offline;
+ camel_disco_folder_class->transfer_online = nntp_folder_transfer_message;
+ camel_disco_folder_class->transfer_resyncing = nntp_folder_transfer_message;
+ camel_disco_folder_class->transfer_offline = nntp_folder_transfer_message;
+
camel_folder_class->set_message_flags = nntp_folder_set_message_flags;
camel_folder_class->get_message = nntp_folder_get_message;
camel_folder_class->search_by_expression = nntp_folder_search_by_expression;
@@ -306,7 +521,7 @@
static CamelType camel_nntp_folder_type = CAMEL_INVALID_TYPE;

if (camel_nntp_folder_type == CAMEL_INVALID_TYPE) {
- camel_nntp_folder_type = camel_type_register (CAMEL_FOLDER_TYPE, "CamelNNTPFolder",
+ camel_nntp_folder_type = camel_type_register (CAMEL_DISCO_FOLDER_TYPE, "CamelNNTPFolder",
sizeof (CamelNNTPFolder),
sizeof (CamelNNTPFolderClass),
(CamelObjectClassInitFunc) nntp_folder_class_init,
--- /home/meilof/cvs/orig/evolution//camel/providers/nntp/camel-nntp-provider.c 2002-07-04 19:15:16.000000000 -0400
+++ camel/providers/nntp/camel-nntp-provider.c 2004-01-04 17:05:19.000000000 -0500
@@ -36,6 +36,15 @@
static gint check_equal (char *s1, char *s2);
static gint nntp_url_equal (gconstpointer a, gconstpointer b);

+CamelProviderConfEntry nntp_conf_entries[] = {
+ { CAMEL_PROVIDER_CONF_SECTION_START, "folders", NULL,
+ N_("Folders") },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "show_short_notation", NULL,
+ N_("Show folders in short notation (e.g. c.o.linux rather than comp.os.linux)"), "1" },
+ { CAMEL_PROVIDER_CONF_SECTION_END },
+ { CAMEL_PROVIDER_CONF_END }
+};
+
static CamelProvider news_provider = {
"nntp",
N_("USENET news"),
@@ -51,9 +60,21 @@
CAMEL_URL_NEED_HOST | CAMEL_URL_ALLOW_USER |
CAMEL_URL_ALLOW_PASSWORD | CAMEL_URL_ALLOW_AUTH,

+ nntp_conf_entries
+
/* ... */
};

+CamelServiceAuthType camel_nntp_password_authtype = {
+ N_("Password"),
+
+ N_("This option will authenticate with the NNTP server using a "
+ "plaintext password."),
+
+ "",
+ TRUE
+};
+
void
camel_provider_module_init (CamelSession *session)
{
@@ -62,6 +83,7 @@

news_provider.url_hash = nntp_url_hash;
news_provider.url_equal = nntp_url_equal;
+ news_provider.authtypes = g_list_append (NULL, &camel_nntp_password_authtype);

camel_session_register_provider (session, &news_provider);
}
--- /home/meilof/cvs/orig/evolution//camel/providers/nntp/camel-nntp-store.c 2003-09-22 11:00:59.000000000 -0400
+++ camel/providers/nntp/camel-nntp-store.c 2004-01-04 17:15:34.000000000 -0500
@@ -40,10 +40,15 @@
#include <camel/camel-tcp-stream-raw.h>
#include <camel/camel-tcp-stream-ssl.h>

+#include <camel/camel-disco-store.h>
+#include <camel/camel-disco-diary.h>
+
#include "camel-nntp-summary.h"
#include "camel-nntp-store.h"
+#include "camel-nntp-store-summary.h"
#include "camel-nntp-folder.h"
#include "camel-nntp-private.h"
+#include "camel-nntp-resp-codes.h"

#define w(x)
extern int camel_verbose_debug;
@@ -54,10 +59,7 @@

#define DUMP_EXTENSIONS

-/* define if you want the subscribe ui to show folders in tree form */
-/* #define INFO_AS_TREE */
-
-static CamelStoreClass *parent_class = NULL;
+static CamelDiscoStoreClass *parent_class = NULL;
static CamelServiceClass *service_class = NULL;

/* Returns the class for a CamelNNTPStore */
@@ -65,6 +67,16 @@
#define CF_CLASS(so) CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
#define CNNTPF_CLASS(so) CAMEL_NNTP_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))

+static void nntp_construct (CamelService *service, CamelSession *session,
+ CamelProvider *provider, CamelURL *url,
+ CamelException *ex);
+
+
+static gboolean
+nntp_can_work_offline(CamelDiscoStore *store)
+{
+ return TRUE;
+}

enum {
USE_SSL_NEVER,
@@ -76,25 +88,23 @@
connect_to_server (CamelService *service, int ssl_mode, CamelException *ex)
{
CamelNNTPStore *store = (CamelNNTPStore *) service;
+ CamelDiscoStore *disco_store = (CamelDiscoStore*) service;
CamelStream *tcp_stream;
gboolean retval = FALSE;
unsigned char *buf;
unsigned int len;
struct hostent *h;
int port, ret;
+ char *path;

CAMEL_NNTP_STORE_LOCK(store, command_lock);
-
+
/* setup store-wide cache */
if (store->cache == NULL) {
- char *root;
-
- root = camel_session_get_storage_path (service->session, service, ex);
- if (root == NULL)
+ if (store->storage_path == NULL)
goto fail;

- store->cache = camel_data_cache_new (root, 0, ex);
- g_free (root);
+ store->cache = camel_data_cache_new (store->storage_path, 0, ex);
if (store->cache == NULL)
goto fail;

@@ -171,9 +181,12 @@
camel_nntp_command (store, (char **) &buf, "mode reader");
retval = TRUE;

+ path = g_strdup_printf ("%s/journal", store->storage_path);
+ disco_store->diary = camel_disco_diary_new (disco_store, path, ex);
+ g_free (path);
+
fail:
CAMEL_NNTP_STORE_UNLOCK(store, command_lock);
-
return retval;
}

@@ -189,7 +202,7 @@
};

static gboolean
-nntp_connect (CamelService *service, CamelException *ex)
+nntp_connect_online (CamelService *service, CamelException *ex)
{
#ifdef HAVE_SSL
const char *use_ssl;
@@ -230,7 +243,36 @@
}

static gboolean
-nntp_disconnect (CamelService *service, gboolean clean, CamelException *ex)
+nntp_connect_offline(CamelService *service, CamelException *ex)
+{
+ CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(service);
+ CamelDiscoStore *disco_store = (CamelDiscoStore*) nntp_store;
+ char *path;
+
+ if (nntp_store->storage_path == NULL) return FALSE;
+
+ /* setup store-wide cache */
+ if (nntp_store->cache == NULL) {
+ nntp_store->cache = camel_data_cache_new (nntp_store->storage_path, 0, ex);
+ if (nntp_store->cache == NULL) return FALSE;
+
+ /* Default cache expiry - 2 weeks old, or not visited in 5 days */
+ camel_data_cache_set_expire_age (nntp_store->cache, 60*60*24*14);
+ camel_data_cache_set_expire_access (nntp_store->cache, 60*60*24*5);
+ }
+
+ path = g_strdup_printf ("%s/journal", nntp_store->storage_path);
+ disco_store->diary = camel_disco_diary_new (disco_store, path, ex);
+ g_free (path);
+
+ if (!disco_store->diary)
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+nntp_disconnect_online (CamelService *service, gboolean clean, CamelException *ex)
{
CamelNNTPStore *store = CAMEL_NNTP_STORE (service);
char *line;
@@ -251,6 +293,22 @@
return TRUE;
}

+static gboolean
+nntp_disconnect_offline (CamelService *service, gboolean clean, CamelException *ex)
+{
+ CamelDiscoStore *disco = CAMEL_DISCO_STORE(service);
+
+ if (!service_class->disconnect (service, clean, ex))
+ return FALSE;
+
+ if (disco->diary) {
+ camel_object_unref (CAMEL_OBJECT (disco->diary));
+ disco->diary = NULL;
+ }
+
+ return TRUE;
+}
+
static char *
nntp_store_get_name (CamelService *service, gboolean brief)
{
@@ -261,26 +319,17 @@

}

-static CamelServiceAuthType password_authtype = {
- N_("Password"),
-
- N_("This option will authenticate with the NNTP server using a "
- "plaintext password."),
-
- "",
- TRUE
-};
+extern CamelServiceAuthType camel_nntp_password_authtype;

static GList *
nntp_store_query_auth_types (CamelService *service, CamelException *ex)
{
- g_warning ("nntp::query_auth_types: not implemented. Defaulting.");
-
- return g_list_append (NULL, &password_authtype);
+ return g_list_append (NULL, &camel_nntp_password_authtype);
+ return NULL;
}

static CamelFolder *
-nntp_store_get_folder(CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex)
+nntp_get_folder(CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex)
{
CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE (store);
CamelFolder *folder;
@@ -294,111 +343,464 @@
return folder;
}

+/*
+ * Converts a fully-fledged newsgroup name to a name in short dotted notation,
+ * e.g. nl.comp.os.linux.programmeren becomes n.c.o.l.programmeren
+ */
+
+static char *
+nntp_newsgroup_name_short(char *name)
+{
+ char *tmp = g_malloc0(strlen(name) + 1), *resptr = tmp, *ptr2;
+
+ while ((ptr2 = strchr(name, '.'))) {
+ if (ptr2 == name) {
+ name++;
+ continue;
+ }
+
+ *resptr++ = *name;
+ *resptr++ = '.';
+ name = ptr2 + 1;
+ }
+
+ strcpy(resptr, name);
+ return tmp;
+}
+
+/*
+ * This function converts a NntpStoreSummary item to a FolderInfo item that
+ * can be returned by the get_folders() call to the store. Both structs have
+ * essentially the same fields.
+ */
+
static CamelFolderInfo *
-nntp_store_get_folder_info(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
+nntp_folder_info_from_store_info(gboolean short_notation, CamelURL *url, CamelStoreInfo *si)
{
- CamelURL *url = CAMEL_SERVICE (store)->url;
- CamelNNTPStore *nntp_store = (CamelNNTPStore *)store;
- CamelFolderInfo *groups = NULL, *last = NULL, *fi;
- unsigned int len;
- unsigned char *line, *space;
- int ret = -1;
+ CamelFolderInfo *fi = g_malloc0(sizeof(*fi));
+
+ fi->full_name = g_strdup(si->path);

- CAMEL_NNTP_STORE_LOCK(nntp_store, command_lock);
+ if (short_notation)
+ fi->name = nntp_newsgroup_name_short(si->path);
+ else
+ fi->name = g_strdup(si->path);
+
+ fi->url = g_strdup(si->uri);
+ fi->unread_message_count = -1;
+ /* fi->path is the 'canonicalised' path used by the UI (folder-tree). Not
+ * as important these days, but folders used to get added to the tree based
+ * on its path rather than the structure of the CamelFolderInfo's.
+ *
+ * must be delimited by '/' which also means that if the user doesn't want
+ * a flat list of newsgroups, you'll have to replace '.' with '/' for
+ * full_name too. */
+ /*camel_folder_info_build_path(fi, '/');*/
+ fi->path = g_strdup_printf("/%s", si->path);
+
+ /* set URL */
+ if (fi->url)
+ return fi;
+
+ if (url->user)
+ fi->url = g_strdup_printf ("nntp://%s@%s/%s", url->user, url->host, si->path);
+ else
+ fi->url = g_strdup_printf ("nntp://%s/%s", url->host, si->path);
+ return fi;
+}
+
+static CamelFolderInfo *
+nntp_folder_info_from_name(gboolean short_notation, CamelURL *url, char *name) {
+ CamelFolderInfo *fi = g_malloc0(sizeof(*fi));
+
+ fi->full_name = g_strdup(name);
+
+ if (short_notation)
+ fi->name = nntp_newsgroup_name_short(name);
+ else
+ fi->name = g_strdup(name);
+
+ fi->unread_message_count = -1;
+ if (url->user)
+ fi->url = g_strdup_printf ("nntp://%s@%s/%s", url->user, url->host, name);
+ else
+ fi->url = g_strdup_printf ("nntp://%s/%s", url->host, name);
+
+ camel_folder_info_build_path(fi, '/');
+ return fi;
+}

- ret = camel_nntp_command(nntp_store, (char **)&line, "list");
- if (ret != 215) {
- ret = -1;
- goto error;
- }
-
- while ( (ret = camel_nntp_stream_line(nntp_store->stream, &line, &len)) > 0) {
- space = strchr(line, ' ');
- if (space)
- *space = 0;
-
- if (top == NULL || top[0] == 0 || strcmp(top, line) == 0) {
- fi = g_malloc0(sizeof(*fi));
- fi->name = g_strdup(line);
- fi->full_name = g_strdup(line);
- if (url->user)
- fi->url = g_strdup_printf ("nntp://%s@%s/%s", url->user, url->host, line);
+static CamelStoreInfo *
+nntp_store_info_from_line(CamelStoreSummary *summ, CamelURL *url, char *line)
+{
+ CamelStoreInfo *si = camel_store_summary_info_new(summ);
+ if (url->user)
+ si->uri = g_strdup_printf ("nntp://%s@%s/%s", url->user, url->host, line);
+ else
+ si->uri = g_strdup_printf ("nntp://%s/%s", url->host, line);
+ si->path = g_strdup(line);
+ return (CamelStoreInfo*)si;
+}
+
+static CamelFolderInfo *
+nntp_store_get_subscribed_folder_info(CamelNNTPStore *store, const char *_top, guint flags, CamelException *ex)
+{
+ CamelURL *url = CAMEL_SERVICE (store)->url;
+ int i;
+ int root_or_flag = (_top == NULL || _top[0] == '\0') ? 1 : 0;
+ CamelStoreInfo *si;
+ CamelFolderInfo *first = NULL, *last = NULL, *fi = NULL;
+ char *top = g_strconcat(_top?_top:"", ".", NULL);
+ int toplen = strlen(top);
+
+ for (i=0;(si = camel_store_summary_index((CamelStoreSummary *)store->summary, i));i++) {
+ if ((si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) &&
+ (root_or_flag || strncasecmp(si->path, top, toplen) == 0)) {
+ fi = nntp_folder_info_from_store_info(store->do_short_folder_notation, url, si);
+ fi->flags |= CAMEL_FOLDER_NOINFERIORS | CAMEL_FOLDER_NOCHILDREN;
+ if (!fi) continue;
+ if (last)
+ last->sibling = fi;
else
- fi->url = g_strdup_printf ("nntp://%s/%s", url->host, line);
- fi->unread_message_count = -1;
- camel_folder_info_build_path(fi, '/');
+ first = fi;
+ last = fi;
+ }
+ camel_store_summary_info_free((CamelStoreSummary *)store->summary, si);
+ }

+ g_free(top);
+ return first;
+}
Currently the subscribed code doesn't do hierarchies yet. If one were to use get_cached_folder_info rather than get_subscribed_folder_info, a hierarchy /is/ returned (and that code does take into account whether you are subscribed or not), but I didn't think this would be /really/ useful (and Notzed told me not to put a high priority on this), and it got a bit messy with subscribing/unsubscribing, because when you added a folder it would not be placed in the hierarchy correctly, but rather it would be added all the way down the list. That probably could be fixed though.
+
+/*
+ * get folder info, using the information in our StoreSummary
+ */
+static CamelFolderInfo *
+nntp_store_get_cached_folder_info(CamelNNTPStore *store, const char *_top, guint flags, CamelException *ex)
+{
+ CamelURL *url = CAMEL_SERVICE (store)->url;
+ int len, i;
+ int subscribed_or_flag = (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED) ? 0 : 1,
+ root_or_flag = (_top == NULL || _top[0] == '\0') ? 1 : 0,
+ recursive_flag = flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE;
+ CamelStoreInfo *si;
+ CamelFolderInfo *first = NULL, *last = NULL, *fi = NULL;
+ char *tmpname;
+ char *top = g_strconcat(_top?_top:"", ".", NULL);
+ int toplen = strlen(top);
+
+ for (i=0;(si = camel_store_summary_index((CamelStoreSummary *)store->summary, i));i++) {
+ if ( (subscribed_or_flag || (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED)) &&
+ (root_or_flag || strncasecmp(si->path, top, toplen) == 0)) {
+ if (recursive_flag || /* we need to add all subitems */
+ strchr(si->path + toplen, '.') == NULL) { /* a direct subitem */
+ /* add the item */
+ fi = nntp_folder_info_from_store_info(FALSE, url, si);
+ if (!fi) continue;
+ } else {
+ /* apparently, this is an indirect subitem. if it's not a subitem of
+ the item we added last, we need to add a portion of this item to
+ the list as a placeholder */
+ if (!last ||
+ strncasecmp(si->path, last->full_name, (len = strlen(last->full_name))) ||
+ si->path[len] != '.') {
+ tmpname = g_strdup(si->path);
+ *(strchr(tmpname + toplen, '.')) = '\0';
+ fi = nntp_folder_info_from_name(FALSE, url, tmpname);
+ } else {
+ continue;
+ }
+ }
if (last)
last->sibling = fi;
else
- groups = fi;
+ first = fi;
last = fi;
+ } else if (subscribed_or_flag && first) {
+ /* we have already added subitems, but this item is no longer a subitem */
+ camel_store_summary_info_free((CamelStoreSummary *)store->summary, si);
+ break;
}
+ camel_store_summary_info_free((CamelStoreSummary *)store->summary, si);
}

- if (ret < 0)
- goto error;
+ g_free(top);
+ return first;
+}
I agree this code is a bit messy, but trouble is that not all parent elements of a newsgroup are in the newsgroup list, e.g. "alt.binaries" is listed but "alt" isn't. I have thought about improving it, but I couldn't find an easy way.
 
- CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock);
+/* retrieves the date from the NNTP server */
+static gboolean
+nntp_get_date(CamelNNTPStore *nntp_store)
+{
+ unsigned char *line;
+ int ret = camel_nntp_command(nntp_store, (char **)&line, "date");
+ char *ptr;
+
+ nntp_store->summary->last_newslist[0] = 0;
+
+ if (ret == 111) {
+ ptr = line + 3;
+ while (*ptr == ' ' || *ptr == '\t') ptr++;
+
+ if (strlen(ptr) == NNTP_DATE_SIZE) {
+ memcpy(nntp_store->summary->last_newslist, ptr, NNTP_DATE_SIZE);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static gint
+store_info_sort(gconstpointer a, gconstpointer b)
+{
+ return strcmp(
+ (*(CamelNntpStoreInfo**)a)->full_name,
+ (*(CamelNntpStoreInfo**)b)->full_name);
+}
+
+static CamelFolderInfo *
+nntp_store_get_folder_info_all(CamelNNTPStore *nntp_store, const char *top, guint32 flags, gboolean online, CamelException *ex)
+{
+ CamelURL *url = CAMEL_SERVICE (nntp_store)->url;
+ CamelNntpStoreSummary *summary = nntp_store->summary;
+ CamelStoreInfo *si;
+ unsigned int len;
+ unsigned char *line, *space;
+ int ret = -1;
+ if (top == NULL) top = "";

- return groups;
+ if (online && (top == NULL || top[0] == 0)) {
+ /* we may need to update */
+ if (summary->last_newslist[0] != 0) {
+ char date[14];
+ memcpy(date, summary->last_newslist + 2, 6); /* YYMMDDD */
+ date[6] = ' ';
+ memcpy(date + 7, summary->last_newslist + 8, 6); /* HHMMSS */
+ date[13] = '\0';
+
+ nntp_get_date(nntp_store);
+
+ ret = camel_nntp_command(nntp_store, (char **)&line, "newgroups %s", date);
+ if (ret != 231) {
+ /* that's pretty odd; the server _used_ to support it... */
+ ret = -1;
+ camel_store_summary_clear((CamelStoreSummary*)summary);
+ summary->last_newslist[0] = 0;
+ goto do_complete_list;
+ }
+
+ while ( (ret = camel_nntp_stream_line(nntp_store->stream, &line, &len)) > 0) {
+ space = strchr(line, ' ');
+ if (space)
+ *space = 0;
+
+ si = nntp_store_info_from_line(CAMEL_STORE_SUMMARY(nntp_store->summary), url, line);
+ camel_store_summary_add((CamelStoreSummary*)nntp_store->summary, si);
+ }
+
+ /* sort the list */
+ /* TODO: insertion sort is probably faster... */
+ g_ptr_array_sort(CAMEL_STORE_SUMMARY(nntp_store->summary)->folders, store_info_sort);
+ } else {
+do_complete_list:
+ /* seems we do need a complete list */
+ /* at first, we do a DATE to find out the last load occasion */
+ nntp_get_date(nntp_store);
+
+ ret = camel_nntp_command(nntp_store, (char **)&line, "list");
+ if (ret != 215) {
+ ret = -1;
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_INVALID,
+ _("Error retrieving newsgroups:\n\n%s"), line);
+ goto error;
+ }
+
+ while ( (ret = camel_nntp_stream_line(nntp_store->stream, &line, &len)) > 0) {
+ space = strchr(line, ' ');
+ if (space)
+ *space = 0;
+ si = nntp_store_info_from_line(CAMEL_STORE_SUMMARY(nntp_store->summary), url, line);
+ camel_store_summary_add((CamelStoreSummary*)nntp_store->summary, si);
+ /* check to see if it answers our current query */
+ }

+ g_ptr_array_sort(CAMEL_STORE_SUMMARY(nntp_store->summary)->folders, store_info_sort);
+
+ if (ret < 0) goto error;
+ }
+
+ camel_store_summary_save((CamelStoreSummary *)nntp_store->summary);
+ }
+ return nntp_store_get_cached_folder_info(nntp_store, top, flags, ex);
error:
+ return NULL;
+}
This code, which retrieves the newsgroups list from the server, makes sure the complete newslist is sorted after it is retrieved. Sorting a 30.000-sized newsgroup list take about a second I believe on my machine, so that shouldn't really be a problem.
+
+static CamelFolderInfo *
+nntp_get_folder_info(CamelStore *store, const char *top, guint32 flags, gboolean online, CamelException *ex)
+{
+ CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(store);
+ CAMEL_NNTP_STORE_LOCK(nntp_store, command_lock);
+ CamelFolderInfo *first = NULL;
+
+ dd(printf("g_f_i: fast %d subscr %d recursive %d online %d top \"%s\"\n",
+ flags & CAMEL_STORE_FOLDER_INFO_FAST,
+ flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED,
+ flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE,
+ online,
+ top?top:""));
+
+ if (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED) {
+ first = nntp_store_get_subscribed_folder_info(nntp_store, top, flags, ex);
+ } else {
+ first = nntp_store_get_folder_info_all(nntp_store, top, flags, online, ex);
+ }
+
CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock);
+ return first;
+}

- if (groups)
- camel_store_free_folder_info(store, groups);
+static CamelFolderInfo *
+nntp_get_folder_info_online(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
+{
+ return nntp_get_folder_info(store, top, flags, FALSE, ex);
+}

- return NULL;
+static CamelFolderInfo *
+nntp_get_folder_info_offline(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
+{
+ return nntp_get_folder_info(store, top, flags, FALSE, ex);
}

static gboolean
nntp_store_folder_subscribed (CamelStore *store, const char *folder_name)
{
CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE (store);
+ CamelStoreInfo *si;

- nntp_store = nntp_store;
+ int truth = FALSE;

- /* FIXME: implement */
+ si = camel_store_summary_path((CamelStoreSummary *)nntp_store->summary, folder_name);
+ if (si) {
+ truth = (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) != 0;
+ camel_store_summary_info_free((CamelStoreSummary *)nntp_store->summary, si);
+ }

- return TRUE;
+ return truth;
}

static void
nntp_store_subscribe_folder (CamelStore *store, const char *folder_name,
CamelException *ex)
{
- CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE (store);
+ CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(store);
+ CamelStoreInfo *fitem;
+ CamelFolderInfo *fi;
+
+ CAMEL_NNTP_STORE_LOCK(nntp_store, command_lock);

- nntp_store = nntp_store;
+ fitem = camel_store_summary_path(CAMEL_STORE_SUMMARY(nntp_store->summary), folder_name);
+ if (!fitem) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
+ _("You cannot subscribe to this folder:\n\n"
+ "The selected item is a probably a parent folder."));
+ } else {
+ if (!(fitem->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED)) {
+ fitem->flags |= CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
+ fi = nntp_folder_info_from_store_info(nntp_store->do_short_folder_notation, CAMEL_SERVICE(store)->url, fitem);
+ fi->flags |= CAMEL_FOLDER_NOINFERIORS | CAMEL_FOLDER_NOCHILDREN;
+ camel_object_trigger_event (CAMEL_OBJECT (nntp_store), "folder_subscribed", fi);
+ camel_folder_info_free (fi);
+ camel_store_summary_touch(CAMEL_STORE_SUMMARY(nntp_store->summary));
+ camel_store_summary_save(CAMEL_STORE_SUMMARY(nntp_store->summary));
+ }
+ }

- /* FIXME: implement */
+ CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock);
}

static void
nntp_store_unsubscribe_folder (CamelStore *store, const char *folder_name,
CamelException *ex)
{
- CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE (store);
+ CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(store);
+ CamelFolderInfo *fi;
+ CamelStoreInfo *fitem;
+ CAMEL_NNTP_STORE_LOCK(nntp_store, command_lock);
+
+ fitem = camel_store_summary_path(CAMEL_STORE_SUMMARY(nntp_store->summary), folder_name);
+
+ if (!fitem) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
+ _("You cannot unsubscribe to this folder:\n\n"
+ "Folder does not exist!"));
+ } else {
+ if (fitem->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) {
+ fitem->flags &= ~CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
+ fi = nntp_folder_info_from_store_info(nntp_store->do_short_folder_notation, CAMEL_SERVICE(store)->url, fitem);
+ camel_object_trigger_event (CAMEL_OBJECT (nntp_store), "folder_unsubscribed", fi);
+ camel_folder_info_free (fi);
+ camel_store_summary_touch(CAMEL_STORE_SUMMARY(nntp_store->summary));
+ camel_store_summary_save(CAMEL_STORE_SUMMARY(nntp_store->summary));
+ }
+ }
+
+ CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock);
+}
+
+/* stubs for various folder operations we're not implementing */

- nntp_store = nntp_store;
+static CamelFolderInfo *
+nntp_create_folder (CamelStore *store, const char *parent_name,
+ const char *folder_name, CamelException *ex)
+{
+ camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
+ _("You cannot create a folder in a News store.\n\n"
+ "To subscribe to a folder, use the Subscribe option from the Tools menu."));
+ return NULL;
+}
+
+static void
+nntp_rename_folder (CamelStore *store, const char *old_name, const char *new_name_in, CamelException *ex)
+{
+ camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
+ _("You cannot rename a folder in a News store."));
+}

- /* FIXME: implement */
+static void
+nntp_delete_folder (CamelStore *store, const char *folder_name, CamelException *ex)
+{
+ nntp_store_subscribe_folder(store, folder_name, ex);
+ camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
+ _("You cannot remove a folder in a News store.\n\n"
+ "To remove a folder, use the Subscribe option from the Tools menu."));
+ return;
}
Stub placeholders. I didn't think adding, renaming and deleting folders would make much sense for NNTP. One might think about making delete_folder unsubscribe, but that didn't work out really well (might be fixed though). Note that the caller of delete_folder has already unsubscribed the folder /before/ it gives us a chance to cancel the operation, so we need to re-subscribe.
 
static void
-nntp_store_finalise (CamelObject *object)
+nntp_store_finalize (CamelObject *object)
{
+ /* call base finalize */
CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE (object);
struct _CamelNNTPStorePrivate *p = nntp_store->priv;

camel_service_disconnect((CamelService *)object, TRUE, NULL);

+ if (nntp_store->summary) {
+ camel_store_summary_save((CamelStoreSummary *)nntp_store->summary);
+ camel_object_unref(nntp_store->summary);
+ }
+
camel_object_unref((CamelObject *)nntp_store->mem);
nntp_store->mem = NULL;
if (nntp_store->stream)
camel_object_unref((CamelObject *)nntp_store->stream);

+ if (nntp_store->base_url)
+ g_free (nntp_store->base_url);
+ if (nntp_store->storage_path)
+ g_free (nntp_store->storage_path);
+
e_mutex_destroy(p->command_lock);

g_free(p);
@@ -407,28 +809,83 @@
static void
nntp_store_class_init (CamelNNTPStoreClass *camel_nntp_store_class)
{
+ CamelDiscoStoreClass *camel_disco_store_class = CAMEL_DISCO_STORE_CLASS (camel_nntp_store_class);
CamelStoreClass *camel_store_class = CAMEL_STORE_CLASS (camel_nntp_store_class);
CamelServiceClass *camel_service_class = CAMEL_SERVICE_CLASS (camel_nntp_store_class);

- parent_class = CAMEL_STORE_CLASS (camel_type_get_global_classfuncs (camel_store_get_type ()));
-
+ parent_class = CAMEL_DISCO_STORE_CLASS (camel_type_get_global_classfuncs (camel_disco_store_get_type ()));
service_class = CAMEL_SERVICE_CLASS (camel_type_get_global_classfuncs (camel_service_get_type ()));

/* virtual method overload */
- camel_service_class->connect = nntp_connect;
- camel_service_class->disconnect = nntp_disconnect;
+ camel_service_class->construct = nntp_construct;
camel_service_class->query_auth_types = nntp_store_query_auth_types;
camel_service_class->get_name = nntp_store_get_name;

- camel_store_class->get_folder = nntp_store_get_folder;
- camel_store_class->get_folder_info = nntp_store_get_folder_info;
+ camel_disco_store_class->can_work_offline = nntp_can_work_offline;
+ camel_disco_store_class->connect_online = nntp_connect_online;
+ camel_disco_store_class->connect_offline = nntp_connect_offline;
+ camel_disco_store_class->disconnect_online = nntp_disconnect_online;
+ camel_disco_store_class->disconnect_offline = nntp_disconnect_offline;
+ camel_disco_store_class->get_folder_online = nntp_get_folder;
+ camel_disco_store_class->get_folder_resyncing = nntp_get_folder;
+ camel_disco_store_class->get_folder_offline = nntp_get_folder;
+
+ camel_disco_store_class->get_folder_info_online = nntp_get_folder_info_online;
+ camel_disco_store_class->get_folder_info_resyncing = nntp_get_folder_info_online;
+ camel_disco_store_class->get_folder_info_offline = nntp_get_folder_info_offline;
+
camel_store_class->free_folder_info = camel_store_free_folder_info_full;

camel_store_class->folder_subscribed = nntp_store_folder_subscribed;
camel_store_class->subscribe_folder = nntp_store_subscribe_folder;
camel_store_class->unsubscribe_folder = nntp_store_unsubscribe_folder;
+
+ camel_store_class->create_folder = nntp_create_folder;
+ camel_store_class->delete_folder = nntp_delete_folder;
+ camel_store_class->rename_folder = nntp_rename_folder;
+}
+
+/* construction function in which we set some basic store properties */
+static void nntp_construct (CamelService *service, CamelSession *session,
+ CamelProvider *provider, CamelURL *url,
+ CamelException *ex) {
+ CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(service);
+ CamelURL *summary_url;
+ char *tmp;
+
+ /* construct the parent first */
+ CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
+ if (camel_exception_is_set (ex))
+ return;
+
+ /* find out the storage path, base url */
+ nntp_store->storage_path = camel_session_get_storage_path (session, service, ex);
+ if (!nntp_store->storage_path)
+ return;
+
+ /* FIXME */
+ nntp_store->base_url = camel_url_to_string (service->url, (CAMEL_URL_HIDE_PASSWORD |
+ CAMEL_URL_HIDE_PARAMS |
+ CAMEL_URL_HIDE_AUTH));
+
+ tmp = alloca(strlen(nntp_store->storage_path)+32);
+ sprintf(tmp, "%s/.ev-store-summary", nntp_store->storage_path);
+ nntp_store->summary = camel_nntp_store_summary_new();
+ camel_store_summary_set_filename((CamelStoreSummary *)nntp_store->summary, tmp);
+ summary_url = camel_url_new(nntp_store->base_url, NULL);
+ camel_store_summary_set_uri_base((CamelStoreSummary *)nntp_store->summary, summary_url);
+
+ camel_url_free(summary_url);
+ if (camel_store_summary_load((CamelStoreSummary *)nntp_store->summary) == 0);
+
+ /* get options */
+ if (camel_url_get_param (url, "show_short_notation"))
+ nntp_store->do_short_folder_notation = TRUE;
+ else
+ nntp_store->do_short_folder_notation = FALSE;
}

+
static void
nntp_store_init (gpointer object, gpointer klass)
{
@@ -451,14 +908,14 @@

if (camel_nntp_store_type == CAMEL_INVALID_TYPE) {
camel_nntp_store_type =
- camel_type_register (CAMEL_STORE_TYPE,
+ camel_type_register (CAMEL_DISCO_STORE_TYPE,
"CamelNNTPStore",
sizeof (CamelNNTPStore),
sizeof (CamelNNTPStoreClass),
(CamelObjectClassInitFunc) nntp_store_class_init,
NULL,
(CamelObjectInitFunc) nntp_store_init,
- (CamelObjectFinalizeFunc) nntp_store_finalise);
+ (CamelObjectFinalizeFunc) nntp_store_finalize);
}

return camel_nntp_store_type;
@@ -483,6 +940,50 @@
}

static gboolean
+camel_nntp_try_authenticate(CamelNNTPStore *store) {
+ CamelService *service = CAMEL_SERVICE(store);
+ CamelSession *session = camel_service_get_session (service);
+ CamelException *ex;
+ int ret;
+ char *line;
+
+ if (!service->url->user) return FALSE;
+
+ /* if nessecary, prompt for the password */
+ if (!service->url->passwd) {
+ char *prompt;
+ prompt = g_strdup_printf (_("Please enter the NNTP "
+ "password for %s@%s"),
+ service->url->user,
+ service->url->host);
+
+ ex = camel_exception_new();
+ service->url->passwd =
+ camel_session_get_password (session, prompt, FALSE, TRUE,
+ service, "password", ex);
+ camel_exception_free(ex);
+ g_free (prompt);
+
+ if (!service->url->passwd) {
+ return FALSE;
+ }
+ }
+
+ /* now, send auth info (currently, only authinfo user/pass is supported) */
+ ret = camel_nntp_command(store, &line, "authinfo user %s", service->url->user);
+ if (ret == NNTP_AUTH_ACCEPTED)
+ return TRUE;
+ else if (ret == NNTP_AUTH_CONTINUE) {
+ ret = camel_nntp_command(store, &line, "authinfo pass %s", service->url->passwd);
+ if (ret == NNTP_AUTH_ACCEPTED)
+ return TRUE;
+ else
+ return FALSE;
+ } else
+ return FALSE;
+}
Currently, this only supports the AUTINFO USER/PASS method, I do not currently have a news server to throw other authentication methods such as TLS or whatever at...
+
+static gboolean
nntp_connected (CamelNNTPStore *store, CamelException *ex)
{
if (store->stream == NULL)
@@ -514,6 +1015,7 @@
}
camel_nntp_stream_set_mode(store->stream, CAMEL_NNTP_STREAM_LINE);

+command_begin_send:
va_start(ap, fmt);
ps = p = fmt;
while ( (c = *p++) ) {
@@ -567,6 +1069,11 @@

u = strtoul(*line, NULL, 10);

+ /* Check for 'authentication required' codes */
+ if (u == NNTP_AUTH_REQUIRED) {
+ if (camel_nntp_try_authenticate(store)) goto command_begin_send;
+ }
+
I thought nntp_command would be the best place to check for this return code because the NNTP spec says servers might not support logging in initially, and might (re-)prompt for your password at any command.
 	/* Handle all switching to data mode here, to make callers job easier */
if (u == 215 || (u >= 220 && u <=224) || (u >= 230 && u <= 231))
camel_nntp_stream_set_mode(store->stream, CAMEL_NNTP_STREAM_DATA);
--- /home/meilof/cvs/orig/evolution//camel/providers/nntp/camel-nntp-store-summary.c 1969-12-31 19:00:00.000000000 -0500
+++ camel/providers/nntp/camel-nntp-store-summary.c 2004-01-04 17:05:19.000000000 -0500
@@ -0,0 +1,429 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2002 Ximian Inc.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+/* currently, this is just a straigt s/imap/nntp from the IMAP file*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include "camel-nntp-store-summary.h"
+
+#include "camel-file-utils.h"
+
+#include "e-util/md5-utils.h"
+#include "e-util/e-memory.h"
+
+#include "camel-private.h"
+#include "camel-utf8.h"
+
+#define d(x)
+#define io(x) /* io debug */
+
+#define CAMEL_NNTP_STORE_SUMMARY_VERSION_0 (0)
+
+#define CAMEL_NNTP_STORE_SUMMARY_VERSION (0)
+
+#define _PRIVATE(o) (((CamelNntpStoreSummary *)(o))->priv)
+
+static int summary_header_load(CamelStoreSummary *, FILE *);
+static int summary_header_save(CamelStoreSummary *, FILE *);
+
+/*static CamelStoreInfo * store_info_new(CamelStoreSummary *, const char *);*/
+static CamelStoreInfo * store_info_load(CamelStoreSummary *, FILE *);
+static int store_info_save(CamelStoreSummary *, FILE *, CamelStoreInfo *);
+static void store_info_free(CamelStoreSummary *, CamelStoreInfo *);
+
+static const char *store_info_string(CamelStoreSummary *, const CamelStoreInfo *, int);
+static void store_info_set_string(CamelStoreSummary *, CamelStoreInfo *, int, const char *);
+
+static void camel_nntp_store_summary_class_init (CamelNntpStoreSummaryClass *klass);
+static void camel_nntp_store_summary_init (CamelNntpStoreSummary *obj);
+static void camel_nntp_store_summary_finalise (CamelObject *obj);
+
+static CamelStoreSummaryClass *camel_nntp_store_summary_parent;
+
+static void
+camel_nntp_store_summary_class_init (CamelNntpStoreSummaryClass *klass)
+{
+ CamelStoreSummaryClass *ssklass = (CamelStoreSummaryClass *)klass;
+
+ ssklass->summary_header_load = summary_header_load;
+ ssklass->summary_header_save = summary_header_save;
+
+ /*ssklass->store_info_new = store_info_new;*/
+ ssklass->store_info_load = store_info_load;
+ ssklass->store_info_save = store_info_save;
+ ssklass->store_info_free = store_info_free;
+
+ ssklass->store_info_string = store_info_string;
+ ssklass->store_info_set_string = store_info_set_string;
+}
+
+static void
+camel_nntp_store_summary_init (CamelNntpStoreSummary *s)
+{
+ /*struct _CamelNntpStoreSummaryPrivate *p;
+
+ p = _PRIVATE(s) = g_malloc0(sizeof(*p));*/
+
+ ((CamelStoreSummary *)s)->store_info_size = sizeof(CamelNntpStoreInfo);
+ s->version = CAMEL_NNTP_STORE_SUMMARY_VERSION;
+ memset(&s->last_newslist, 0, sizeof(s->last_newslist));
+}
+
+static void
+camel_nntp_store_summary_finalise (CamelObject *obj)
+{
+ /*struct _CamelNntpStoreSummaryPrivate *p;*/
+ /*CamelNntpStoreSummary *s = (CamelNntpStoreSummary *)obj;*/
+
+ /*p = _PRIVATE(obj);
+ g_free(p);*/
+}
+
+CamelType
+camel_nntp_store_summary_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ camel_nntp_store_summary_parent = (CamelStoreSummaryClass *)camel_store_summary_get_type();
+ type = camel_type_register((CamelType)camel_nntp_store_summary_parent, "CamelNntpStoreSummary",
+ sizeof (CamelNntpStoreSummary),
+ sizeof (CamelNntpStoreSummaryClass),
+ (CamelObjectClassInitFunc) camel_nntp_store_summary_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_nntp_store_summary_init,
+ (CamelObjectFinalizeFunc) camel_nntp_store_summary_finalise);
+ }
+
+ return type;
+}
+
+/**
+ * camel_nntp_store_summary_new:
+ *
+ * Create a new CamelNntpStoreSummary object.
+ *
+ * Return value: A new CamelNntpStoreSummary widget.
+ **/
+CamelNntpStoreSummary *
+camel_nntp_store_summary_new (void)
+{
+ CamelNntpStoreSummary *new = CAMEL_NNTP_STORE_SUMMARY ( camel_object_new (camel_nntp_store_summary_get_type ()));
+
+ return new;
+}
+
+/**
+ * camel_nntp_store_summary_full_name:
+ * @s:
+ * @path:
+ *
+ * Retrieve a summary item by full name.
+ *
+ * A referenced to the summary item is returned, which may be
+ * ref'd or free'd as appropriate.
+ *
+ * Return value: The summary item, or NULL if the @full_name name
+ * is not available.
+ * It must be freed using camel_store_summary_info_free().
+ **/
+CamelNntpStoreInfo *
+camel_nntp_store_summary_full_name(CamelNntpStoreSummary *s, const char *full_name)
+{
+ int count, i;
+ CamelNntpStoreInfo *info;
+
+ count = camel_store_summary_count((CamelStoreSummary *)s);
+ for (i=0;i<count;i++) {
+ info = (CamelNntpStoreInfo *)camel_store_summary_index((CamelStoreSummary *)s, i);
+ if (info) {
+ if (strcmp(info->full_name, full_name) == 0)
+ return info;
+ camel_store_summary_info_free((CamelStoreSummary *)s, (CamelStoreInfo *)info);
+ }
+ }
+
+ return NULL;
+}
+
+char *
+camel_nntp_store_summary_full_to_path(CamelNntpStoreSummary *s, const char *full_name, char dir_sep)
+{
+ char *path, *p;
+ int c;
+ const char *f;
+
+ if (dir_sep != '/') {
+ p = path = alloca(strlen(full_name)*3+1);
+ f = full_name;
+ while ( (c = *f++ & 0xff) ) {
+ if (c == dir_sep)
+ *p++ = '/';
+ else if (c == '/' || c == '%')
+ p += sprintf(p, "%%%02X", c);
+ else
+ *p++ = c;
+ }
+ *p = 0;
+ } else
+ path = (char *)full_name;
+
+ return camel_utf7_utf8(path);
+}
+
+static guint32 hexnib(guint32 c)
+{
+ if (c >= '0' && c <= '9')
+ return c-'0';
+ else if (c>='A' && c <= 'Z')
+ return c-'A'+10;
+ else
+ return 0;
+}
+
+char *
+camel_nntp_store_summary_path_to_full(CamelNntpStoreSummary *s, const char *path, char dir_sep)
+{
+ unsigned char *full, *f;
+ guint32 c, v = 0;
+ const char *p;
+ int state=0;
+ char *subpath, *last = NULL;
+ CamelStoreInfo *si;
+
+ /* check to see if we have a subpath of path already defined */
+ subpath = alloca(strlen(path)+1);
+ strcpy(subpath, path);
+ do {
+ si = camel_store_summary_path((CamelStoreSummary *)s, subpath);
+ if (si == NULL) {
+ last = strrchr(subpath, '/');
+ if (last)
+ *last = 0;
+ }
+ } while (si == NULL && last);
+
+ /* path is already present, use the raw version we have */
+ if (si && strlen(subpath) == strlen(path)) {
+ f = g_strdup(camel_nntp_store_info_full_name(s, si));
+ camel_store_summary_info_free((CamelStoreSummary *)s, si);
+ return f;
+ }
+
+ f = full = alloca(strlen(path)*2+1);
+ if (si)
+ p = path + strlen(subpath);
+ else
+ p = path;
+
+ while ( (c = camel_utf8_getc((const unsigned char **)&p)) ) {
+ switch(state) {
+ case 0:
+ if (c == '%')
+ state = 1;
+ else {
+ if (c == '/')
+ c = dir_sep;
+ camel_utf8_putc(&f, c);
+ }
+ break;
+ case 1:
+ state = 2;
+ v = hexnib(c)<<4;
+ break;
+ case 2:
+ state = 0;
+ v |= hexnib(c);
+ camel_utf8_putc(&f, v);
+ break;
+ }
+ }
+ camel_utf8_putc(&f, c);
+
+ /* merge old path part if required */
+ f = camel_utf8_utf7(full);
+ if (si) {
+ full = g_strdup_printf("%s%s", camel_nntp_store_info_full_name(s, si), f);
+ g_free(f);
+ camel_store_summary_info_free((CamelStoreSummary *)s, si);
+ f = full;
+ }
+
+ return f;
+}
+
+CamelNntpStoreInfo *
+camel_nntp_store_summary_add_from_full(CamelNntpStoreSummary *s, const char *full, char dir_sep)
+{
+ CamelNntpStoreInfo *info;
+ char *pathu8;
+ int len;
+ char *full_name;
+
+ d(printf("adding full name '%s' '%c'\n", full, dir_sep));
+
+ len = strlen(full);
+ full_name = alloca(len+1);
+ strcpy(full_name, full);
+ if (full_name[len-1] == dir_sep)
+ full_name[len-1] = 0;
+
+ info = camel_nntp_store_summary_full_name(s, full_name);
+ if (info) {
+ camel_store_summary_info_free((CamelStoreSummary *)s, (CamelStoreInfo *)info);
+ d(printf(" already there\n"));
+ return info;
+ }
+
+ pathu8 = camel_nntp_store_summary_full_to_path(s, full_name, dir_sep);
+
+ info = (CamelNntpStoreInfo *)camel_store_summary_add_from_path((CamelStoreSummary *)s, pathu8);
+ if (info) {
+ d(printf(" '%s' -> '%s'\n", pathu8, full_name));
+ camel_store_info_set_string((CamelStoreSummary *)s, (CamelStoreInfo *)info, CAMEL_NNTP_STORE_INFO_FULL_NAME, full_name);
+ } else
+ d(printf(" failed\n"));
+
+ return info;
+}
+
+static int
+summary_header_load(CamelStoreSummary *s, FILE *in)
+{
+ CamelNntpStoreSummary *is = (CamelNntpStoreSummary *)s;
+ gint32 version, nil;
+
+ if (camel_nntp_store_summary_parent->summary_header_load((CamelStoreSummary *)s, in) == -1
+ || camel_file_util_decode_fixed_int32(in, &version) == -1)
+ return -1;
+
+ is->version = version;
+
+ if (version < CAMEL_NNTP_STORE_SUMMARY_VERSION_0) {
+ g_warning("Store summary header version too low");
+ return -1;
+ }
+
+ if (fread(is->last_newslist, 1, NNTP_DATE_SIZE, in) < NNTP_DATE_SIZE) return -1;
+
+ camel_file_util_decode_fixed_int32(in, &nil);
+
+ return 0;
+}
+
+static int
+summary_header_save(CamelStoreSummary *s, FILE *out)
+{
+ CamelNntpStoreSummary *is = (CamelNntpStoreSummary *)s;
+
+ /* always write as latest version */
+ if (camel_nntp_store_summary_parent->summary_header_save((CamelStoreSummary *)s, out) == -1
+ || camel_file_util_encode_fixed_int32(out, CAMEL_NNTP_STORE_SUMMARY_VERSION) == -1
+ || fwrite(is->last_newslist, 1, NNTP_DATE_SIZE, out) < NNTP_DATE_SIZE
+ || camel_file_util_encode_fixed_int32(out, 0) == -1)
+ return -1;
+
+ return 0;
+}
+
+static CamelStoreInfo *
+store_info_load(CamelStoreSummary *s, FILE *in)
+{
+ CamelNntpStoreInfo *ni;
+
+ ni = (CamelNntpStoreInfo *)camel_nntp_store_summary_parent->store_info_load(s, in);
+ if (ni) {
+ if (camel_file_util_decode_string(in, &ni->full_name) == -1) {
+ camel_store_summary_info_free(s, (CamelStoreInfo *)ni);
+ ni = NULL;
+ }
+ }
+
+ return (CamelStoreInfo *)ni;
+}
+
+static int
+store_info_save(CamelStoreSummary *s, FILE *out, CamelStoreInfo *mi)
+{
+ CamelNntpStoreInfo *isi = (CamelNntpStoreInfo *)mi;
+
+ if (camel_nntp_store_summary_parent->store_info_save(s, out, mi) == -1
+ || camel_file_util_encode_string(out, isi->full_name) == -1)
+ return -1;
+
+ return 0;
+}
+
+static void
+store_info_free(CamelStoreSummary *s, CamelStoreInfo *mi)
+{
+ CamelNntpStoreInfo *nsi = (CamelNntpStoreInfo *)mi;
+
+ g_free(nsi->full_name);
+ camel_nntp_store_summary_parent->store_info_free(s, mi);
+}
+
+static const char *
+store_info_string(CamelStoreSummary *s, const CamelStoreInfo *mi, int type)
+{
+ CamelNntpStoreInfo *isi = (CamelNntpStoreInfo *)mi;
+
+ /* FIXME: Locks? */
+
+ g_assert (mi != NULL);
+
+ switch (type) {
+ case CAMEL_NNTP_STORE_INFO_FULL_NAME:
+ return isi->full_name;
+ default:
+ return camel_nntp_store_summary_parent->store_info_string(s, mi, type);
+ }
+}
+
+static void
+store_info_set_string(CamelStoreSummary *s, CamelStoreInfo *mi, int type, const char *str)
+{
+ CamelNntpStoreInfo *isi = (CamelNntpStoreInfo *)mi;
+
+ g_assert(mi != NULL);
+
+ switch(type) {
+ case CAMEL_NNTP_STORE_INFO_FULL_NAME:
+ d(printf("Set full name %s -> %s\n", isi->full_name, str));
+ CAMEL_STORE_SUMMARY_LOCK(s, summary_lock);
+ g_free(isi->full_name);
+ isi->full_name = g_strdup(str);
+ CAMEL_STORE_SUMMARY_UNLOCK(s, summary_lock);
+ break;
+ default:
+ camel_nntp_store_summary_parent->store_info_set_string(s, mi, type, str);
+ break;
+ }
+}
Most of this is just a straight grep from imap which I haven't looked really carefully at, so it might not be entirely correct, though it does seem to work well.
--- /home/meilof/cvs/orig/evolution//camel/providers/nntp/camel-nntp-summary.c	2003-09-18 13:07:07.000000000 -0400
+++ camel/providers/nntp/camel-nntp-summary.c 2004-01-04 17:05:19.000000000 -0500
@@ -247,9 +247,6 @@
f = strtoul(line, &line, 10);
l = strtoul(line, &line, 10);

- dd(printf("nntp_summary: got last '%u' first '%u'\n"
- "nntp_summary: high '%u' low '%u'\n", l, f, cns->high, cns->low));
-
if (cns->low == f && cns->high == l) {
dd(printf("nntp_summary: no work to do!\n"));
return 0;
@@ -301,6 +298,9 @@

camel_folder_summary_touch(s);

+ /* TODO: not from here */
+ camel_folder_summary_save(s);
+
return ret;
}

--- /home/meilof/cvs/orig/evolution//camel/providers/nntp/camel-nntp-folder.h 2001-11-29 22:09:38.000000000 -0500
+++ camel/providers/nntp/camel-nntp-folder.h 2004-01-04 17:05:19.000000000 -0500
@@ -33,6 +33,7 @@
#endif /* __cplusplus }*/

#include "camel/camel-folder.h"
+#include "camel/camel-disco-folder.h"

/* #include "camel-store.h" */

@@ -42,7 +43,7 @@
#define CAMEL_IS_NNTP_FOLDER(o) (CAMEL_CHECK_TYPE((o), CAMEL_NNTP_FOLDER_TYPE))

typedef struct _CamelNNTPFolder {
- CamelFolder parent;
+ CamelDiscoFolder parent;

struct _CamelNNTPFolderPrivate *priv;

@@ -52,7 +53,7 @@
} CamelNNTPFolder;

typedef struct _CamelNNTPFolderClass {
- CamelFolderClass parent;
+ CamelDiscoFolderClass parent;

/* Virtual methods */

--- /home/meilof/cvs/orig/evolution//camel/providers/nntp/camel-nntp-grouplist.h 2001-10-27 12:59:31.000000000 -0400
+++ camel/providers/nntp/camel-nntp-grouplist.h 2004-01-04 17:05:19.000000000 -0500
@@ -40,6 +40,9 @@
GList *group_list;
};

+typedef struct CamelNNTPGroupList CamelNNTPGroupList;
+typedef struct CamelNNTPGroupListEntry CamelNNTPGroupListEntry;
+
CamelNNTPGroupList* camel_nntp_grouplist_fetch (CamelNNTPStore *store, CamelException *ex);
gint camel_nntp_grouplist_update (CamelNNTPGroupList *group_list, CamelException *ex);
void camel_nntp_grouplist_save (CamelNNTPGroupList *group_list, CamelException *ex);
--- /home/meilof/cvs/orig/evolution//camel/providers/nntp/camel-nntp-store.h 2002-05-27 23:00:31.000000000 -0400
+++ camel/providers/nntp/camel-nntp-store.h 2004-01-04 17:05:19.000000000 -0500
@@ -36,7 +36,11 @@
#include <camel/camel-exception.h>
#include <camel/camel-folder.h>

+#include <camel/camel-disco-store.h>
+#include <camel/camel-disco-folder.h>
+
#include "camel-nntp-stream.h"
+#include "camel-nntp-store-summary.h"

#define CAMEL_NNTP_STORE_TYPE (camel_nntp_store_get_type ())
#define CAMEL_NNTP_STORE(obj) (CAMEL_CHECK_CAST((obj), CAMEL_NNTP_STORE_TYPE, CamelNNTPStore))
@@ -55,25 +59,30 @@
typedef struct _CamelNNTPStore CamelNNTPStore;
typedef struct _CamelNNTPStoreClass CamelNNTPStoreClass;

+#include "camel-nntp-grouplist.h"
+
struct _CamelNNTPStore {
- CamelStore parent_object;
+ CamelDiscoStore parent_object;

struct _CamelNNTPStorePrivate *priv;

guint32 extensions;

gboolean posting_allowed;
+ gboolean do_short_folder_notation;
+
+ CamelNntpStoreSummary *summary;

CamelNNTPStream *stream;
CamelStreamMem *mem;

CamelDataCache *cache;

- char *current_folder;
+ char *current_folder, *storage_path, *base_url;
};

struct _CamelNNTPStoreClass {
- CamelStoreClass parent_class;
+ CamelDiscoStoreClass parent_class;

};

--- /home/meilof/cvs/orig/evolution//camel/providers/nntp/camel-nntp-store-summary.h 1969-12-31 19:00:00.000000000 -0500
+++ camel/providers/nntp/camel-nntp-store-summary.h 2004-01-04 17:05:19.000000000 -0500
@@ -0,0 +1,100 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2002 Ximian Inc.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+/* currently, this is just a straigt s/imap/nntp from the IMAP file*/
+
+
+#ifndef _CAMEL_NNTP_STORE_SUMMARY_H
+#define _CAMEL_NNTP_STORE_SUMMARY_H
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+#include <camel/camel-object.h>
+#include <camel/camel-store-summary.h>
+
+#define CAMEL_NNTP_STORE_SUMMARY(obj) CAMEL_CHECK_CAST (obj, camel_nntp_store_summary_get_type (), CamelNntpStoreSummary)
+#define CAMEL_NNTP_STORE_SUMMARY_CLASS(klass) CAMEL_CHECK_CLASS_CAST (klass, camel_nntp_store_summary_get_type (), CamelNntpStoreSummaryClass)
+#define CAMEL_IS_NNTP_STORE_SUMMARY(obj) CAMEL_CHECK_TYPE (obj, camel_nntp_store_summary_get_type ())
+
+typedef struct _CamelNntpStoreSummary CamelNntpStoreSummary;
+typedef struct _CamelNntpStoreSummaryClass CamelNntpStoreSummaryClass;
+
+typedef struct _CamelNntpStoreInfo CamelNntpStoreInfo;
+
+enum {
+ CAMEL_NNTP_STORE_INFO_FULL_NAME = CAMEL_STORE_INFO_LAST,
+ CAMEL_NNTP_STORE_INFO_LAST,
+};
+
+struct _CamelNntpStoreInfo {
+ CamelStoreInfo info;
+ char *full_name;
+};
+
+#define NNTP_DATE_SIZE 14
+
+struct _CamelNntpStoreSummary {
+ CamelStoreSummary summary;
+
+ struct _CamelNntpStoreSummaryPrivate *priv;
+
+ /* header info */
+ guint32 version; /* version of base part of file */
+ char last_newslist[NNTP_DATE_SIZE];
+};
+
+struct _CamelNntpStoreSummaryClass {
+ CamelStoreSummaryClass summary_class;
+};
+
+CamelType camel_nntp_store_summary_get_type (void);
+CamelNntpStoreSummary *camel_nntp_store_summary_new (void);
+
+/* TODO: this api needs some more work, needs to support lists */
+/*CamelNntpStoreNamespace *camel_nntp_store_summary_namespace_new(CamelNntpStoreSummary *s, const char *full_name, char dir_sep);*/
+/*void camel_nntp_store_summary_namespace_set(CamelNntpStoreSummary *s, CamelNntpStoreNamespace *ns);*/
+/*CamelNntpStoreNamespace *camel_nntp_store_summary_namespace_find_path(CamelNntpStoreSummary *s, const char *path);*/
+/*CamelNntpStoreNamespace *camel_nntp_store_summary_namespace_find_full(CamelNntpStoreSummary *s, const char *full_name);*/
+
+/* helper macro's */
+#define camel_nntp_store_info_full_name(s, i) (camel_store_info_string((CamelStoreSummary *)s, (const CamelStoreInfo *)i, CAMEL_NNTP_STORE_INFO_FULL_NAME))
+
+/* converts to/from utf8 canonical nasmes */
+char *camel_nntp_store_summary_full_to_path(CamelNntpStoreSummary *s, const char *full_name, char dir_sep);
+
+char *camel_nntp_store_summary_path_to_full(CamelNntpStoreSummary *s, const char *path, char dir_sep);
+char *camel_nntp_store_summary_dotted_to_full(CamelNntpStoreSummary *s, const char *dotted, char dir_sep);
+
+CamelNntpStoreInfo *camel_nntp_store_summary_full_name(CamelNntpStoreSummary *s, const char *full_name);
+CamelNntpStoreInfo *camel_nntp_store_summary_add_from_full(CamelNntpStoreSummary *s, const char *full_name, char dir_sep);
+
+/* a convenience lookup function. always use this if path known */
+char *camel_nntp_store_summary_full_from_path(CamelNntpStoreSummary *s, const char *path);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* ! _CAMEL_NNTP_STORE_SUMMARY_H */
--- /home/meilof/cvs/orig/evolution//camel/providers/nntp/camel-nntp-summary.h 2002-05-07 03:31:25.000000000 -0400
+++ camel/providers/nntp/camel-nntp-summary.h 2004-01-04 17:05:19.000000000 -0500
@@ -24,7 +24,6 @@
#include <camel/camel-folder-summary.h>
#include <camel/camel-folder.h>
#include <camel/camel-exception.h>
-#include <libibex/ibex.h>

#define CAMEL_NNTP_SUMMARY(obj) CAMEL_CHECK_CAST (obj, camel_nntp_summary_get_type (), CamelNNTPSummary)
#define CAMEL_NNTP_SUMMARY_CLASS(klass) CAMEL_CHECK_CLASS_CAST (klass, camel_nntp_summary_get_type (), CamelNNTPSummaryClass)
--- /home/meilof/cvs/orig/evolution//camel/providers/nntp/Makefile.am 2003-12-06 13:04:07.000000000 -0500
+++ camel/providers/nntp/Makefile.am 2004-01-02 22:32:05.000000000 -0500
@@ -6,6 +6,7 @@
camel_provider_DATA = libcamelnntp.urls

INCLUDES = -I../.. \
+ -I$(top_srcdir) \
-I$(top_srcdir)/camel \
-I$(top_srcdir)/intl \
-I$(top_srcdir)/e-util \
@@ -20,13 +21,15 @@
camel-nntp-store.c \
camel-nntp-folder.c \
camel-nntp-stream.c \
- camel-nntp-summary.c
+ camel-nntp-summary.c \
+ camel-nntp-store-summary.c

libcamelnntpinclude_HEADERS = \
camel-nntp-store.h \
camel-nntp-folder.h \
camel-nntp-stream.h \
- camel-nntp-summary.h
+ camel-nntp-summary.h \
+ camel-nntp-store-summary.h

noinst_HEADERS = \
camel-nntp-private.h

evolution/composer

My approach at cross-posting to mail and news groups is roughly this:
--- /home/meilof/cvs/orig/evolution//composer/e-msg-composer.c	2003-12-01 10:20:49.000000000 -0500
+++ composer/e-msg-composer.c 2004-01-04 17:04:06.000000000 -0500
@@ -1869,11 +1869,37 @@
{
if (type != Bonobo_UIComponent_STATE_CHANGED)
return;
-
+
e_msg_composer_set_view_replyto (E_MSG_COMPOSER (user_data), atoi (state));
}
Sorry 'bout that...
 
static void
+menu_view_to_cb (BonoboUIComponent *component,
+ const char *path,
+ Bonobo_UIComponent_EventType type,
+ const char *state,
+ gpointer user_data)
+{
+ if (type != Bonobo_UIComponent_STATE_CHANGED)
+ return;
+
+ e_msg_composer_set_view_to (E_MSG_COMPOSER (user_data), atoi (state));
+}
+
+static void
+menu_view_postto_cb (BonoboUIComponent *component,
+ const char *path,
+ Bonobo_UIComponent_EventType type,
+ const char *state,
+ gpointer user_data)
+{
+ if (type != Bonobo_UIComponent_STATE_CHANGED)
+ return;
+
+ e_msg_composer_set_view_postto (E_MSG_COMPOSER (user_data), atoi (state));
+}
+
+static void
menu_view_cc_cb (BonoboUIComponent *component,
const char *path,
Bonobo_UIComponent_EventType type,
@@ -2119,6 +2145,22 @@
composer->uic, "ViewReplyTo",
menu_view_replyto_cb, composer);

+ /* View/To */
+ bonobo_ui_component_set_prop (
+ composer->uic, "/commands/ViewTo",
+ "state", composer->view_to ? "1" : "0", NULL);
+ bonobo_ui_component_add_listener (
+ composer->uic, "ViewTo",
+ menu_view_to_cb, composer);
+
+ /* View/PostTo */
+ bonobo_ui_component_set_prop (
+ composer->uic, "/commands/ViewPostTo",
+ "state", composer->view_postto ? "1" : "0", NULL);
+ bonobo_ui_component_add_listener (
+ composer->uic, "ViewPostTo",
+ menu_view_postto_cb, composer);
+
/* View/CC */
bonobo_ui_component_set_prop (
composer->uic, "/commands/ViewCC",
@@ -2718,7 +2760,7 @@
}

static void
-e_msg_composer_load_config (EMsgComposer *composer)
+e_msg_composer_load_config (EMsgComposer *composer, int visible_mask)
{
GConfClient *gconf;

@@ -2726,14 +2768,28 @@

composer->view_from = gconf_client_get_bool (
gconf, "/apps/evolution/mail/composer/view/From", NULL);
- composer->view_replyto = gconf_client_get_bool (
+ composer->view_replyto = gconf_client_get_bool (
gconf, "/apps/evolution/mail/composer/view/ReplyTo", NULL);
+ composer->view_to = gconf_client_get_bool (
+ gconf, "/apps/evolution/mail/composer/view/To", NULL);
+ composer->view_postto = gconf_client_get_bool (
+ gconf, "/apps/evolution/mail/composer/view/PostTo", NULL);
composer->view_cc = gconf_client_get_bool (
gconf, "/apps/evolution/mail/composer/view/Cc", NULL);
composer->view_bcc = gconf_client_get_bool (
gconf, "/apps/evolution/mail/composer/view/Bcc", NULL);
composer->view_subject = gconf_client_get_bool (
gconf, "/apps/evolution/mail/composer/view/Subject", NULL);
+
+ /* initially, set these values to FALSE */
+ if (!(visible_mask & E_MSG_COMPOSER_VISIBLE_TO))
+ composer->view_to = FALSE;
+ if (!(visible_mask & E_MSG_COMPOSER_VISIBLE_CC))
+ composer->view_cc = FALSE;
+ if (!(visible_mask & E_MSG_COMPOSER_VISIBLE_BCC))
+ composer->view_bcc = FALSE;
+ if (!(visible_mask & E_MSG_COMPOSER_VISIBLE_POSTTO))
+ composer->view_postto = FALSE;

g_object_unref (gconf);
}
This code makes all headers that are not in the mask invisible by default.
@@ -2747,6 +2803,10 @@
flags |= E_MSG_COMPOSER_VISIBLE_FROM;
if (composer->view_replyto)
flags |= E_MSG_COMPOSER_VISIBLE_REPLYTO;
+ if (composer->view_to)
+ flags |= E_MSG_COMPOSER_VISIBLE_TO;
+ if (composer->view_postto)
+ flags |= E_MSG_COMPOSER_VISIBLE_POSTTO;
if (composer->view_cc)
flags |= E_MSG_COMPOSER_VISIBLE_CC;
if (composer->view_bcc)
@@ -2948,7 +3008,7 @@
drop_types, num_drop_types, GDK_ACTION_COPY);
g_signal_connect (composer, "drag_data_received",
G_CALLBACK (drag_data_received), NULL);
- e_msg_composer_load_config (composer);
+ e_msg_composer_load_config (composer, visible_mask);

setup_ui (composer);

@@ -3156,6 +3216,33 @@
return new;
}

+/**
+ * e_msg_composer_new_mail_post:
+ *
+ * Create a new message composer widget.
+ *
+ * Return value: A pointer to the newly created widget
+ **/
+EMsgComposer *
+e_msg_composer_new_mail_post (void)
+{
+ gboolean send_html;
+ GConfClient *gconf;
+ EMsgComposer *new;
+
+ gconf = gconf_client_get_default ();
+ send_html = gconf_client_get_bool (gconf, "/apps/evolution/mail/composer/send_html", NULL);
+ g_object_unref (gconf);
+
+ new = create_composer (E_MSG_COMPOSER_VISIBLE_MASK_MAIL | E_MSG_COMPOSER_VISIBLE_MASK_POST);
+ if (new) {
+ e_msg_composer_set_send_html (new, send_html);
+ set_editor_text (new, "");
+ set_editor_signature (new);
+ }
+
+ return new;
+}
When "Reply-all"'ing to a message, you'll want /both/ mail and news fields, thus this composer constructor.
 
static gboolean
is_special_header (const char *hdr_name)
@@ -4649,16 +4736,16 @@
/**
* e_msg_composer_get_view_replyto:
* @composer: A message composer widget
- *
+ *
* Get the status of the "View Reply-To header" flag.
- *
+ *
* Return value: The status of the "View Reply-To header" flag.
**/
gboolean
e_msg_composer_get_view_replyto (EMsgComposer *composer)
{
g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
-
+
return composer->view_replyto;
}

@@ -4674,27 +4761,130 @@
e_msg_composer_set_view_replyto (EMsgComposer *composer, gboolean view_replyto)
{
GConfClient *gconf;
-
+
g_return_if_fail (E_IS_MSG_COMPOSER (composer));
-
+
if ((composer->view_replyto && view_replyto) ||
(!composer->view_replyto && !view_replyto))
return;
-
+
composer->view_replyto = view_replyto;
bonobo_ui_component_set_prop (composer->uic, "/commands/ViewReplyTo",
"state", composer->view_replyto ? "1" : "0", NULL);
-
+
+ /* we do this /only/ if the fields is in the visible_mask */
gconf = gconf_client_get_default ();
gconf_client_set_bool (gconf, "/apps/evolution/mail/composer/view/ReplyTo", view_replyto, NULL);
g_object_unref (gconf);
-
+
e_msg_composer_hdrs_set_visible (E_MSG_COMPOSER_HDRS (composer->hdrs),
e_msg_composer_get_visible_flags (composer));
}


/**
+ * e_msg_composer_get_view_to:
+ * @composer: A message composer widget
+ *
+ * Get the status of the "View To header" flag.
+ *
+ * Return value: The status of the "View To header" flag.
+ **/
+gboolean
+e_msg_composer_get_view_to (EMsgComposer *composer)
+{
+ g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
+
+ return composer->view_to;
+}
+
+
+/**
+ * e_msg_composer_set_view_to:
+ * @composer: A message composer widget
+ * @state: whether to show or hide the To selector
+ *
+ * Controls the state of the To selector
+ */
+void
+e_msg_composer_set_view_to (EMsgComposer *composer, gboolean view_to)
+{
+ GConfClient *gconf;
+
+ g_return_if_fail (E_IS_MSG_COMPOSER (composer));
+
+ if ((composer->view_to && view_to) ||
+ (!composer->view_to && !view_to))
+ return;
+
+ composer->view_to = view_to;
+ bonobo_ui_component_set_prop (composer->uic, "/commands/ViewTo",
+ "state", composer->view_to ? "1" : "0", NULL);
+
+ if ((E_MSG_COMPOSER_HDRS(composer->hdrs))->visible_mask & E_MSG_COMPOSER_VISIBLE_TO) {
+ gconf = gconf_client_get_default ();
+ gconf_client_set_bool (gconf, "/apps/evolution/mail/composer/view/To", view_to, NULL);
+ g_object_unref (gconf);
+ }
+
+ e_msg_composer_hdrs_set_visible (E_MSG_COMPOSER_HDRS (composer->hdrs),
+ e_msg_composer_get_visible_flags (composer));
+}
+
+
+
+/**
+ * e_msg_composer_get_view_postto:
+ * @composer: A message composer widget
+ *
+ * Get the status of the "View PostTo header" flag.
+ *
+ * Return value: The status of the "View PostTo header" flag.
+ **/
+gboolean
+e_msg_composer_get_view_postto (EMsgComposer *composer)
+{
+ g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
+
+ return composer->view_postto;
+}
+
+
+/**
+ * e_msg_composer_set_view_postto:
+ * @composer: A message composer widget
+ * @state: whether to show or hide the PostTo selector
+ *
+ * Controls the state of the PostTo selector
+ */
+void
+e_msg_composer_set_view_postto (EMsgComposer *composer, gboolean view_postto)
+{
+ GConfClient *gconf;
+
+ g_return_if_fail (E_IS_MSG_COMPOSER (composer));
+
+ if ((composer->view_postto && view_postto) ||
+ (!composer->view_postto && !view_postto))
+ return;
+
+ composer->view_postto = view_postto;
+ bonobo_ui_component_set_prop (composer->uic, "/commands/ViewPostTo",
+ "state", composer->view_postto ? "1" : "0", NULL);
+
+ if ((E_MSG_COMPOSER_HDRS(composer->hdrs))->visible_mask & E_MSG_COMPOSER_VISIBLE_POSTTO) {
+ gconf = gconf_client_get_default ();
+ gconf_client_set_bool (gconf, "/apps/evolution/mail/composer/view/PostTo", view_postto, NULL);
+ g_object_unref (gconf);
+ }
+
+ e_msg_composer_hdrs_set_visible (E_MSG_COMPOSER_HDRS (composer->hdrs),
+ e_msg_composer_get_visible_flags (composer));
+}
+
+
+
+/**
* e_msg_composer_get_view_cc:
* @composer: A message composer widget
*
@@ -4733,9 +4923,11 @@
bonobo_ui_component_set_prop (composer->uic, "/commands/ViewCC",
"state", composer->view_cc ? "1" : "0", NULL);

- gconf = gconf_client_get_default ();
- gconf_client_set_bool (gconf, "/apps/evolution/mail/composer/view/Cc", view_cc, NULL);
- g_object_unref (gconf);
+ if ((E_MSG_COMPOSER_HDRS(composer->hdrs))->visible_mask & E_MSG_COMPOSER_VISIBLE_CC) {
+ gconf = gconf_client_get_default ();
+ gconf_client_set_bool (gconf, "/apps/evolution/mail/composer/view/Cc", view_cc, NULL);
+ g_object_unref (gconf);
+ }
This makes sure the preference is only stored if the item is in the visible_mask.
 	
e_msg_composer_hdrs_set_visible (E_MSG_COMPOSER_HDRS (composer->hdrs),
e_msg_composer_get_visible_flags (composer));
@@ -4781,9 +4973,11 @@
bonobo_ui_component_set_prop (composer->uic, "/commands/ViewBCC",
"state", composer->view_bcc ? "1" : "0", NULL);

- gconf = gconf_client_get_default ();
- gconf_client_set_bool (gconf, "/apps/evolution/mail/composer/view/Bcc", view_bcc, NULL);
- g_object_unref (gconf);
+ if ((E_MSG_COMPOSER_HDRS(composer->hdrs))->visible_mask & E_MSG_COMPOSER_VISIBLE_BCC) {
+ gconf = gconf_client_get_default ();
+ gconf_client_set_bool (gconf, "/apps/evolution/mail/composer/view/Bcc", view_bcc, NULL);
+ g_object_unref (gconf);
+ }

e_msg_composer_hdrs_set_visible (E_MSG_COMPOSER_HDRS (composer->hdrs),
e_msg_composer_get_visible_flags (composer));
--- /home/meilof/cvs/orig/evolution//composer/e-msg-composer.h 2003-12-01 10:20:49.000000000 -0500
+++ composer/e-msg-composer.h 2004-01-04 17:04:06.000000000 -0500
@@ -94,6 +94,8 @@
guint32 smime_encrypt : 1;
guint32 view_from : 1;
guint32 view_replyto : 1;
+ guint32 view_to : 1;
+ guint32 view_postto : 1;
guint32 view_bcc : 1;
guint32 view_cc : 1;
guint32 view_subject : 1;
@@ -125,6 +127,7 @@

EMsgComposer *e_msg_composer_new (void);
EMsgComposer *e_msg_composer_new_post (void);
+EMsgComposer *e_msg_composer_new_mail_post (void);

EMsgComposer *e_msg_composer_new_with_message (CamelMimeMessage *msg);
EMsgComposer *e_msg_composer_new_from_url (const char *url);
@@ -164,9 +167,15 @@
gboolean e_msg_composer_get_view_from (EMsgComposer *composer);
void e_msg_composer_set_view_from (EMsgComposer *composer,
gboolean view_from);
+gboolean e_msg_composer_get_view_to (EMsgComposer *composer);
+void e_msg_composer_set_view_to (EMsgComposer *composer,
+ gboolean view_replyto);
gboolean e_msg_composer_get_view_replyto (EMsgComposer *composer);
void e_msg_composer_set_view_replyto (EMsgComposer *composer,
gboolean view_replyto);
+gboolean e_msg_composer_get_view_postto (EMsgComposer *composer);
+void e_msg_composer_set_view_postto (EMsgComposer *composer,
+ gboolean view_replyto);
gboolean e_msg_composer_get_view_cc (EMsgComposer *composer);
void e_msg_composer_set_view_cc (EMsgComposer *composer,
gboolean view_cc);
--- /home/meilof/cvs/orig/evolution//composer/e-msg-composer-hdrs.c 2003-12-04 12:24:23.000000000 -0500
+++ composer/e-msg-composer-hdrs.c 2004-01-04 17:04:06.000000000 -0500
@@ -579,7 +579,7 @@
static void
set_pair_visibility (EMsgComposerHdrs *h, EMsgComposerHdrPair *pair, int visible)
{
- if (visible & h->visible_mask) {
+ if (visible/* & h->visible_mask*/) {
Not make it being in the visible_mask a requirement for showing the header....
 		gtk_widget_show (pair->label);
gtk_widget_show (pair->entry);
} else {
@@ -611,11 +611,19 @@
static void
headers_set_sensitivity (EMsgComposerHdrs *h)
{
- bonobo_ui_component_set_prop (
+/* bonobo_ui_component_set_prop (
h->priv->uic, "/commands/ViewFrom", "sensitive",
h->visible_mask & E_MSG_COMPOSER_VISIBLE_FROM ? "1" : "0", NULL);

bonobo_ui_component_set_prop (
+ h->priv->uic, "/commands/ViewTo", "sensitive",
+ h->visible_mask & E_MSG_COMPOSER_VISIBLE_TO ? "1" : "0", NULL);
+
+ bonobo_ui_component_set_prop (
+ h->priv->uic, "/commands/ViewPostTo", "sensitive",
+ h->visible_mask & E_MSG_COMPOSER_VISIBLE_POSTTO ? "1" : "0", NULL);
+
+ bonobo_ui_component_set_prop (
h->priv->uic, "/commands/ViewReplyTo", "sensitive",
h->visible_mask & E_MSG_COMPOSER_VISIBLE_REPLYTO ? "1" : "0", NULL);

@@ -625,7 +633,7 @@

bonobo_ui_component_set_prop (
h->priv->uic, "/commands/ViewBCC", "sensitive",
- h->visible_mask & E_MSG_COMPOSER_VISIBLE_BCC ? "1" : "0", NULL);
+ h->visible_mask & E_MSG_COMPOSER_VISIBLE_BCC ? "1" : "0", NULL); */
}
We want the user to be able to toggle /all/ menu items.
 
void
--- /home/meilof/cvs/orig/evolution//composer/e-msg-composer-hdrs.h 2003-12-01 17:14:31.000000000 -0500
+++ composer/e-msg-composer-hdrs.h 2004-01-04 17:04:06.000000000 -0500
@@ -82,7 +82,7 @@
E_MSG_COMPOSER_VISIBLE_CC = (1 << 3),
E_MSG_COMPOSER_VISIBLE_BCC = (1 << 4),
E_MSG_COMPOSER_VISIBLE_POSTTO = (1 << 5), /* for posting to folders */
- E_MSG_COMPOSER_VISIBLE_NEWSGROUP = (1 << 6), /* for posting to newsgroups */
+ /*E_MSG_COMPOSER_VISIBLE_NEWSGROUP = (1 << 6),*/ /* for posting to newsgroups */
E_MSG_COMPOSER_VISIBLE_SUBJECT = (1 << 7)
} EMsgComposerHeaderVisibleFlags;

@@ -91,7 +91,7 @@
#define E_MSG_COMPOSER_VISIBLE_MASK_RECIPIENTS (E_MSG_COMPOSER_VISIBLE_TO | E_MSG_COMPOSER_VISIBLE_CC | E_MSG_COMPOSER_VISIBLE_BCC)

#define E_MSG_COMPOSER_VISIBLE_MASK_MAIL (E_MSG_COMPOSER_VISIBLE_MASK_BASIC | E_MSG_COMPOSER_VISIBLE_MASK_RECIPIENTS)
-#define E_MSG_COMPOSER_VISIBLE_MASK_NEWS (E_MSG_COMPOSER_VISIBLE_MASK_BASIC | E_MSG_COMPOSER_VISIBLE_NEWSGROUP)
+/*#define E_MSG_COMPOSER_VISIBLE_MASK_NEWS (E_MSG_COMPOSER_VISIBLE_MASK_BASIC | E_MSG_COMPOSER_VISIBLE_NEWSGROUP)*/
Since news uses post, this has no meaning.
 #define E_MSG_COMPOSER_VISIBLE_MASK_POST (E_MSG_COMPOSER_VISIBLE_MASK_BASIC | E_MSG_COMPOSER_VISIBLE_POSTTO)


evolution/mail

--- /home/meilof/cvs/orig/evolution//mail/em-composer-utils.c	2003-12-02 00:16:21.000000000 -0500
+++ mail/em-composer-utils.c 2004-01-04 17:04:35.000000000 -0500
@@ -231,7 +231,7 @@
}

static CamelMimeMessage *
-composer_get_message (EMsgComposer *composer, gboolean post, gboolean save_html_object_data)
+composer_get_message (EMsgComposer *composer, gboolean post, gboolean save_html_object_data, gboolean *no_recipients)
{
CamelMimeMessage *message = NULL;
EABDestination **recipients, **recipients_bcc;
@@ -296,10 +296,14 @@
camel_object_unref (cia);

/* I'm sensing a lack of love, er, I mean recipients. */
- if (num == 0 && !post) {
- e_notice ((GtkWindow *) composer, GTK_MESSAGE_WARNING,
- _("You must specify recipients in order to send this message."));
- goto finished;
+ if (num == 0) {
+ if (post) {
+ if (no_recipients) *no_recipients = TRUE;
+ } else {
+ e_notice ((GtkWindow *) composer, GTK_MESSAGE_WARNING,
+ _("You must specify recipients in order to send this message."));
+ goto finished;
+ }
}
If we're posting, we don't want the fact that there are no mail recipients to spoil everything for us.
 	
if (num > 0 && (num == num_bcc || shown == 0)) {
@@ -389,55 +393,79 @@
CamelMimeMessage *message;
CamelMessageInfo *info;
struct _send_data *send;
- gboolean post = FALSE;
- CamelFolder *folder;
+ /*gboolean post = FALSE;*/
+ gboolean no_recipients = FALSE;
+ CamelFolder *post_folder = NULL, *mail_folder = NULL;
XEvolution *xev;
char *url;

url = e_msg_composer_hdrs_get_post_to ((EMsgComposerHdrs *) composer->hdrs);
if (url && *url) {
- post = TRUE;
-
- mail_msg_wait (mail_get_folder (url, 0, got_post_folder, &folder, mail_thread_new));
-
- if (!folder) {
- g_free (url);
- return;
- }
- } else {
- folder = outbox_folder;
- camel_object_ref (folder);
+ mail_msg_wait (mail_get_folder (url, 0, got_post_folder, &post_folder, mail_thread_new));
}

+ mail_folder = outbox_folder;
+ camel_object_ref (mail_folder);

+
g_free (url);
+
+ if (!post_folder && !mail_folder) return;
If we do posting, we might want to mail as well...
 	
- message = composer_get_message (composer, post, FALSE);
+ message = composer_get_message (composer, post_folder != NULL, FALSE, &no_recipients);
if (!message)
return;
-
- if (post) {
+
+ if (no_recipients) {
+ /* we're doing a post with no recipients */
+ camel_object_unref (mail_folder);
+ mail_folder = NULL;
+ }
+
+ if (mail_folder) {
+ /* mail the message */
+ info = camel_message_info_new ();
+ info->flags = CAMEL_MESSAGE_SEEN;
+
+ send = g_malloc (sizeof (*send));
+ send->emcs = user_data;
+ if (send->emcs)
+ emcs_ref (send->emcs);
+ send->send = FALSE;
+ send->composer = composer;
+ g_object_ref (composer);
+ gtk_widget_hide (GTK_WIDGET (composer));
+
+ e_msg_composer_set_enable_autosave (composer, FALSE);
+
+ mail_append_mail (mail_folder, message, info, composer_send_queued_cb, send);
+ camel_object_unref (mail_folder);
+ }
+
+ if (post_folder) {
/* Remove the X-Evolution* headers if we are in Post-To mode */
xev = mail_tool_remove_xevolution_headers (message);
mail_tool_destroy_xevolution (xev);
+
+ /* mail the message */
+ info = camel_message_info_new ();
+ info->flags = CAMEL_MESSAGE_SEEN;
+
+ send = g_malloc (sizeof (*send));
+ send->emcs = user_data;
+ if (send->emcs)
+ emcs_ref (send->emcs);
+ send->send = TRUE;
+ send->composer = composer;
+ g_object_ref (composer);
+ gtk_widget_hide (GTK_WIDGET (composer));
+
+ e_msg_composer_set_enable_autosave (composer, FALSE);
+
+ mail_append_mail (post_folder, message, info, composer_send_queued_cb, send);
+ camel_object_unref (post_folder);
}
Separate cases for sending mail and news
-	
- info = camel_message_info_new ();
- info->flags = CAMEL_MESSAGE_SEEN;
-
- send = g_malloc (sizeof (*send));
- send->emcs = user_data;
- if (send->emcs)
- emcs_ref (send->emcs);
- send->send = !post;
- send->composer = composer;
- g_object_ref (composer);
- gtk_widget_hide (GTK_WIDGET (composer));
-
- e_msg_composer_set_enable_autosave (composer, FALSE);
-
- mail_append_mail (folder, message, info, composer_send_queued_cb, send);
+
camel_object_unref (message);
- camel_object_unref (folder);
}

struct _save_draft_info {
--- /home/meilof/cvs/orig/evolution//mail/em-subscribe-editor.c 2003-12-02 00:16:22.000000000 -0500
+++ mail/em-subscribe-editor.c 2004-01-04 17:04:35.000000000 -0500
@@ -127,7 +127,6 @@

static void sub_editor_busy(EMSubscribeEditor *se, int dir);
static int sub_queue_fill_level(EMSubscribe *sub, EMSubscribeNode *node);
-static void sub_selection_changed(GtkTreeSelection *selection, EMSubscribe *sub);

static void
sub_node_free(char *key, EMSubscribeNode *node, EMSubscribe *sub)
@@ -195,7 +194,7 @@
camel_store_unsubscribe_folder (m->sub->store, m->node->info->full_name, &mm->ex);
}

-static void
+static void
sub_folder_subscribed (struct _mail_msg *mm)
{
struct _zsubscribe_msg *m = (struct _zsubscribe_msg *)mm, *next;
@@ -503,6 +502,29 @@
gtk_widget_set_sensitive(sub->editor->unsubscribe_button, dounsub);
}

+/* double-clicking causes a node item to be evaluated directly */
+static void sub_row_activated(GtkTreeView *tree, GtkTreePath *path, GtkTreeViewColumn *col, EMSubscribe *sub) {
+ EMSubscribeNode *node;
+ GtkTreeIter iter;
+ GtkTreeModel *model = gtk_tree_view_get_model(tree);
+
+ if (gtk_tree_model_get_iter(model, &iter, path) != TRUE) return;
+
+ gtk_tree_model_get(model, &iter, 2, &node, -1);
+
+ /* check whether the item is already processed */
+ if (node->path == NULL)
+ return;
+
+ /* remove it from wherever in the list it is, and place it in front instead */
+ e_dlist_remove((EDListNode *)node);
+ e_dlist_addhead(&sub->pending, (EDListNode *)node);
+
+ if (sub->pending_id == -1
+ && (node = (EMSubscribeNode *)e_dlist_remtail(&sub->pending)))
+ sub_queue_fill_level(sub, node);
+}
+
static void
sub_row_expanded(GtkTreeView *tree, GtkTreeIter *iter, GtkTreePath *path, EMSubscribe *sub)
{
@@ -621,6 +643,7 @@
gtk_tree_view_set_headers_visible (sub->tree, FALSE);

g_signal_connect(sub->tree, "row-expanded", G_CALLBACK(sub_row_expanded), sub);
+ g_signal_connect(sub->tree, "row-activated", G_CALLBACK(sub_row_activated), sub);
g_signal_connect(sub->tree, "destroy", G_CALLBACK(sub_destroy), sub);

sub_selection_changed(selection, sub);
When you double-click an item in the subscription dialogue, it gets updated immediately. Since the newsgroups list /does/ actually take a while (some 20 seconds on my 1GHZ box for a moderately large list with a debug build of evo) to be loaded, I think this is a very useful addition if you don't want to wait.
--- /home/meilof/cvs/orig/evolution//mail/em-utils.c	2003-12-22 09:50:57.000000000 -0500
+++ mail/em-utils.c 2004-01-04 17:04:35.000000000 -0500
@@ -52,7 +52,7 @@
#include "em-composer-utils.h"
#include "em-format-quote.h"

-static EAccount *guess_account (CamelMimeMessage *message);
+static EAccount *guess_account (CamelMimeMessage *message, CamelFolder *folder);
static void emu_save_part_done (CamelMimePart *part, char *name, int done, void *data);

/**
@@ -647,7 +647,7 @@
while (camel_medium_get_header (CAMEL_MEDIUM (message), "Delivered-To"))
camel_medium_remove_header (CAMEL_MEDIUM (message), "Delivered-To");

- account = guess_account (message);
+ account = guess_account (message, NULL);

composer = e_msg_composer_new_redirect (message, account ? account->name : NULL);

@@ -795,7 +795,8 @@

static EMsgComposer *
reply_get_composer (CamelMimeMessage *message, EAccount *account,
- CamelInternetAddress *to, CamelInternetAddress *cc)
+ CamelInternetAddress *to, CamelInternetAddress *cc,
+ const char *postto)
{
const char *message_id, *references;
EABDestination **tov, **ccv;
@@ -805,8 +806,14 @@
g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
g_return_val_if_fail (to == NULL || CAMEL_IS_INTERNET_ADDRESS (to), NULL);
g_return_val_if_fail (cc == NULL || CAMEL_IS_INTERNET_ADDRESS (cc), NULL);
-
- composer = e_msg_composer_new ();
+
+ if (to || cc) {
+ if (postto)
+ composer = e_msg_composer_new_mail_post ();
+ else
+ composer = e_msg_composer_new();
+ } else
+ composer = e_msg_composer_new_post();

/* construct the tov/ccv */
tov = em_utils_camel_address_to_destination (to);
@@ -826,6 +833,11 @@

g_free (subject);

+ /* add post-to, if nessecary */
+ if (postto) {
+ e_msg_composer_hdrs_set_post_to(E_MSG_COMPOSER_HDRS (composer->hdrs), postto);
+ }
+
/* Add In-Reply-To and References. */
message_id = camel_medium_get_header (CAMEL_MEDIUM (message), "Message-Id");
references = camel_medium_get_header (CAMEL_MEDIUM (message), "References");
@@ -851,14 +863,25 @@
}

static EAccount *
-guess_account (CamelMimeMessage *message)
+guess_account (CamelMimeMessage *message, CamelFolder *folder)
{
const CamelInternetAddress *to, *cc;
GHashTable *account_hash;
EAccount *account = NULL;
const char *addr;
int i;
-
+
+ /* check for newsgroup header */
+ const char *posthdr = camel_medium_get_header (CAMEL_MEDIUM(message), "Newsgroups");
+ char *tmp;
+ if (posthdr && folder) {
+ /* this was posted at a newsgroup! */
+ tmp = camel_url_to_string (CAMEL_SERVICE(folder->parent_store)->url, CAMEL_URL_HIDE_ALL);
+ account = mail_config_get_account_by_source_url(tmp);
+ g_free(tmp);
+ return account;
+ }
+
to = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_TO);
cc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC);

To guess what account an newsgroup message came from, you simply /need/ to have the Folder: the headers don't say anything as the message might have been cross-posted. An other method might be to browse through the news stores and see which one has a subscription to one of the newsgroup the message was sent to, or the NNTP store might add an internal header to each message.... The downside of this method is that the folder information isn't always available.
@@ -891,12 +914,20 @@
}

static void
-get_reply_sender (CamelMimeMessage *message, CamelInternetAddress **to)
+get_reply_sender (CamelMimeMessage *message, CamelInternetAddress **to, const char **postto)
{
const CamelInternetAddress *reply_to;
- const char *name, *addr;
+ const char *name, *addr, *posthdr;
int i;

+ /* check whether there is a 'Newsgroups: ' header in there */
+ posthdr = camel_medium_get_header (CAMEL_MEDIUM(message), "Newsgroups");
+ if (posthdr && postto) {
+ *postto = posthdr;
+ while (**postto == ' ') (*postto)++;
+ }
+ if (postto) return;
+
reply_to = camel_mime_message_get_reply_to (message);
if (!reply_to)
reply_to = camel_mime_message_get_from (message);
@@ -907,6 +938,7 @@
for (i = 0; camel_internet_address_get (reply_to, i, &name, &addr); i++)
camel_internet_address_add (*to, name, addr);
}
+
}
If you do a straight reply to a newsgroup message, you'll reply directly to the newsgroup and not to the sender of the message.
 
static gboolean
@@ -967,13 +999,20 @@
}

static void
-get_reply_all (CamelMimeMessage *message, CamelInternetAddress **to, CamelInternetAddress **cc)
+get_reply_all (CamelMimeMessage *message, CamelInternetAddress **to, CamelInternetAddress **cc, const char **postto)
{
const CamelInternetAddress *reply_to, *to_addrs, *cc_addrs;
- const char *name, *addr;
+ const char *name, *addr, *posthdr;
GHashTable *rcpt_hash;
int i;

+ /* check whether there is a 'Newsgroups: ' header in there */
+ posthdr = camel_medium_get_header (CAMEL_MEDIUM(message), "Newsgroups");
+ if (posthdr && postto) {
+ *postto = posthdr;
+ while (**postto == ' ') (*postto)++;
+ }
+
rcpt_hash = generate_account_hash ();

reply_to = camel_mime_message_get_reply_to (message);
@@ -1079,21 +1118,21 @@
EMsgComposer *composer;
EAccount *account;

- account = guess_account (message);
+ account = guess_account (message, NULL);

switch (mode) {
case REPLY_MODE_SENDER:
- get_reply_sender (message, &to);
+ get_reply_sender (message, &to, NULL);
break;
case REPLY_MODE_LIST:
if (get_reply_list (message, &to))
break;
case REPLY_MODE_ALL:
- get_reply_all (message, &to, &cc);
+ get_reply_all (message, &to, &cc, NULL);
break;
}

- composer = reply_get_composer (message, account, to, cc);
+ composer = reply_get_composer (message, account, to, cc, NULL);
e_msg_composer_add_message_attachments (composer, message, TRUE);

if (to != NULL)
Same as above, but now do add the usual mail fields.
@@ -1114,6 +1153,7 @@
reply_to_message (CamelFolder *folder, const char *uid, CamelMimeMessage *message, void *user_data)
{
CamelInternetAddress *to = NULL, *cc = NULL;
+ const char *postto = NULL;
EMsgComposer *composer;
EAccount *account;
guint32 flags;
@@ -1121,12 +1161,12 @@

mode = GPOINTER_TO_INT (user_data);

- account = guess_account (message);
+ account = guess_account (message, folder);
flags = CAMEL_MESSAGE_ANSWERED | CAMEL_MESSAGE_SEEN;

switch (mode) {
case REPLY_MODE_SENDER:
- get_reply_sender (message, &to);
+ get_reply_sender (message, &to, &postto);
break;
case REPLY_MODE_LIST:
flags |= CAMEL_MESSAGE_ANSWERED_ALL;
@@ -1134,11 +1174,17 @@
break;
case REPLY_MODE_ALL:
flags |= CAMEL_MESSAGE_ANSWERED_ALL;
- get_reply_all (message, &to, &cc);
+ get_reply_all (message, &to, &cc, &postto);
break;
}

- composer = reply_get_composer (message, account, to, cc);
+ /* prepend the folder name to the postto */
+ if (postto) {
+ /* FIXME: is there any /general/ way of doing this? */
+ postto = mail_tools_folder_to_url (folder);
+ }
The message might have been cross-posted, so the "Newsgroups:" header is of no use. Just post to the folder the mesage was in.
+
+ composer = reply_get_composer (message, account, to, cc, postto);
e_msg_composer_add_message_attachments (composer, message, TRUE);

if (to != NULL)
@@ -1187,10 +1233,10 @@
EAccount *account;
guint32 flags;

- account = guess_account (message);
+ account = guess_account (message, folder);
flags = CAMEL_MESSAGE_ANSWERED | CAMEL_MESSAGE_SEEN;

- get_reply_sender (message, &to);
+ get_reply_sender (message, &to, NULL);

composer = e_msg_composer_new_post ();

--- /home/meilof/cvs/orig/evolution//mail/mail-ops.c 2003-12-04 15:31:48.000000000 -0500
+++ mail/mail-ops.c 2004-01-04 17:04:35.000000000 -0500
@@ -908,6 +908,11 @@
g_assert(CAMEL_IS_FOLDER (folder));
g_assert(CAMEL_IS_MIME_MESSAGE (message));

+ if (!camel_medium_get_header (CAMEL_MEDIUM (message), "X-Mailer"))
+ camel_medium_set_header (CAMEL_MEDIUM (message), "X-Mailer",
+ "Ximian Evolution " VERSION SUB_VERSION " " VERSION_COMMENT);
+
+
m = mail_msg_new (&append_mail_op, NULL, sizeof (*m));
m->folder = folder;
camel_object_ref(folder);
Of course, proud as we are at having NNTP support, we want to let the world know that _we_ sent the message.

evolution/ui

--- /home/meilof/cvs/orig/evolution//ui/evolution-message-composer.xml	2002-11-20 17:57:13.000000000 -0500
+++ ui/evolution-message-composer.xml 2003-12-31 02:42:03.000000000 -0500
@@ -36,6 +36,14 @@
<cmd name="ViewFrom" _label="_From Field"
_tip="Toggles whether the From chooser is displayed" type="toggle" state="0"/>

+ <cmd name="ViewTo" _label="_To Field"
+ _tip="Toggles whether the To field is displayed"
+ type="toggle" state="0"/>
+
+ <cmd name="ViewPostTo" _label="_Post-To Field"
+ _tip="Toggles whether the Post-To field is displayed"
+ type="toggle" state="0"/>
+
<cmd name="ViewReplyTo" _label="_Reply-To Field"
_tip="Toggles whether the Reply-To field is displayed"
type="toggle" state="0"/>
@@ -112,6 +120,8 @@
<menuitem name="ViewAttach" verb="" _label="Show _attachments"/>
<separator f="" name="emailcomposer"/>
<menuitem name="ViewFrom" verb=""/>
+ <menuitem name="ViewTo" verb=""/>
+ <menuitem name="ViewPostTo" verb=""/>
<menuitem name="ViewReplyTo" verb=""/>
<menuitem name="ViewCC" verb=""/>
<menuitem name="ViewBCC" verb=""/>
To and Post-To menu items

Copyright (C) Meilof Veeningen <meilof@wanadoo.nl>, 2004