aboutsummaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
authorMatthew Barnes <mbarnes@redhat.com>2011-07-12 19:06:12 +0800
committerMatthew Barnes <mbarnes@redhat.com>2011-07-14 10:46:26 +0800
commit8dfd4278a78e7d6ae30f92d50ee46029a5fbd106 (patch)
treea185d4fc44593ad3e727944873641c513b588535 /modules
parent2b342a4d9c020270da929a900b55105fc83bd57b (diff)
downloadgsoc2013-evolution-8dfd4278a78e7d6ae30f92d50ee46029a5fbd106.tar.gz
gsoc2013-evolution-8dfd4278a78e7d6ae30f92d50ee46029a5fbd106.tar.zst
gsoc2013-evolution-8dfd4278a78e7d6ae30f92d50ee46029a5fbd106.zip
Convert junk filtering EPlugins to EExtensions.
We now have a proper junk mail filtering API. All junk filtering extensions must subclass EMailJunkFilter for user preferences and availability testing, and implement the CamelJunkFilter interface for the actual junk filtering and learning operations. The bogofilter module should be feature-equivalent to its former EPlugin. The spamassassin module is far more complex. It's nearly feature-equivalent to its former EPlugin, but I ditched the spamd respawning code since it seemed unnecessary for a mail client to have to deal with. If there's a huge outcry from users about it I'll reluctantly put it back, but I don't expect one. This gets us a step closer to killing off EConfig, and eventually the EPlugin framework itself.
Diffstat (limited to 'modules')
-rw-r--r--modules/Makefile.am2
-rw-r--r--modules/bogofilter/Makefile.am57
-rw-r--r--modules/bogofilter/evolution-bogofilter.c524
-rw-r--r--modules/bogofilter/evolution-bogofilter.schemas.in20
-rw-r--r--modules/mail/Makefile.am2
-rw-r--r--modules/mail/e-mail-junk-hook.c344
-rw-r--r--modules/mail/e-mail-junk-hook.h66
-rw-r--r--modules/mail/em-mailer-prefs.c164
-rw-r--r--modules/mail/em-mailer-prefs.h6
-rw-r--r--modules/mail/evolution-module-mail.c2
-rw-r--r--modules/spamassassin/Makefile.am56
-rw-r--r--modules/spamassassin/evolution-spamassassin.c1177
-rw-r--r--modules/spamassassin/evolution-spamassassin.schemas.in33
13 files changed, 1888 insertions, 565 deletions
diff --git a/modules/Makefile.am b/modules/Makefile.am
index 9a32c72ebc..dd363d27a3 100644
--- a/modules/Makefile.am
+++ b/modules/Makefile.am
@@ -24,6 +24,7 @@ endif
SUBDIRS = \
addressbook \
+ bogofilter \
calendar \
mail \
composer-autosave \
@@ -31,6 +32,7 @@ SUBDIRS = \
offline-alert \
plugin-lib \
plugin-manager \
+ spamassassin \
startup-wizard \
$(MONO_DIR) \
$(PYTHON_DIR) \
diff --git a/modules/bogofilter/Makefile.am b/modules/bogofilter/Makefile.am
new file mode 100644
index 0000000000..7803267836
--- /dev/null
+++ b/modules/bogofilter/Makefile.am
@@ -0,0 +1,57 @@
+module_LTLIBRARIES = libevolution-module-bogofilter.la
+
+libevolution_module_bogofilter_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ -I$(top_srcdir) \
+ -DG_LOG_DOMAIN=\"evolution-bogofilter\" \
+ -DWELCOME_MESSAGE=\""$(privdatadir)/default/C/mail/local/Inbox"\" \
+ $(GNOME_PLATFORM_CFLAGS) \
+ $(EVOLUTION_MAIL_CFLAGS)
+
+libevolution_module_bogofilter_la_SOURCES = \
+ evolution-bogofilter.c
+
+libevolution_module_bogofilter_la_LIBADD = \
+ $(top_builddir)/e-util/libeutil.la \
+ $(top_builddir)/mail/libevolution-mail.la \
+ $(GNOME_PLATFORM_LIBS) \
+ $(EVOLUTION_MAIL_LIBS)
+
+libevolution_module_bogofilter_la_LDFLAGS = \
+ -module -avoid-version $(NO_UNDEFINED)
+
+schemadir = $(GCONF_SCHEMA_FILE_DIR)
+schema_in_files = evolution-bogofilter.schemas.in
+schema_DATA = $(schema_in_files:.schemas.in=.schemas)
+
+@INTLTOOL_SCHEMAS_RULE@
+
+if GCONF_SCHEMAS_INSTALL
+
+if OS_WIN32
+install-data-local:
+ if test -z "$(DESTDIR)" ; then \
+ for p in $(schema_DATA) ; do \
+ (echo set GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE); \
+ echo $(GCONFTOOL) --makefile-install-rule $$p) >_temp.bat; \
+ cmd /c _temp.bat; \
+ rm _temp.bat; \
+ done \
+ fi
+else
+install-data-local:
+ if test -z "$(DESTDIR)" ; then \
+ for p in $(schema_DATA) ; do \
+ GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE) \
+ $(GCONFTOOL) --makefile-install-rule $$p; \
+ done \
+ fi
+endif
+
+endif
+
+DISTCLEANFILES = $(schema_DATA)
+
+EXTRA_DIST = $(schema_in_files)
+
+-include $(top_srcdir)/git.mk
diff --git a/modules/bogofilter/evolution-bogofilter.c b/modules/bogofilter/evolution-bogofilter.c
new file mode 100644
index 0000000000..2cc7a64359
--- /dev/null
+++ b/modules/bogofilter/evolution-bogofilter.c
@@ -0,0 +1,524 @@
+/*
+ * evolution-bogofilter.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include <config.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <glib/gi18n-lib.h>
+
+#include <camel/camel.h>
+
+#include <e-util/gconf-bridge.h>
+#include <mail/e-mail-junk-filter.h>
+
+/* Standard GObject macros */
+#define E_TYPE_BOGOFILTER \
+ (e_bogofilter_get_type ())
+#define E_BOGOFILTER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_BOGOFILTER, EBogofilter))
+
+#ifndef BOGOFILTER_BINARY
+#define BOGOFILTER_BINARY "/usr/bin/bogofilter"
+#endif
+
+#define BOGOFILTER_EXIT_STATUS_SPAM 0
+#define BOGOFILTER_EXIT_STATUS_HAM 1
+#define BOGOFILTER_EXIT_STATUS_UNSURE 2
+#define BOGOFILTER_EXIT_STATUS_ERROR 3
+
+typedef struct _EBogofilter EBogofilter;
+typedef struct _EBogofilterClass EBogofilterClass;
+
+struct _EBogofilter {
+ EMailJunkFilter parent;
+ gboolean convert_to_unicode;
+};
+
+struct _EBogofilterClass {
+ EMailJunkFilterClass parent_class;
+};
+
+enum {
+ PROP_0,
+ PROP_CONVERT_TO_UNICODE
+};
+
+/* Module Entry Points */
+void e_module_load (GTypeModule *type_module);
+void e_module_unload (GTypeModule *type_module);
+
+/* Forward Declarations */
+GType e_bogofilter_get_type (void);
+static void e_bogofilter_interface_init (CamelJunkFilterInterface *interface);
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (
+ EBogofilter,
+ e_bogofilter,
+ E_TYPE_MAIL_JUNK_FILTER, 0,
+ G_IMPLEMENT_INTERFACE_DYNAMIC (
+ CAMEL_TYPE_JUNK_FILTER,
+ e_bogofilter_interface_init))
+
+#ifdef G_OS_UNIX
+static void
+bogofilter_cancelled_cb (GCancellable *cancellable,
+ GPid *pid)
+{
+ /* XXX On UNIX-like systems we can safely assume a GPid is the
+ * process ID and use it to terminate the process via signal. */
+ kill (*pid, SIGTERM);
+}
+#endif
+
+static void
+bogofilter_exited_cb (GPid *pid,
+ gint status,
+ gpointer user_data)
+{
+ struct {
+ GMainLoop *loop;
+ gint exit_code;
+ } *source_data = user_data;
+
+ if (WIFEXITED (status))
+ source_data->exit_code = WEXITSTATUS (status);
+ else
+ source_data->exit_code = BOGOFILTER_EXIT_STATUS_ERROR;
+
+ g_main_loop_quit (source_data->loop);
+}
+
+static gint
+bogofilter_command (const gchar **argv,
+ CamelMimeMessage *message,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStream *stream;
+ GMainContext *context;
+ GSource *source;
+ GPid child_pid;
+ gssize bytes_written;
+ gint standard_input;
+ gulong handler_id = 0;
+ gboolean success;
+
+ struct {
+ GMainLoop *loop;
+ gint exit_code;
+ } source_data;
+
+ /* Spawn Bogofilter with an open stdin pipe. */
+ success = g_spawn_async_with_pipes (
+ NULL,
+ (gchar **) argv,
+ NULL,
+ G_SPAWN_DO_NOT_REAP_CHILD |
+ G_SPAWN_STDOUT_TO_DEV_NULL,
+ NULL, NULL,
+ &child_pid,
+ &standard_input,
+ NULL,
+ NULL,
+ error);
+
+ if (!success) {
+ gchar *command_line;
+
+ command_line = g_strjoinv (" ", (gchar **) argv);
+ g_prefix_error (
+ error, _("Failed to spawn Bogofilter (%s): "),
+ command_line);
+ g_free (command_line);
+
+ return BOGOFILTER_EXIT_STATUS_ERROR;
+ }
+
+ /* Stream the CamelMimeMessage to Bogofilter. */
+ stream = camel_stream_fs_new_with_fd (standard_input);
+ bytes_written = camel_data_wrapper_write_to_stream_sync (
+ CAMEL_DATA_WRAPPER (message), stream, cancellable, error);
+ success = (bytes_written >= 0) &&
+ (camel_stream_close (stream, cancellable, error) == 0);
+ g_object_unref (stream);
+
+ if (!success) {
+ g_spawn_close_pid (child_pid);
+ g_prefix_error (
+ error, _("Failed to stream mail "
+ "message content to Bogofilter: "));
+ return BOGOFILTER_EXIT_STATUS_ERROR;
+ }
+
+ /* Wait for the Bogofilter process to terminate
+ * using GLib's main loop for better portability. */
+
+ context = g_main_context_new ();
+
+ source = g_child_watch_source_new (child_pid);
+ g_source_set_callback (
+ source, (GSourceFunc)
+ bogofilter_exited_cb,
+ &source_data, NULL);
+ g_source_attach (source, context);
+ g_source_unref (source);
+
+ source_data.loop = g_main_loop_new (context, TRUE);
+ source_data.exit_code = 0;
+
+#ifdef G_OS_UNIX
+ if (G_IS_CANCELLABLE (cancellable))
+ handler_id = g_cancellable_connect (
+ cancellable,
+ G_CALLBACK (bogofilter_cancelled_cb),
+ &child_pid, (GDestroyNotify) NULL);
+#endif
+
+ g_main_loop_run (source_data.loop);
+
+ if (handler_id > 0)
+ g_cancellable_disconnect (cancellable, handler_id);
+
+ g_main_loop_unref (source_data.loop);
+ source_data.loop = NULL;
+
+ g_main_context_unref (context);
+
+ /* Clean up. */
+
+ g_spawn_close_pid (child_pid);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ source_data.exit_code = BOGOFILTER_EXIT_STATUS_ERROR;
+
+ else if (source_data.exit_code == BOGOFILTER_EXIT_STATUS_ERROR)
+ g_set_error_literal (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Bogofilter either crashed or "
+ "failed to process a mail message"));
+
+ return source_data.exit_code;
+}
+
+static void
+bogofilter_init_wordlist (EBogofilter *extension)
+{
+ CamelStream *stream;
+ CamelMimeParser *parser;
+ CamelMimeMessage *message;
+
+ /* Initialize the Bogofilter database with a welcome message. */
+
+ parser = camel_mime_parser_new ();
+ message = camel_mime_message_new ();
+
+ stream = camel_stream_fs_new_with_name (
+ WELCOME_MESSAGE, O_RDONLY, 0, NULL);
+ camel_mime_parser_init_with_stream (parser, stream, NULL);
+ camel_mime_parser_scan_from (parser, FALSE);
+ g_object_unref (stream);
+
+ camel_mime_part_construct_from_parser_sync (
+ CAMEL_MIME_PART (message), parser, NULL, NULL);
+
+ camel_junk_filter_learn_not_junk (
+ CAMEL_JUNK_FILTER (extension), message, NULL, NULL);
+
+ g_object_unref (message);
+ g_object_unref (parser);
+}
+
+static gboolean
+bogofilter_get_convert_to_unicode (EBogofilter *extension)
+{
+ return extension->convert_to_unicode;
+}
+
+static void
+bogofilter_set_convert_to_unicode (EBogofilter *extension,
+ gboolean convert_to_unicode)
+{
+ extension->convert_to_unicode = convert_to_unicode;
+
+ g_object_notify (G_OBJECT (extension), "convert-to-unicode");
+}
+
+static void
+bogofilter_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CONVERT_TO_UNICODE:
+ bogofilter_set_convert_to_unicode (
+ E_BOGOFILTER (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+bogofilter_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CONVERT_TO_UNICODE:
+ g_value_set_boolean (
+ value, bogofilter_get_convert_to_unicode (
+ E_BOGOFILTER (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static gboolean
+bogofilter_available (EMailJunkFilter *junk_filter)
+{
+ return g_file_test (BOGOFILTER_BINARY, G_FILE_TEST_IS_EXECUTABLE);
+}
+
+static GtkWidget *
+bogofilter_new_config_widget (EMailJunkFilter *junk_filter)
+{
+ GtkWidget *box;
+ GtkWidget *widget;
+ gchar *markup;
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+
+ markup = g_markup_printf_escaped (
+ "<b>%s</b>", _("Bogofilter Options"));
+ widget = gtk_label_new (markup);
+ gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+ gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
+ gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+ g_free (markup);
+
+ widget = gtk_check_button_new_with_mnemonic (
+ _("Convert message text to _Unicode"));
+ gtk_widget_set_margin_left (widget, 12);
+ gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ g_object_bind_property (
+ junk_filter, "convert-to-unicode",
+ widget, "active",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ return box;
+}
+
+static gboolean
+bogofilter_classify (CamelJunkFilter *junk_filter,
+ CamelMimeMessage *message,
+ CamelJunkStatus *status,
+ GCancellable *cancellable,
+ GError **error)
+{
+ EBogofilter *extension = E_BOGOFILTER (junk_filter);
+ static gboolean wordlist_initialized = FALSE;
+ gint exit_code;
+
+ const gchar *argv[] = {
+ BOGOFILTER_BINARY,
+ NULL, /* leave room for unicode option */
+ NULL
+ };
+
+ if (bogofilter_get_convert_to_unicode (extension))
+ argv[1] = "--unicode=yes";
+
+retry:
+ exit_code = bogofilter_command (argv, message, cancellable, error);
+
+ switch (exit_code) {
+ case BOGOFILTER_EXIT_STATUS_SPAM:
+ *status = CAMEL_JUNK_STATUS_MESSAGE_IS_JUNK;
+ break;
+
+ case BOGOFILTER_EXIT_STATUS_HAM:
+ *status = CAMEL_JUNK_STATUS_MESSAGE_IS_NOT_JUNK;
+ break;
+
+ case BOGOFILTER_EXIT_STATUS_UNSURE:
+ *status = CAMEL_JUNK_STATUS_INCONCLUSIVE;
+ break;
+
+ case BOGOFILTER_EXIT_STATUS_ERROR:
+ if (!wordlist_initialized) {
+ wordlist_initialized = TRUE;
+ bogofilter_init_wordlist (extension);
+ goto retry;
+ }
+ break;
+
+ default:
+ g_warning (
+ "Bogofilter: Unexpected exit code (%d) "
+ "while classifying message", exit_code);
+ break;
+ }
+
+ /* Check that the return value and GError agree. */
+ if (exit_code != BOGOFILTER_EXIT_STATUS_ERROR)
+ g_warn_if_fail (error == NULL || *error == NULL);
+ else
+ g_warn_if_fail (error == NULL || *error != NULL);
+
+ return (exit_code != BOGOFILTER_EXIT_STATUS_ERROR);
+}
+
+static gboolean
+bogofilter_learn_junk (CamelJunkFilter *junk_filter,
+ CamelMimeMessage *message,
+ GCancellable *cancellable,
+ GError **error)
+{
+ EBogofilter *extension = E_BOGOFILTER (junk_filter);
+ gint exit_code;
+
+ const gchar *argv[] = {
+ BOGOFILTER_BINARY,
+ "--register-spam",
+ NULL, /* leave room for unicode option */
+ NULL
+ };
+
+ if (bogofilter_get_convert_to_unicode (extension))
+ argv[2] = "--unicode=yes";
+
+ exit_code = bogofilter_command (argv, message, cancellable, error);
+
+ if (exit_code != 0)
+ g_warning (
+ "Bogofilter: Unexpected exit code (%d) "
+ "while registering spam", exit_code);
+
+ /* Check that the return value and GError agree. */
+ if (exit_code != BOGOFILTER_EXIT_STATUS_ERROR)
+ g_warn_if_fail (error == NULL || *error == NULL);
+ else
+ g_warn_if_fail (error == NULL || *error != NULL);
+
+ return (exit_code != BOGOFILTER_EXIT_STATUS_ERROR);
+}
+
+static gboolean
+bogofilter_learn_not_junk (CamelJunkFilter *junk_filter,
+ CamelMimeMessage *message,
+ GCancellable *cancellable,
+ GError **error)
+{
+ EBogofilter *extension = E_BOGOFILTER (junk_filter);
+ gint exit_code;
+
+ const gchar *argv[] = {
+ BOGOFILTER_BINARY,
+ "--register-ham",
+ NULL, /* leave room for unicode option */
+ NULL
+ };
+
+ if (bogofilter_get_convert_to_unicode (extension))
+ argv[2] = "--unicode=yes";
+
+ exit_code = bogofilter_command (argv, message, cancellable, error);
+
+ if (exit_code != 0)
+ g_warning (
+ "Bogofilter: Unexpected exit code (%d) "
+ "while registering ham", exit_code);
+
+ /* Check that the return value and GError agree. */
+ if (exit_code != BOGOFILTER_EXIT_STATUS_ERROR)
+ g_warn_if_fail (error == NULL || *error == NULL);
+ else
+ g_warn_if_fail (error == NULL || *error != NULL);
+
+ return (exit_code != BOGOFILTER_EXIT_STATUS_ERROR);
+}
+
+static void
+e_bogofilter_class_init (EBogofilterClass *class)
+{
+ GObjectClass *object_class;
+ EMailJunkFilterClass *junk_filter_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = bogofilter_set_property;
+ object_class->get_property = bogofilter_get_property;
+
+ junk_filter_class = E_MAIL_JUNK_FILTER_CLASS (class);
+ junk_filter_class->filter_name = "Bogofilter";
+ junk_filter_class->display_name = _("Bogofilter");
+ junk_filter_class->available = bogofilter_available;
+ junk_filter_class->new_config_widget = bogofilter_new_config_widget;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CONVERT_TO_UNICODE,
+ g_param_spec_boolean (
+ "convert-to-unicode",
+ "Convert to Unicode",
+ "Convert message text to Unicode",
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_bogofilter_class_finalize (EBogofilterClass *class)
+{
+}
+
+static void
+e_bogofilter_interface_init (CamelJunkFilterInterface *interface)
+{
+ interface->classify = bogofilter_classify;
+ interface->learn_junk = bogofilter_learn_junk;
+ interface->learn_not_junk = bogofilter_learn_not_junk;
+}
+
+static void
+e_bogofilter_init (EBogofilter *extension)
+{
+ gconf_bridge_bind_property (
+ gconf_bridge_get (),
+ "/apps/evolution/mail/junk/bogofilter/unicode",
+ G_OBJECT (extension), "convert-to-unicode");
+}
+
+G_MODULE_EXPORT void
+e_module_load (GTypeModule *type_module)
+{
+ e_bogofilter_register_type (type_module);
+}
+
+G_MODULE_EXPORT void
+e_module_unload (GTypeModule *type_module)
+{
+}
diff --git a/modules/bogofilter/evolution-bogofilter.schemas.in b/modules/bogofilter/evolution-bogofilter.schemas.in
new file mode 100644
index 0000000000..e313eb9ffc
--- /dev/null
+++ b/modules/bogofilter/evolution-bogofilter.schemas.in
@@ -0,0 +1,20 @@
+<gconfschemafile>
+ <schemalist>
+
+ <schema>
+ <key>/schemas/apps/evolution/mail/junk/bogofilter/unicode</key>
+ <applyto>/apps/evolution/mail/junk/bogofilter/unicode</applyto>
+ <owner>evolution-bogofilter</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Convert mail messages to Unicode</short>
+ <long>
+ Convert message text to Unicode UTF-8 to unify spam/ham tokens
+ coming from different character sets.
+ </long>
+ </locale>
+ </schema>
+
+ </schemalist>
+</gconfschemafile>
diff --git a/modules/mail/Makefile.am b/modules/mail/Makefile.am
index 884d050d78..0bea9ac1a2 100644
--- a/modules/mail/Makefile.am
+++ b/modules/mail/Makefile.am
@@ -27,8 +27,6 @@ libevolution_module_mail_la_SOURCES = \
e-mail-config-web-view.h \
e-mail-event-hook.c \
e-mail-event-hook.h \
- e-mail-junk-hook.c \
- e-mail-junk-hook.h \
e-mail-shell-backend.c \
e-mail-shell-backend.h \
e-mail-shell-content.c \
diff --git a/modules/mail/e-mail-junk-hook.c b/modules/mail/e-mail-junk-hook.c
deleted file mode 100644
index 4ccc404e8d..0000000000
--- a/modules/mail/e-mail-junk-hook.c
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * e-mail-junk-hook.c
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) version 3.
- *
- * 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with the program; if not, see <http://www.gnu.org/licenses/>
- *
- *
- * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
- *
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include "e-mail-junk-hook.h"
-
-#include <glib/gi18n.h>
-
-#include "e-util/e-alert-dialog.h"
-#include "shell/e-shell.h"
-
-#include "mail/em-junk.h"
-#include "mail/em-utils.h"
-#include "mail/e-mail-backend.h"
-#include "mail/e-mail-session.h"
-
-struct _EMailJunkHookPrivate {
- EMJunkInterface interface;
-};
-
-struct ErrorData {
- const gchar *error_message;
- GError *error;
-};
-
-static gpointer parent_class;
-static GType mail_junk_hook_type;
-
-static gboolean
-mail_junk_hook_idle_cb (struct ErrorData *data)
-{
- EShell *shell;
- EShellBackend *shell_backend;
-
- shell = e_shell_get_default ();
- shell_backend = e_shell_get_backend_by_name (shell, "mail");
-
- e_mail_backend_submit_alert (
- E_MAIL_BACKEND (shell_backend),
- data->error_message, data->error->message, NULL);
-
- g_error_free (data->error);
- g_slice_free (struct ErrorData, data);
-
- return FALSE;
-}
-
-static void
-mail_junk_hook_error (const gchar *error_message,
- GError *error)
-{
- struct ErrorData *data;
-
- g_return_if_fail (error != NULL);
-
- data = g_slice_new (struct ErrorData);
- data->error_message = error_message;
- data->error = error;
-
- g_idle_add ((GSourceFunc) mail_junk_hook_idle_cb, data);
-}
-
-static const gchar *
-mail_junk_hook_get_name (CamelJunkPlugin *junk_plugin)
-{
- EMJunkInterface *interface;
-
- interface = (EMJunkInterface *) junk_plugin;
-
- if (!interface->hook->plugin->enabled) {
- /* Translators: "None" for a junk hook name,
- * when the junk plugin is not enabled. */
- return C_("mail-junk-hook", "None");
- }
-
- return interface->hook->plugin->name;
-}
-
-static void
-mail_junk_hook_plugin_init (CamelJunkPlugin *junk_plugin)
-{
- EMJunkInterface *interface;
- EPluginClass *class;
-
- interface = (EMJunkInterface *) junk_plugin;
-
- class = E_PLUGIN_GET_CLASS (interface->hook->plugin);
- g_return_if_fail (class->enable != NULL);
-
- class->enable (interface->hook->plugin, 1);
-}
-
-static gboolean
-mail_junk_hook_check_junk (CamelJunkPlugin *junk_plugin,
- CamelMimeMessage *mime_message)
-{
- EMJunkTarget target = { mime_message, NULL };
- EMJunkInterface *interface;
- gpointer result;
-
- interface = (EMJunkInterface *) junk_plugin;
-
- if (!interface->hook->plugin->enabled)
- return FALSE;
-
- result = e_plugin_invoke (
- interface->hook->plugin,
- interface->check_junk, &target);
-
- if (target.error != NULL)
- mail_junk_hook_error ("mail:junk-check-error", target.error);
-
- return (result != NULL);
-}
-
-static void
-mail_junk_hook_report_junk (CamelJunkPlugin *junk_plugin,
- CamelMimeMessage *mime_message)
-{
- EMJunkTarget target = { mime_message, NULL };
- EMJunkInterface *interface;
-
- interface = (EMJunkInterface *) junk_plugin;
-
- if (!interface->hook->plugin->enabled)
- return;
-
- e_plugin_invoke (
- interface->hook->plugin,
- interface->report_junk, &target);
-
- if (target.error != NULL)
- mail_junk_hook_error ("mail:junk-report-error", target.error);
-}
-
-static void
-mail_junk_hook_report_notjunk (CamelJunkPlugin *junk_plugin,
- CamelMimeMessage *mime_message)
-{
- EMJunkTarget target = { mime_message, NULL };
- EMJunkInterface *interface;
-
- interface = (EMJunkInterface *) junk_plugin;
-
- if (!interface->hook->plugin->enabled)
- return;
-
- e_plugin_invoke (
- interface->hook->plugin,
- interface->report_notjunk, &target);
-
- if (target.error != NULL)
- mail_junk_hook_error (
- "mail:junk-not-report-error", target.error);
-}
-
-static void
-mail_junk_hook_commit_reports (CamelJunkPlugin *junk_plugin)
-{
- EMJunkInterface *interface;
-
- interface = (EMJunkInterface *) junk_plugin;
-
- if (!interface->hook->plugin->enabled)
- return;
-
- e_plugin_invoke (
- interface->hook->plugin,
- interface->commit_reports, NULL);
-}
-
-static void
-mail_junk_hook_finalize (GObject *object)
-{
- EMailJunkHookPrivate *priv;
-
- priv = E_MAIL_JUNK_HOOK (object)->priv;
-
- g_free (priv->interface.check_junk);
- g_free (priv->interface.report_junk);
- g_free (priv->interface.report_notjunk);
- g_free (priv->interface.commit_reports);
- g_free (priv->interface.validate_binary);
- g_free (priv->interface.plugin_name);
-
- /* Chain up to parent's finalize() method. */
- G_OBJECT_CLASS (parent_class)->finalize (object);
-}
-
-static gint
-mail_junk_hook_construct (EPluginHook *hook,
- EPlugin *plugin,
- xmlNodePtr node)
-{
- EMailJunkHookPrivate *priv;
- EShell *shell;
- EShellBackend *shell_backend;
- EMailBackend *backend;
- EMailSession *session;
- gchar *property;
-
- priv = E_MAIL_JUNK_HOOK (hook)->priv;
-
- /* Chain up to parent's construct() method. */
- if (E_PLUGIN_HOOK_CLASS (parent_class)->construct (hook, plugin, node) == -1)
- return -1;
-
- if (!plugin->enabled)
- return -1;
-
- node = xmlFirstElementChild (node);
-
- if (node == NULL)
- return -1;
-
- if (g_strcmp0 ((gchar *) node->name, "interface") != 0)
- return -1;
-
- property = e_plugin_xml_prop (node, "check_junk");
- priv->interface.check_junk = property;
-
- property = e_plugin_xml_prop (node, "report_junk");
- priv->interface.report_junk = property;
-
- property = e_plugin_xml_prop (node, "report_non_junk");
- priv->interface.report_notjunk = property;
-
- property = e_plugin_xml_prop (node, "commit_reports");
- priv->interface.commit_reports = property;
-
- property = e_plugin_xml_prop (node, "validate_binary");
- priv->interface.validate_binary = property;
-
- property = e_plugin_xml_prop (node, "name");
- priv->interface.plugin_name = property;
-
- if (priv->interface.check_junk == NULL)
- return -1;
-
- if (priv->interface.report_junk == NULL)
- return -1;
-
- if (priv->interface.report_notjunk == NULL)
- return -1;
-
- if (priv->interface.commit_reports == NULL)
- return -1;
-
- shell = e_shell_get_default ();
- shell_backend = e_shell_get_backend_by_name (shell, "mail");
-
- backend = E_MAIL_BACKEND (shell_backend);
- session = e_mail_backend_get_session (backend);
-
- mail_session_add_junk_plugin (
- session, priv->interface.plugin_name,
- &priv->interface.camel);
-
- return 0;
-}
-
-static void
-mail_junk_hook_class_init (EMailJunkHookClass *class)
-{
- GObjectClass *object_class;
- EPluginHookClass *plugin_hook_class;
-
- parent_class = g_type_class_peek_parent (class);
- g_type_class_add_private (class, sizeof (EMailJunkHookPrivate));
-
- object_class = G_OBJECT_CLASS (class);
- object_class->finalize = mail_junk_hook_finalize;
-
- plugin_hook_class = E_PLUGIN_HOOK_CLASS (class);
- plugin_hook_class->construct = mail_junk_hook_construct;
- plugin_hook_class->id = "org.gnome.evolution.mail.junk:1.0";
-}
-
-static void
-mail_junk_hook_init (EMailJunkHook *mail_junk_hook)
-{
- EMJunkInterface *interface;
-
- mail_junk_hook->priv = G_TYPE_INSTANCE_GET_PRIVATE (
- mail_junk_hook, E_TYPE_MAIL_JUNK_HOOK, EMailJunkHookPrivate);
-
- interface = &mail_junk_hook->priv->interface;
- interface->camel.get_name = mail_junk_hook_get_name;
- interface->camel.api_version = 1;
- interface->camel.check_junk = mail_junk_hook_check_junk;
- interface->camel.report_junk = mail_junk_hook_report_junk;
- interface->camel.report_notjunk = mail_junk_hook_report_notjunk;
- interface->camel.commit_reports = mail_junk_hook_commit_reports;
- interface->camel.init = mail_junk_hook_plugin_init;
- interface->hook = E_PLUGIN_HOOK (mail_junk_hook);
-}
-
-GType
-e_mail_junk_hook_get_type (void)
-{
- return mail_junk_hook_type;
-}
-
-void
-e_mail_junk_hook_register_type (GTypeModule *type_module)
-{
- const GTypeInfo type_info = {
- sizeof (EMailJunkHookClass),
- (GBaseInitFunc) NULL,
- (GBaseFinalizeFunc) NULL,
- (GClassInitFunc) mail_junk_hook_class_init,
- (GClassFinalizeFunc) NULL,
- NULL, /* class_data */
- sizeof (EMailJunkHook),
- 0, /* n_preallocs */
- (GInstanceInitFunc) mail_junk_hook_init,
- NULL /* value_table */
- };
-
- mail_junk_hook_type = g_type_module_register_type (
- type_module, E_TYPE_PLUGIN_HOOK,
- "EMailJunkHook", &type_info, 0);
-}
diff --git a/modules/mail/e-mail-junk-hook.h b/modules/mail/e-mail-junk-hook.h
deleted file mode 100644
index f5882e66b3..0000000000
--- a/modules/mail/e-mail-junk-hook.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * e-mail-junk-hook.h
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) version 3.
- *
- * 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with the program; if not, see <http://www.gnu.org/licenses/>
- *
- *
- * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
- *
- */
-
-#ifndef E_MAIL_JUNK_HOOK_H
-#define E_MAIL_JUNK_HOOK_H
-
-#include <e-util/e-plugin.h>
-
-/* Standard GObject macros */
-#define E_TYPE_MAIL_JUNK_HOOK \
- (e_mail_junk_hook_get_type ())
-#define E_MAIL_JUNK_HOOK(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST \
- ((obj), E_TYPE_MAIL_JUNK_HOOK, EMailJunkHook))
-#define E_MAIL_JUNK_HOOK_CLASS(cls) \
- (G_TYPE_CHECK_CLASS_CAST \
- ((cls), E_TYPE_MAIL_JUNK_HOOK, EMailJunkHookClass))
-#define E_IS_MAIL_JUNK_HOOK(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE \
- ((obj), E_TYPE_MAIL_JUNK_HOOK))
-#define E_IS_MAIL_JUNK_HOOK_CLASS(cls) \
- (G_TYPE_CHECK_CLASS_TYPE \
- ((cls), E_TYPE_MAIL_JUNK_HOOK))
-#define E_MAIL_JUNK_HOOK_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS \
- ((obj), E_TYPE_MAIL_JUNK_HOOK, EMailJunkHookClass))
-
-G_BEGIN_DECLS
-
-typedef struct _EMailJunkHook EMailJunkHook;
-typedef struct _EMailJunkHookClass EMailJunkHookClass;
-typedef struct _EMailJunkHookPrivate EMailJunkHookPrivate;
-
-struct _EMailJunkHook {
- EPluginHook parent;
- EMailJunkHookPrivate *priv;
-};
-
-struct _EMailJunkHookClass {
- EPluginHookClass parent_class;
-};
-
-GType e_mail_junk_hook_get_type (void);
-void e_mail_junk_hook_register_type (GTypeModule *type_module);
-
-G_END_DECLS
-
-#endif /* E_MAIL_JUNK_HOOK_H */
diff --git a/modules/mail/em-mailer-prefs.c b/modules/mail/em-mailer-prefs.c
index 8e2688e615..bbf65e36b5 100644
--- a/modules/mail/em-mailer-prefs.c
+++ b/modules/mail/em-mailer-prefs.c
@@ -37,18 +37,20 @@
#include "libedataserverui/e-cell-renderer-color.h"
-#include "e-util/e-util.h"
-#include "e-util/e-datetime-format.h"
-#include "e-util/e-util-private.h"
-#include "widgets/misc/e-charset-combo-box.h"
-#include "shell/e-shell-utils.h"
-
-#include "e-mail-backend.h"
-#include "e-mail-label-manager.h"
-#include "e-mail-reader-utils.h"
-#include "em-folder-selection-button.h"
-#include "em-junk.h"
-#include "em-config.h"
+#include <e-util/e-util.h>
+#include <e-util/e-datetime-format.h>
+#include <e-util/e-util-private.h>
+
+#include <misc/e-charset-combo-box.h>
+#include <misc/e-port-entry.h>
+#include <shell/e-shell-utils.h>
+
+#include <mail/e-mail-backend.h>
+#include <mail/e-mail-junk-options.h>
+#include <mail/e-mail-label-manager.h>
+#include <mail/e-mail-reader-utils.h>
+#include <mail/em-folder-selection-button.h>
+#include <mail/em-config.h>
enum {
HEADER_LIST_NAME_COLUMN, /* displayable name of the header (may be a translation) */
@@ -102,7 +104,6 @@ em_mailer_prefs_finalize (GObject *object)
{
EMMailerPrefs *prefs = (EMMailerPrefs *) object;
- g_object_unref (prefs->session);
g_object_unref (prefs->builder);
if (prefs->labels_change_notify_id) {
@@ -676,131 +677,8 @@ emmp_free (EConfig *ec, GSList *items, gpointer data)
}
static void
-junk_plugin_changed (GtkWidget *combo, EMMailerPrefs *prefs)
-{
- gchar *def_plugin;
- const GList *plugins = mail_session_get_junk_plugins (prefs->session);
- GtkTreeIter iter;
-
- g_return_if_fail (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter));
-
- def_plugin = NULL;
- gtk_tree_model_get (gtk_combo_box_get_model (GTK_COMBO_BOX (combo)), &iter, 0, &def_plugin, -1);
-
- gconf_client_set_string (prefs->gconf, "/apps/evolution/mail/junk/default_plugin", def_plugin, NULL);
- while (plugins) {
- EMJunkInterface *iface = plugins->data;
-
- if (iface->plugin_name && def_plugin && !strcmp (iface->plugin_name, def_plugin)) {
- gboolean status;
-
- CAMEL_SESSION (prefs->session)->junk_plugin =
- CAMEL_JUNK_PLUGIN (&iface->camel);
- status = e_plugin_invoke (iface->hook->plugin, iface->validate_binary, NULL) != NULL;
- if ((gboolean) status == TRUE) {
- gchar *text, *html;
- gtk_image_set_from_stock (prefs->plugin_image, "gtk-dialog-info", GTK_ICON_SIZE_MENU);
- text = g_strdup_printf (_("%s plugin is available and the binary is installed."), iface->plugin_name);
- html = g_strdup_printf ("<i>%s</i>", text);
- gtk_label_set_markup (prefs->plugin_status, html);
- g_free (html);
- g_free (text);
- } else {
- gchar *text, *html;
- gtk_image_set_from_stock (prefs->plugin_image, "gtk-dialog-warning", GTK_ICON_SIZE_MENU);
- text = g_strdup_printf (_("%s plugin is not available. Please check whether the package is installed."), iface->plugin_name);
- html = g_strdup_printf ("<i>%s</i>", text);
- gtk_label_set_markup (prefs->plugin_status, html);
- g_free (html);
- g_free (text);
- }
- break;
- }
- plugins = plugins->next;
- }
-
- g_free (def_plugin);
-}
-
-static void
-junk_plugin_setup (GtkComboBox *combo_box, EMMailerPrefs *prefs)
-{
- GtkListStore *store;
- GtkCellRenderer *cell;
- gint index = 0;
- gboolean def_set = FALSE;
- const GList *plugins = mail_session_get_junk_plugins (prefs->session);
- gchar *pdefault = gconf_client_get_string (prefs->gconf, "/apps/evolution/mail/junk/default_plugin", NULL);
-
- store = gtk_list_store_new (1, G_TYPE_STRING);
- gtk_combo_box_set_model (combo_box, GTK_TREE_MODEL (store));
-
- cell = gtk_cell_renderer_text_new ();
- gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), cell, TRUE);
- gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), cell,
- "text", 0,
- NULL);
-
- if (!plugins || !g_list_length ((GList *) plugins)) {
- GtkTreeIter iter;
-
- gtk_list_store_append (store, &iter);
- gtk_list_store_set (
- store, &iter, 0, _("No junk plugin available"), -1);
- gtk_combo_box_set_active (combo_box, 0);
- gtk_widget_set_sensitive (GTK_WIDGET (combo_box), FALSE);
- gtk_widget_hide (GTK_WIDGET (prefs->plugin_image));
- gtk_widget_hide (GTK_WIDGET (prefs->plugin_status));
- gtk_image_set_from_stock (prefs->plugin_image, NULL, 0);
- g_free (pdefault);
-
- return;
- }
-
- while (plugins) {
- EMJunkInterface *iface = plugins->data;
- GtkTreeIter iter;
-
- gtk_list_store_append (store, &iter);
- gtk_list_store_set (store, &iter, 0, iface->plugin_name, -1);
- if (!def_set && pdefault && iface->plugin_name && !strcmp (pdefault, iface->plugin_name)) {
- gboolean status;
-
- def_set = TRUE;
- gtk_combo_box_set_active (combo_box, index);
- status = e_plugin_invoke (iface->hook->plugin, iface->validate_binary, NULL) != NULL;
- if (status) {
- gchar *text, *html;
- gtk_image_set_from_stock (prefs->plugin_image, "gtk-dialog-info", GTK_ICON_SIZE_MENU);
- /* May be a better text */
- text = g_strdup_printf (_("%s plugin is available and the binary is installed."), iface->plugin_name);
- html = g_strdup_printf ("<i>%s</i>", text);
- gtk_label_set_markup (prefs->plugin_status, html);
- g_free (html);
- g_free (text);
- } else {
- gchar *text, *html;
- gtk_image_set_from_stock (prefs->plugin_image, "gtk-dialog-warning", GTK_ICON_SIZE_MENU);
- /* May be a better text */
- text = g_strdup_printf (_("%s plugin is not available. Please check whether the package is installed."), iface->plugin_name);
- html = g_strdup_printf ("<i>%s</i>", text);
- gtk_label_set_markup (prefs->plugin_status, html);
- g_free (html);
- g_free (text);
- }
- }
- plugins = plugins->next;
- index++;
- }
-
- g_signal_connect (
- combo_box, "changed",
- G_CALLBACK (junk_plugin_changed), prefs);
- g_free (pdefault);
-}
-
-static void
em_mailer_prefs_construct (EMMailerPrefs *prefs,
+ EMailSession *session,
EShell *shell)
{
GSList *header_config_list, *header_add_list, *p;
@@ -823,6 +701,7 @@ em_mailer_prefs_construct (EMMailerPrefs *prefs,
/* Make sure our custom widget classes are registered with
* GType before we load the GtkBuilder definition file. */
+ E_TYPE_MAIL_JUNK_OPTIONS;
EM_TYPE_FOLDER_SELECTION_BUTTON;
prefs->builder = gtk_builder_new ();
@@ -1182,10 +1061,8 @@ em_mailer_prefs_construct (EMMailerPrefs *prefs,
G_BINDING_SYNC_CREATE);
emmp_empty_junk_init (prefs, GTK_COMBO_BOX (widget));
- prefs->default_junk_plugin = GTK_COMBO_BOX (e_builder_get_widget (prefs->builder, "default_junk_plugin"));
- prefs->plugin_status = GTK_LABEL (e_builder_get_widget (prefs->builder, "plugin_status"));
- prefs->plugin_image = GTK_IMAGE (e_builder_get_widget (prefs->builder, "plugin_image"));
- junk_plugin_setup (prefs->default_junk_plugin, prefs);
+ widget = e_builder_get_widget (prefs->builder, "junk-module-options");
+ e_mail_junk_options_set_session (E_MAIL_JUNK_OPTIONS (widget), session);
prefs->junk_header_check = (GtkToggleButton *)e_builder_get_widget (prefs->builder, "junk_header_check");
prefs->junk_header_tree = (GtkTreeView *)e_builder_get_widget (prefs->builder, "junk_header_tree");
@@ -1238,11 +1115,8 @@ em_mailer_prefs_new (EPreferencesWindow *window)
new = g_object_new (EM_TYPE_MAILER_PREFS, NULL);
- /* FIXME This should be a constructor property. */
- new->session = g_object_ref (session);
-
/* FIXME Kill this function. */
- em_mailer_prefs_construct (new, shell);
+ em_mailer_prefs_construct (new, session, shell);
return GTK_WIDGET (new);
}
diff --git a/modules/mail/em-mailer-prefs.h b/modules/mail/em-mailer-prefs.h
index d28fd5f5e4..35ebec3bf7 100644
--- a/modules/mail/em-mailer-prefs.h
+++ b/modules/mail/em-mailer-prefs.h
@@ -26,7 +26,6 @@
#include <gtk/gtk.h>
#include <gconf/gconf-client.h>
#include <shell/e-shell.h>
-#include <mail/e-mail-session.h>
#include <widgets/misc/e-preferences-window.h>
/* Standard GObject macros */
@@ -56,8 +55,6 @@ typedef struct _EMMailerPrefsClass EMMailerPrefsClass;
struct _EMMailerPrefs {
GtkVBox parent_object;
- EMailSession *session;
-
GtkBuilder *builder;
GConfClient *gconf;
@@ -95,9 +92,6 @@ struct _EMMailerPrefs {
GtkToggleButton *sa_local_tests_only;
GtkToggleButton *sa_use_daemon;
- GtkComboBox *default_junk_plugin;
- GtkLabel *plugin_status;
- GtkImage *plugin_image;
GtkToggleButton *junk_header_check;
GtkTreeView *junk_header_tree;
diff --git a/modules/mail/evolution-module-mail.c b/modules/mail/evolution-module-mail.c
index ddb9d8996b..bda0db5d95 100644
--- a/modules/mail/evolution-module-mail.c
+++ b/modules/mail/evolution-module-mail.c
@@ -27,7 +27,6 @@
#include "e-mail-config-hook.h"
#include "e-mail-event-hook.h"
-#include "e-mail-junk-hook.h"
#include "e-mail-shell-backend.h"
#include "e-mail-shell-content.h"
@@ -52,7 +51,6 @@ e_module_load (GTypeModule *type_module)
e_mail_config_hook_register_type (type_module);
e_mail_event_hook_register_type (type_module);
- e_mail_junk_hook_register_type (type_module);
e_mail_shell_backend_register_type (type_module);
e_mail_shell_content_register_type (type_module);
diff --git a/modules/spamassassin/Makefile.am b/modules/spamassassin/Makefile.am
new file mode 100644
index 0000000000..8bce85214b
--- /dev/null
+++ b/modules/spamassassin/Makefile.am
@@ -0,0 +1,56 @@
+module_LTLIBRARIES = libevolution-module-spamassassin.la
+
+libevolution_module_spamassassin_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ -I$(top_srcdir) \
+ -DG_LOG_DOMAIN=\"evolution-spamassassin\" \
+ $(GNOME_PLATFORM_CFLAGS) \
+ $(EVOLUTION_MAIL_CFLAGS)
+
+libevolution_module_spamassassin_la_SOURCES = \
+ evolution-spamassassin.c
+
+libevolution_module_spamassassin_la_LIBADD = \
+ $(top_builddir)/e-util/libeutil.la \
+ $(top_builddir)/mail/libevolution-mail.la \
+ $(GNOME_PLATFORM_LIBS) \
+ $(EVOLUTION_MAIL_LIBS)
+
+libevolution_module_spamassassin_la_LDFLAGS = \
+ -module -avoid-version $(NO_UNDEFINED)
+
+schemadir = $(GCONF_SCHEMA_FILE_DIR)
+schema_in_files = evolution-spamassassin.schemas.in
+schema_DATA = $(schema_in_files:.schemas.in=.schemas)
+
+@INTLTOOL_SCHEMAS_RULE@
+
+if GCONF_SCHEMAS_INSTALL
+
+if OS_WIN32
+install-data-local:
+ if test -z "$(DESTDIR)" ; then \
+ for p in $(schema_DATA) ; do \
+ (echo set GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE); \
+ echo $(GCONFTOOL) --makefile-install-rule $$p) >_temp.bat; \
+ cmd /c _temp.bat; \
+ rm _temp.bat; \
+ done \
+ fi
+else
+install-data-local:
+ if test -z "$(DESTDIR)" ; then \
+ for p in $(schema_DATA) ; do \
+ GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE) \
+ $(GCONFTOOL) --makefile-install-rule $$p; \
+ done \
+ fi
+endif
+
+endif
+
+DISTCLEANFILES = $(schema_DATA)
+
+EXTRA_DIST = $(schema_in_files)
+
+-include $(top_srcdir)/git.mk
diff --git a/modules/spamassassin/evolution-spamassassin.c b/modules/spamassassin/evolution-spamassassin.c
new file mode 100644
index 0000000000..6107694713
--- /dev/null
+++ b/modules/spamassassin/evolution-spamassassin.c
@@ -0,0 +1,1177 @@
+/*
+ * evolution-spamassassin.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include <config.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <glib/gi18n-lib.h>
+
+#include <camel/camel.h>
+
+#include <e-util/e-mktemp.h>
+#include <e-util/gconf-bridge.h>
+#include <mail/e-mail-junk-filter.h>
+
+/* Standard GObject macros */
+#define E_TYPE_SPAM_ASSASSIN \
+ (e_spam_assassin_get_type ())
+#define E_SPAM_ASSASSIN(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_SPAM_ASSASSIN, ESpamAssassin))
+
+#ifndef SPAMASSASSIN_BINARY
+#define SPAMASSASSIN_BINARY "/usr/bin/spamassassin"
+#endif
+
+#ifndef SA_LEARN_BINARY
+#define SA_LEARN_BINARY "/usr/bin/sa-learn"
+#endif
+
+#ifndef SPAMC_BINARY
+#define SPAMC_BINARY "/usr/bin/spamc"
+#endif
+
+#ifndef SPAMD_BINARY
+#define SPAMD_BINARY "/usr/bin/spamd"
+#endif
+
+/* For starting our own daemon. */
+#define DAEMON_MAX_RETRIES 100
+#define DAEMON_RETRY_DELAY 0.05 /* seconds */
+
+#define SPAM_ASSASSIN_EXIT_STATUS_SUCCESS 0
+#define SPAM_ASSASSIN_EXIT_STATUS_ERROR -1
+
+typedef struct _ESpamAssassin ESpamAssassin;
+typedef struct _ESpamAssassinClass ESpamAssassinClass;
+
+struct _ESpamAssassin {
+ EMailJunkFilter parent;
+
+ GMutex *socket_path_mutex;
+
+ gchar *pid_file;
+ gchar *socket_path;
+ gchar *spamc_binary;
+ gchar *spamd_binary;
+ gint version;
+
+ gboolean local_only;
+ gboolean use_daemon;
+ gboolean version_set;
+
+ /* spamc/spamd state */
+ gboolean spamd_tested;
+ gboolean spamd_using_allow_tell;
+ gboolean system_spamd_available;
+ gboolean use_spamc;
+};
+
+struct _ESpamAssassinClass {
+ EMailJunkFilterClass parent_class;
+};
+
+enum {
+ PROP_0,
+ PROP_LOCAL_ONLY,
+ PROP_SPAMC_BINARY,
+ PROP_SPAMD_BINARY,
+ PROP_SOCKET_PATH,
+ PROP_USE_DAEMON
+};
+
+/* Module Entry Points */
+void e_module_load (GTypeModule *type_module);
+void e_module_unload (GTypeModule *type_module);
+
+/* Forward Declarations */
+GType e_spam_assassin_get_type (void);
+static void e_spam_assassin_interface_init (CamelJunkFilterInterface *interface);
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (
+ ESpamAssassin,
+ e_spam_assassin,
+ E_TYPE_MAIL_JUNK_FILTER, 0,
+ G_IMPLEMENT_INTERFACE_DYNAMIC (
+ CAMEL_TYPE_JUNK_FILTER,
+ e_spam_assassin_interface_init))
+
+#ifdef G_OS_UNIX
+static void
+spam_assassin_cancelled_cb (GCancellable *cancellable,
+ GPid *pid)
+{
+ /* XXX On UNIX-like systems we can safely assume a GPid is the
+ * process ID and use it to terminate the process via signal. */
+ kill (*pid, SIGTERM);
+}
+#endif
+
+static void
+spam_assassin_exited_cb (GPid *pid,
+ gint status,
+ gpointer user_data)
+{
+ struct {
+ GMainLoop *loop;
+ gint exit_code;
+ } *source_data = user_data;
+
+ if (WIFEXITED (status))
+ source_data->exit_code = WEXITSTATUS (status);
+ else
+ source_data->exit_code = SPAM_ASSASSIN_EXIT_STATUS_ERROR;
+
+ g_main_loop_quit (source_data->loop);
+}
+
+static gint
+spam_assassin_command_full (const gchar **argv,
+ CamelMimeMessage *message,
+ const gchar *input_data,
+ GByteArray *output_buffer,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GMainContext *context;
+ GSpawnFlags flags;
+ GSource *source;
+ GPid child_pid;
+ gint standard_input;
+ gint standard_output;
+ gulong handler_id = 0;
+ gboolean success;
+
+ struct {
+ GMainLoop *loop;
+ gint exit_code;
+ } source_data;
+
+ flags = G_SPAWN_DO_NOT_REAP_CHILD;
+ if (output_buffer == NULL)
+ flags |= G_SPAWN_STDOUT_TO_DEV_NULL;
+
+ /* Spawn SpamAssassin with an open stdin pipe. */
+ success = g_spawn_async_with_pipes (
+ NULL,
+ (gchar **) argv,
+ NULL,
+ flags,
+ NULL, NULL,
+ &child_pid,
+ &standard_input,
+ (output_buffer != NULL) ? &standard_output : NULL,
+ NULL,
+ error);
+
+ if (!success) {
+ gchar *command_line;
+
+ command_line = g_strjoinv (" ", (gchar **) argv);
+ g_prefix_error (
+ error, _("Failed to spawn SpamAssassin (%s): "),
+ command_line);
+ g_free (command_line);
+
+ return SPAM_ASSASSIN_EXIT_STATUS_ERROR;
+ }
+
+ if (message != NULL) {
+ CamelStream *stream;
+ gssize bytes_written;
+
+ /* Stream the CamelMimeMessage to SpamAssassin. */
+ stream = camel_stream_fs_new_with_fd (standard_input);
+ bytes_written = camel_data_wrapper_write_to_stream_sync (
+ CAMEL_DATA_WRAPPER (message),
+ stream, cancellable, error);
+ success = (bytes_written >= 0) &&
+ (camel_stream_close (stream, cancellable, error) == 0);
+ g_object_unref (stream);
+
+ if (!success) {
+ g_spawn_close_pid (child_pid);
+ g_prefix_error (
+ error, _("Failed to stream mail "
+ "message content to SpamAssassin: "));
+ return SPAM_ASSASSIN_EXIT_STATUS_ERROR;
+ }
+
+ } else if (input_data != NULL) {
+ gssize bytes_written;
+
+ /* Write raw data directly to SpamAssassin. */
+ bytes_written = camel_write (
+ standard_input, input_data,
+ strlen (input_data), cancellable, error);
+ success = (bytes_written >= 0);
+
+ close (standard_input);
+
+ if (!success) {
+ g_spawn_close_pid (child_pid);
+ g_prefix_error (
+ error, _("Failed to write '%s' "
+ "to SpamAssassin: "), input_data);
+ return SPAM_ASSASSIN_EXIT_STATUS_ERROR;
+ }
+ }
+
+ if (output_buffer != NULL) {
+ CamelStream *input_stream;
+ CamelStream *output_stream;
+ gssize bytes_written;
+
+ input_stream = camel_stream_fs_new_with_fd (standard_output);
+
+ output_stream = camel_stream_mem_new ();
+ camel_stream_mem_set_byte_array (
+ CAMEL_STREAM_MEM (output_stream), output_buffer);
+
+ bytes_written = camel_stream_write_to_stream (
+ input_stream, output_stream, cancellable, error);
+ g_byte_array_append (output_buffer, (guint8 *) "", 1);
+ success = (bytes_written >= 0);
+
+ g_object_unref (input_stream);
+ g_object_unref (output_stream);
+
+ if (!success) {
+ g_spawn_close_pid (child_pid);
+ g_prefix_error (
+ error, _("Failed to read "
+ "output from SpamAssassin: "));
+ return SPAM_ASSASSIN_EXIT_STATUS_ERROR;
+ }
+ }
+
+ /* Wait for the SpamAssassin process to terminate
+ * using GLib's main loop for better portability. */
+
+ context = g_main_context_new ();
+
+ source = g_child_watch_source_new (child_pid);
+ g_source_set_callback (
+ source, (GSourceFunc)
+ spam_assassin_exited_cb,
+ &source_data, NULL);
+ g_source_attach (source, context);
+ g_source_unref (source);
+
+ source_data.loop = g_main_loop_new (context, TRUE);
+ source_data.exit_code = 0;
+
+#ifdef G_OS_UNIX
+ if (G_IS_CANCELLABLE (cancellable))
+ handler_id = g_cancellable_connect (
+ cancellable,
+ G_CALLBACK (spam_assassin_cancelled_cb),
+ &child_pid, (GDestroyNotify) NULL);
+#endif
+
+ g_main_loop_run (source_data.loop);
+
+ if (handler_id > 0)
+ g_cancellable_disconnect (cancellable, handler_id);
+
+ g_main_loop_unref (source_data.loop);
+ source_data.loop = NULL;
+
+ g_main_context_unref (context);
+
+ /* Clean up. */
+
+ g_spawn_close_pid (child_pid);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ source_data.exit_code = SPAM_ASSASSIN_EXIT_STATUS_ERROR;
+
+ else if (source_data.exit_code == SPAM_ASSASSIN_EXIT_STATUS_ERROR)
+ g_set_error_literal (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("SpamAssassin either crashed or "
+ "failed to process a mail message"));
+
+ return source_data.exit_code;
+}
+
+static gint
+spam_assassin_command (const gchar **argv,
+ CamelMimeMessage *message,
+ const gchar *input_data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return spam_assassin_command_full (
+ argv, message, input_data, NULL, cancellable, error);
+}
+
+static gboolean
+spam_assassin_get_local_only (ESpamAssassin *extension)
+{
+ return extension->local_only;
+}
+
+static void
+spam_assassin_set_local_only (ESpamAssassin *extension,
+ gboolean local_only)
+{
+ extension->local_only = local_only;
+
+ g_object_notify (G_OBJECT (extension), "local-only");
+}
+
+static const gchar *
+spam_assassin_get_spamc_binary (ESpamAssassin *extension)
+{
+ return extension->spamc_binary;
+}
+
+static void
+spam_assassin_set_spamc_binary (ESpamAssassin *extension,
+ const gchar *spamc_binary)
+{
+ g_free (extension->spamc_binary);
+ extension->spamc_binary = g_strdup (spamc_binary);
+
+ g_object_notify (G_OBJECT (extension), "spamc-binary");
+}
+
+static const gchar *
+spam_assassin_get_spamd_binary (ESpamAssassin *extension)
+{
+ return extension->spamd_binary;
+}
+
+static void
+spam_assassin_set_spamd_binary (ESpamAssassin *extension,
+ const gchar *spamd_binary)
+{
+ g_free (extension->spamd_binary);
+ extension->spamd_binary = g_strdup (spamd_binary);
+
+ g_object_notify (G_OBJECT (extension), "spamd-binary");
+}
+
+static const gchar *
+spam_assassin_get_socket_path (ESpamAssassin *extension)
+{
+ return extension->socket_path;
+}
+
+static void
+spam_assassin_set_socket_path (ESpamAssassin *extension,
+ const gchar *socket_path)
+{
+ g_free (extension->socket_path);
+ extension->socket_path = g_strdup (socket_path);
+
+ g_object_notify (G_OBJECT (extension), "socket-path");
+}
+
+static gboolean
+spam_assassin_get_use_daemon (ESpamAssassin *extension)
+{
+ return extension->use_daemon;
+}
+
+static void
+spam_assassin_set_use_daemon (ESpamAssassin *extension,
+ gboolean use_daemon)
+{
+ extension->use_daemon = use_daemon;
+
+ g_object_notify (G_OBJECT (extension), "use-daemon");
+}
+
+static gboolean
+spam_assassin_get_version (ESpamAssassin *extension,
+ gint *spam_assassin_version,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GByteArray *output_buffer;
+ gint exit_code;
+ guint ii;
+
+ const gchar *argv[] = {
+ SA_LEARN_BINARY,
+ "--version",
+ NULL
+ };
+
+ if (extension->version_set) {
+ if (spam_assassin_version != NULL)
+ *spam_assassin_version = extension->version;
+ return TRUE;
+ }
+
+ output_buffer = g_byte_array_new ();
+
+ exit_code = spam_assassin_command_full (
+ argv, NULL, NULL, output_buffer, cancellable, error);
+
+ if (exit_code != 0) {
+ g_byte_array_free (output_buffer, TRUE);
+ return FALSE;
+ }
+
+ for (ii = 0; ii < output_buffer->len; ii++) {
+ if (g_ascii_isdigit (output_buffer->data[ii])) {
+ guint8 ch = output_buffer->data[ii];
+ extension->version = (ch - '0');
+ extension->version_set = TRUE;
+ break;
+ }
+ }
+
+ if (spam_assassin_version != NULL)
+ *spam_assassin_version = extension->version;
+
+ g_byte_array_free (output_buffer, TRUE);
+
+ return TRUE;
+}
+
+static void
+spam_assassin_test_spamd_allow_tell (ESpamAssassin *extension)
+{
+ gint exit_code;
+ GError *error = NULL;
+
+ const gchar *argv[] = {
+ SPAMC_BINARY,
+ "--learntype=forget",
+ NULL
+ };
+
+ /* Check if spamd is running with --allow-tell. */
+
+ exit_code = spam_assassin_command (argv, NULL, "\n", NULL, &error);
+ extension->spamd_using_allow_tell = (exit_code == 0);
+
+ if (error != NULL) {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+}
+
+static gboolean
+spam_assassin_test_spamd_running (ESpamAssassin *extension,
+ gboolean system_spamd)
+{
+ const gchar *argv[5];
+ gint exit_code;
+ gint ii = 0;
+ GError *error = NULL;
+
+ g_mutex_lock (extension->socket_path_mutex);
+
+ argv[ii++] = extension->spamc_binary;
+ argv[ii++] = "--no-safe-fallback";
+ if (!system_spamd) {
+ argv[ii++] = "--socket";
+ argv[ii++] = extension->socket_path;
+ }
+ argv[ii] = NULL;
+
+ g_assert (ii < G_N_ELEMENTS (argv));
+
+ exit_code = spam_assassin_command (
+ argv, NULL, "From test@127.0.0.1", NULL, &error);
+
+ if (error != NULL) {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+
+ g_mutex_unlock (extension->socket_path_mutex);
+
+ return (exit_code == 0);
+}
+
+static gboolean
+spam_assassin_start_our_own_daemon (ESpamAssassin *extension)
+{
+ const gchar *argv[8];
+ gchar *pid_file;
+ gchar *socket_path;
+ gboolean started = FALSE;
+ gint exit_code;
+ gint ii = 0;
+ GError *error = NULL;
+
+ g_mutex_lock (extension->socket_path_mutex);
+
+ pid_file = e_mktemp ("spamd-pid-file-XXXXXX");
+ socket_path = e_mktemp ("spamd-socket-path-XXXXXX");
+
+ argv[ii++] = extension->spamd_binary;
+ argv[ii++] = "--socketpath";
+ argv[ii++] = socket_path;
+
+ if (spam_assassin_get_local_only (extension))
+ argv[ii++] = "--local";
+
+ argv[ii++] = "--max-children=1";
+ argv[ii++] = "--pidfile";
+ argv[ii++] = pid_file;
+ argv[ii] = NULL;
+
+ g_assert (ii < G_N_ELEMENTS (argv));
+
+ exit_code = spam_assassin_command (argv, NULL, NULL, NULL, &error);
+
+ if (error != NULL) {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ goto exit;
+ }
+
+ if (exit_code == 0) {
+ /* Wait for the socket path to appear. */
+ for (ii = 0; ii < DAEMON_MAX_RETRIES; ii++) {
+ if (g_file_test (socket_path, G_FILE_TEST_EXISTS)) {
+ started = TRUE;
+ break;
+ }
+ g_usleep (DAEMON_RETRY_DELAY * G_USEC_PER_SEC);
+ }
+ }
+
+ /* Set these directly to avoid emitting "notify" signals. */
+ if (started) {
+ g_free (extension->pid_file);
+ extension->pid_file = pid_file;
+ pid_file = NULL;
+
+ g_free (extension->socket_path);
+ extension->socket_path = socket_path;
+ socket_path = NULL;
+ }
+
+exit:
+ g_free (pid_file);
+ g_free (socket_path);
+
+ g_mutex_unlock (extension->socket_path_mutex);
+
+ return started;
+}
+
+static void
+spam_assassin_kill_our_own_daemon (ESpamAssassin *extension)
+{
+ gint pid;
+ gchar *contents = NULL;
+ GError *error = NULL;
+
+ g_mutex_lock (extension->socket_path_mutex);
+
+ g_free (extension->socket_path);
+ extension->socket_path = NULL;
+
+ g_mutex_unlock (extension->socket_path_mutex);
+
+ if (extension->pid_file == NULL)
+ return;
+
+ g_file_get_contents (extension->pid_file, &contents, NULL, &error);
+
+ if (error != NULL) {
+ g_warn_if_fail (contents == NULL);
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ g_return_if_fail (contents != NULL);
+
+ pid = atoi (contents);
+ g_free (contents);
+
+ if (pid > 0 && kill (pid, SIGTERM) == 0)
+ waitpid (pid, NULL, 0);
+}
+
+static void
+spam_assassin_test_spamd (ESpamAssassin *extension)
+{
+ const gchar *spamd_binary;
+ gboolean try_system_spamd;
+
+ /* XXX SpamAssassin could really benefit from a D-Bus interface
+ * these days. These tests are just needlessly painful for
+ * clients trying to talk to an already-running spamd. */
+
+ extension->use_spamc = FALSE;
+ spamd_binary = extension->spamd_binary;
+ try_system_spamd = (g_strcmp0 (spamd_binary, SPAMD_BINARY) == 0);
+
+ if (extension->local_only && try_system_spamd) {
+ gint exit_code;
+
+ /* Run a shell command to check for a running
+ * spamd process with a -L/--local option or a
+ * -p/--port option. */
+
+ const gchar *argv[] = {
+ "/bin/sh",
+ "-c",
+ "ps ax | grep -v grep | "
+ "grep -E 'spamd.*(\\-L|\\-\\-local)' | "
+ "grep -E -v '\\ \\-p\\ |\\ \\-\\-port\\ '",
+ NULL
+ };
+
+ exit_code = spam_assassin_command (
+ argv, NULL, NULL, NULL, NULL);
+ try_system_spamd = (exit_code == 0);
+ }
+
+ /* Try to use the system spamd first. */
+ if (try_system_spamd) {
+ if (spam_assassin_test_spamd_running (extension, TRUE)) {
+ extension->use_spamc = TRUE;
+ extension->system_spamd_available = TRUE;
+ }
+ }
+
+ /* If there's no system spamd running, try
+ * to use one with a user specified socket. */
+ if (!extension->use_spamc && extension->socket_path != NULL) {
+ if (spam_assassin_test_spamd_running (extension, FALSE)) {
+ extension->use_spamc = TRUE;
+ extension->system_spamd_available = FALSE;
+ }
+ }
+
+ /* Still unsuccessful? Try to start our own spamd. */
+ if (!extension->use_spamc) {
+ extension->use_spamc =
+ spam_assassin_start_our_own_daemon (extension) &&
+ spam_assassin_test_spamd_running (extension, FALSE);
+ }
+}
+
+static void
+spam_assassin_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_LOCAL_ONLY:
+ spam_assassin_set_local_only (
+ E_SPAM_ASSASSIN (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_SPAMC_BINARY:
+ spam_assassin_set_spamc_binary (
+ E_SPAM_ASSASSIN (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_SPAMD_BINARY:
+ spam_assassin_set_spamd_binary (
+ E_SPAM_ASSASSIN (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_SOCKET_PATH:
+ spam_assassin_set_socket_path (
+ E_SPAM_ASSASSIN (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_USE_DAEMON:
+ spam_assassin_set_use_daemon (
+ E_SPAM_ASSASSIN (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+spam_assassin_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_LOCAL_ONLY:
+ g_value_set_boolean (
+ value, spam_assassin_get_local_only (
+ E_SPAM_ASSASSIN (object)));
+ return;
+
+ case PROP_SPAMC_BINARY:
+ g_value_set_string (
+ value, spam_assassin_get_spamc_binary (
+ E_SPAM_ASSASSIN (object)));
+ return;
+
+ case PROP_SPAMD_BINARY:
+ g_value_set_string (
+ value, spam_assassin_get_spamd_binary (
+ E_SPAM_ASSASSIN (object)));
+ return;
+
+ case PROP_SOCKET_PATH:
+ g_value_set_string (
+ value, spam_assassin_get_socket_path (
+ E_SPAM_ASSASSIN (object)));
+ return;
+
+ case PROP_USE_DAEMON:
+ g_value_set_boolean (
+ value, spam_assassin_get_use_daemon (
+ E_SPAM_ASSASSIN (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+spam_assassin_finalize (GObject *object)
+{
+ ESpamAssassin *extension = E_SPAM_ASSASSIN (object);
+
+ /* If we started our own daemon, kill it. */
+ spam_assassin_kill_our_own_daemon (extension);
+
+ g_mutex_free (extension->socket_path_mutex);
+
+ g_free (extension->pid_file);
+ g_free (extension->socket_path);
+ g_free (extension->spamc_binary);
+ g_free (extension->spamd_binary);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_spam_assassin_parent_class)->finalize (object);
+}
+
+static gboolean
+spam_assassin_available (EMailJunkFilter *junk_filter)
+{
+ ESpamAssassin *extension = E_SPAM_ASSASSIN (junk_filter);
+ gboolean available;
+ GError *error = NULL;
+
+ available = spam_assassin_get_version (extension, NULL, NULL, &error);
+
+ /* XXX These tests block like crazy so maybe this isn't the best
+ * place to be doing this, but the first available() call is
+ * done at startup before the UI is shown. So hopefully the
+ * delay will not be noticeable. */
+ if (available && extension->use_daemon && !extension->spamd_tested) {
+ extension->spamd_tested = TRUE;
+ spam_assassin_test_spamd (extension);
+ spam_assassin_test_spamd_allow_tell (extension);
+ }
+
+ if (error != NULL) {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+
+ return available;
+}
+
+static GtkWidget *
+spam_assassin_new_config_widget (EMailJunkFilter *junk_filter)
+{
+ GtkWidget *box;
+ GtkWidget *widget;
+ GtkWidget *container;
+ gchar *markup;
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+
+ markup = g_markup_printf_escaped (
+ "<b>%s</b>", _("SpamAssassin Options"));
+ widget = gtk_label_new (markup);
+ gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+ gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
+ gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+ g_free (markup);
+
+ widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_check_button_new_with_mnemonic (
+ _("I_nclude remote tests"));
+ gtk_widget_set_margin_left (widget, 12);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ g_object_bind_property (
+ junk_filter, "local-only",
+ widget, "active",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE |
+ G_BINDING_INVERT_BOOLEAN);
+
+ markup = g_markup_printf_escaped (
+ "<small>%s</small>",
+ _("This will make SpamAssassin more reliable, but slower."));
+ widget = gtk_label_new (markup);
+ gtk_widget_set_margin_left (widget, 36);
+ gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+ gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+ g_free (markup);
+
+ return box;
+}
+
+static gboolean
+spam_assassin_classify (CamelJunkFilter *junk_filter,
+ CamelMimeMessage *message,
+ CamelJunkStatus *status,
+ GCancellable *cancellable,
+ GError **error)
+{
+ ESpamAssassin *extension = E_SPAM_ASSASSIN (junk_filter);
+ const gchar *argv[7];
+ gboolean using_spamc;
+ gint exit_code;
+ gint ii = 0;
+
+ g_mutex_lock (extension->socket_path_mutex);
+
+ using_spamc = (extension->use_spamc && extension->use_daemon);
+
+ if (using_spamc) {
+ argv[ii++] = extension->spamc_binary;
+ argv[ii++] = "--check";
+ argv[ii++] = "--timeout=60";
+ if (!extension->system_spamd_available) {
+ argv[ii++] = "--socket";
+ argv[ii++] = extension->socket_path;
+ }
+ } else {
+ argv[ii++] = SPAMASSASSIN_BINARY;
+ argv[ii++] = "--exit-code";
+ if (extension->local_only)
+ argv[ii++] = "--local";
+ }
+ argv[ii] = NULL;
+
+ g_assert (ii < G_N_ELEMENTS (argv));
+
+ exit_code = spam_assassin_command (
+ argv, message, NULL, cancellable, error);
+
+ /* For either program, exit code 0 means the message is ham. */
+ if (exit_code == 0)
+ *status = CAMEL_JUNK_STATUS_MESSAGE_IS_NOT_JUNK;
+
+ /* spamassassin(1) only specifies zero and non-zero exit codes. */
+ else if (!using_spamc)
+ *status = CAMEL_JUNK_STATUS_MESSAGE_IS_JUNK;
+
+ /* Whereas spamc(1) explicitly states exit code 1 means spam. */
+ else if (exit_code == 1)
+ *status = CAMEL_JUNK_STATUS_MESSAGE_IS_JUNK;
+
+ /* Consider any other spamc(1) exit code to be inconclusive
+ * since it most likely failed to process the message. */
+ else
+ *status = CAMEL_JUNK_STATUS_INCONCLUSIVE;
+
+ /* Check that the return value and GError agree. */
+ if (exit_code != SPAM_ASSASSIN_EXIT_STATUS_ERROR)
+ g_warn_if_fail (error == NULL || *error == NULL);
+ else
+ g_warn_if_fail (error == NULL || *error != NULL);
+
+ g_mutex_unlock (extension->socket_path_mutex);
+
+ return (exit_code != SPAM_ASSASSIN_EXIT_STATUS_ERROR);
+}
+
+static gboolean
+spam_assassin_learn_junk (CamelJunkFilter *junk_filter,
+ CamelMimeMessage *message,
+ GCancellable *cancellable,
+ GError **error)
+{
+ ESpamAssassin *extension = E_SPAM_ASSASSIN (junk_filter);
+ const gchar *argv[5];
+ gint exit_code;
+ gint ii = 0;
+
+ if (extension->spamd_using_allow_tell) {
+ argv[ii++] = extension->spamc_binary;
+ argv[ii++] = "--learntype=spam";
+ } else {
+ argv[ii++] = SA_LEARN_BINARY;
+ argv[ii++] = "--spam";
+ if (extension->version >= 3)
+ argv[ii++] = "--no-sync";
+ else
+ argv[ii++] = "--no-rebuild";
+ if (extension->local_only)
+ argv[ii++] = "--local";
+ }
+ argv[ii] = NULL;
+
+ g_assert (ii < G_N_ELEMENTS (argv));
+
+ exit_code = spam_assassin_command (
+ argv, message, NULL, cancellable, error);
+
+ /* Check that the return value and GError agree. */
+ if (exit_code == SPAM_ASSASSIN_EXIT_STATUS_SUCCESS)
+ g_warn_if_fail (error == NULL || *error == NULL);
+ else
+ g_warn_if_fail (error == NULL || *error != NULL);
+
+ return (exit_code == SPAM_ASSASSIN_EXIT_STATUS_SUCCESS);
+}
+
+static gboolean
+spam_assassin_learn_not_junk (CamelJunkFilter *junk_filter,
+ CamelMimeMessage *message,
+ GCancellable *cancellable,
+ GError **error)
+{
+ ESpamAssassin *extension = E_SPAM_ASSASSIN (junk_filter);
+ const gchar *argv[5];
+ gint exit_code;
+ gint ii = 0;
+
+ if (extension->spamd_using_allow_tell) {
+ argv[ii++] = extension->spamc_binary;
+ argv[ii++] = "--learntype=ham";
+ } else {
+ argv[ii++] = SA_LEARN_BINARY;
+ argv[ii++] = "--ham";
+ if (extension->version >= 3)
+ argv[ii++] = "--no-sync";
+ else
+ argv[ii++] = "--no-rebuild";
+ if (extension->local_only)
+ argv[ii++] = "--local";
+ }
+ argv[ii] = NULL;
+
+ g_assert (ii < G_N_ELEMENTS (argv));
+
+ exit_code = spam_assassin_command (
+ argv, message, NULL, cancellable, error);
+
+ /* Check that the return value and GError agree. */
+ if (exit_code == SPAM_ASSASSIN_EXIT_STATUS_SUCCESS)
+ g_warn_if_fail (error == NULL || *error == NULL);
+ else
+ g_warn_if_fail (error == NULL || *error != NULL);
+
+ return (exit_code == SPAM_ASSASSIN_EXIT_STATUS_SUCCESS);
+}
+
+static gboolean
+spam_assassin_synchronize (CamelJunkFilter *junk_filter,
+ GCancellable *cancellable,
+ GError **error)
+{
+ ESpamAssassin *extension = E_SPAM_ASSASSIN (junk_filter);
+ const gchar *argv[4];
+ gint exit_code;
+ gint ii = 0;
+
+ /* If we're using a spamd that allows learning,
+ * there's no need to synchronize anything. */
+ if (extension->spamd_using_allow_tell)
+ return TRUE;
+
+ argv[ii++] = SA_LEARN_BINARY;
+ if (extension->version >= 3)
+ argv[ii++] = "--sync";
+ else
+ argv[ii++] = "--rebuild";
+ if (extension->local_only)
+ argv[ii++] = "--local";
+ argv[ii] = NULL;
+
+ g_assert (ii < G_N_ELEMENTS (argv));
+
+ exit_code = spam_assassin_command (
+ argv, NULL, NULL, cancellable, error);
+
+ /* Check that the return value and GError agree. */
+ if (exit_code == SPAM_ASSASSIN_EXIT_STATUS_SUCCESS)
+ g_warn_if_fail (error == NULL || *error == NULL);
+ else
+ g_warn_if_fail (error == NULL || *error != NULL);
+
+ return (exit_code == SPAM_ASSASSIN_EXIT_STATUS_SUCCESS);
+}
+
+static void
+e_spam_assassin_class_init (ESpamAssassinClass *class)
+{
+ GObjectClass *object_class;
+ EMailJunkFilterClass *junk_filter_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = spam_assassin_set_property;
+ object_class->get_property = spam_assassin_get_property;
+ object_class->finalize = spam_assassin_finalize;
+
+ junk_filter_class = E_MAIL_JUNK_FILTER_CLASS (class);
+ junk_filter_class->filter_name = "SpamAssassin";
+ junk_filter_class->display_name = _("SpamAssassin");
+ junk_filter_class->available = spam_assassin_available;
+ junk_filter_class->new_config_widget = spam_assassin_new_config_widget;
+
+ /* XXX Argh, the boolean sense of the GConf key is inverted from
+ * that of the checkbox widget. The checkbox wording is more
+ * natural, but GConfBridge doesn't support transform functions
+ * so the property has to match the sense of the GConf key. */
+ g_object_class_install_property (
+ object_class,
+ PROP_LOCAL_ONLY,
+ g_param_spec_boolean (
+ "local-only",
+ "Local Only",
+ "Do not use tests requiring DNS lookups",
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SPAMC_BINARY,
+ g_param_spec_string (
+ "spamc-binary",
+ "spamc Binary",
+ "File path for the spamc binary",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SPAMD_BINARY,
+ g_param_spec_string (
+ "spamd-binary",
+ "spamd Binary",
+ "File path for the spamd binary",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SOCKET_PATH,
+ g_param_spec_string (
+ "socket-path",
+ "Socket Path",
+ "Socket path for a SpamAssassin daemon",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_USE_DAEMON,
+ g_param_spec_boolean (
+ "use-daemon",
+ "Use Daemon",
+ "Whether to use a SpamAssassin daemon",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_spam_assassin_class_finalize (ESpamAssassinClass *class)
+{
+}
+
+static void
+e_spam_assassin_interface_init (CamelJunkFilterInterface *interface)
+{
+ interface->classify = spam_assassin_classify;
+ interface->learn_junk = spam_assassin_learn_junk;
+ interface->learn_not_junk = spam_assassin_learn_not_junk;
+ interface->synchronize = spam_assassin_synchronize;
+}
+
+static void
+e_spam_assassin_init (ESpamAssassin *extension)
+{
+ extension->socket_path_mutex = g_mutex_new ();
+
+ /* XXX Once we move to GSettings these probably don't
+ * need to be properties anymore. GConfBridge is
+ * just easier to deal with than GConfClient. */
+
+ gconf_bridge_bind_property (
+ gconf_bridge_get (),
+ "/apps/evolution/mail/junk/sa/local_only",
+ G_OBJECT (extension), "local-only");
+
+ gconf_bridge_bind_property (
+ gconf_bridge_get (),
+ "/apps/evolution/mail/junk/sa/spamc_binary",
+ G_OBJECT (extension), "spamc-binary");
+
+ gconf_bridge_bind_property (
+ gconf_bridge_get (),
+ "/apps/evolution/mail/junk/sa/spamd_binary",
+ G_OBJECT (extension), "spamd-binary");
+
+ gconf_bridge_bind_property (
+ gconf_bridge_get (),
+ "/apps/evolution/mail/junk/sa/socket_path",
+ G_OBJECT (extension), "socket-path");
+
+ gconf_bridge_bind_property (
+ gconf_bridge_get (),
+ "/apps/evolution/mail/junk/sa/use_daemon",
+ G_OBJECT (extension), "use-daemon");
+
+ if (extension->spamc_binary == NULL)
+ extension->spamc_binary = g_strdup (SPAMC_BINARY);
+
+ if (extension->spamd_binary == NULL)
+ extension->spamd_binary = g_strdup (SPAMD_BINARY);
+}
+
+G_MODULE_EXPORT void
+e_module_load (GTypeModule *type_module)
+{
+ e_spam_assassin_register_type (type_module);
+}
+
+G_MODULE_EXPORT void
+e_module_unload (GTypeModule *type_module)
+{
+}
diff --git a/modules/spamassassin/evolution-spamassassin.schemas.in b/modules/spamassassin/evolution-spamassassin.schemas.in
new file mode 100644
index 0000000000..b344d1bffd
--- /dev/null
+++ b/modules/spamassassin/evolution-spamassassin.schemas.in
@@ -0,0 +1,33 @@
+<gconfschemafile>
+ <schemalist>
+
+ <schema>
+ <key>/schemas/apps/evolution/mail/junk/sa/local_only</key>
+ <applyto>/apps/evolution/mail/junk/sa/local_only</applyto>
+ <owner>evolution-spamassassin</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Use only local spam tests.</short>
+ <long>
+ Use only the local spam tests (no DNS).
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/evolution/mail/junk/sa/use_daemon</key>
+ <applyto>/apps/evolution/mail/junk/sa/use_daemon</applyto>
+ <owner>evolution-spamassassin</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Use SpamAssassin daemon and client</short>
+ <long>
+ Use SpamAssassin daemon and client (spamc/spamd).
+ </long>
+ </locale>
+ </schema>
+
+ </schemalist>
+</gconfschemafile>