+2006-02-24 [colin] 2.0.0cvs82
+
+ * src/Makefile.am
+ * src/mainwindow.c
+ * src/mainwindow.h
+ * src/procmsg.c
+ * src/procmsg.h
+ * src/stock_pixmap.c
+ * src/stock_pixmap.h
+ * src/summaryview.c
+ * src/summaryview.h
+ * src/gtk/icon_legend.c
+ * src/pixmaps/spam.xpm ** NEW FILE **
+ * src/plugins/spamassassin/spamassassin.c
+ * src/plugins/spamassassin/spamassassin_gtk.c
+ Add spam learning interface (Mark/Mark as
+ (spam|ham) menus)
+
2006-02-24 [wwp] 2.0.0cvs81
* src/compose.c
( cvs diff -u -r 1.101.2.20 -r 1.101.2.21 src/news.c; cvs diff -u -r 1.21.2.3 -r 1.21.2.4 src/news.h; cvs diff -u -r 1.2.2.15 -r 1.2.2.16 src/news_gtk.c; ) > 2.0.0cvs79.patchset
( cvs diff -u -r 1.274.2.98 -r 1.274.2.99 src/mainwindow.c; ) > 2.0.0cvs80.patchset
( cvs diff -u -r 1.382.2.246 -r 1.382.2.247 src/compose.c; cvs diff -u -r 1.50.2.21 -r 1.50.2.22 src/compose.h; cvs diff -u -r 1.60.2.13 -r 1.60.2.14 src/filtering.c; ) > 2.0.0cvs81.patchset
+( cvs diff -u -r 1.155.2.35 -r 1.155.2.36 src/Makefile.am; cvs diff -u -r 1.274.2.99 -r 1.274.2.100 src/mainwindow.c; cvs diff -u -r 1.39.2.12 -r 1.39.2.13 src/mainwindow.h; cvs diff -u -r 1.150.2.51 -r 1.150.2.52 src/procmsg.c; cvs diff -u -r 1.60.2.23 -r 1.60.2.24 src/procmsg.h; cvs diff -u -r 1.25.2.20 -r 1.25.2.21 src/stock_pixmap.c; cvs diff -u -r 1.18.2.14 -r 1.18.2.15 src/stock_pixmap.h; cvs diff -u -r 1.395.2.173 -r 1.395.2.174 src/summaryview.c; cvs diff -u -r 1.68.2.15 -r 1.68.2.16 src/summaryview.h; cvs diff -u -r 1.1.2.6 -r 1.1.2.7 src/gtk/icon_legend.c; diff -u /dev/null src/pixmaps/spam.xpm; cvs diff -u -r 1.18.2.17 -r 1.18.2.18 src/plugins/spamassassin/spamassassin.c; cvs diff -u -r 1.23.2.18 -r 1.23.2.19 src/plugins/spamassassin/spamassassin_gtk.c; ) > 2.0.0cvs82.patchset
MICRO_VERSION=0
INTERFACE_AGE=0
BINARY_AGE=0
-EXTRA_VERSION=81
+EXTRA_VERSION=82
EXTRA_RELEASE=
EXTRA_GTK2_VERSION=
pixmaps/quicksearch.xpm \
pixmaps/replied.xpm \
pixmaps/search.xpm \
+ pixmaps/spam.xpm \
pixmaps/sylpheed_icon.xpm \
pixmaps/sylpheed_logo.xpm \
pixmaps/sylpheed.xpm \
#include "stock_pixmap.h"
#include "prefs_gtk.h"
-#define ICONS 15
+#define ICONS 16
StockPixmap legend_icons[ICONS] = {
STOCK_PIXMAP_NEW,
STOCK_PIXMAP_MARK,
STOCK_PIXMAP_LOCKED,
STOCK_PIXMAP_IGNORETHREAD,
+ STOCK_PIXMAP_SPAM,
STOCK_PIXMAP_DIR_OPEN,
STOCK_PIXMAP_DIR_OPEN_HRM,
STOCK_PIXMAP_DIR_OPEN_MARK,
N_("Marked message"),
N_("Locked message"),
N_("Message is in an ignored thread"),
+ N_("Message is spam"),
N_("Folder (normal, opened)"),
N_("Folder with read messages hidden"),
N_("Folder contains marked emails"),
static void mark_all_read_cb (MainWindow *mainwin,
guint action,
GtkWidget *widget);
+static void mark_as_spam_cb (MainWindow *mainwin,
+ guint action,
+ GtkWidget *widget);
static void reedit_cb (MainWindow *mainwin,
guint action,
{N_("/_Message/_Mark/_Unmark"), "U", unmark_cb, 0, NULL},
{N_("/_Message/_Mark/---"), NULL, NULL, 0, "<Separator>"},
{N_("/_Message/_Mark/Mark as unr_ead"), "<shift>exclam", mark_as_unread_cb, 0, NULL},
- {N_("/_Message/_Mark/Mark as rea_d"),
- NULL, mark_as_read_cb, 0, NULL},
+ {N_("/_Message/_Mark/Mark as rea_d"), NULL, mark_as_read_cb, 0, NULL},
{N_("/_Message/_Mark/Mark all _read"), NULL, mark_all_read_cb, 0, NULL},
+ {N_("/_Message/_Mark/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_Message/_Mark/Mark as _spam"), NULL, mark_as_spam_cb, 1, NULL},
+ {N_("/_Message/_Mark/Mark as _ham"), NULL, mark_as_spam_cb, 0, NULL},
{N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
{N_("/_Message/Re-_edit"), NULL, reedit_cb, 0, NULL},
break;
}
}
+
+ if (procmsg_spam_can_learn()) {
+ state |= M_CAN_LEARN_SPAM;
+ }
if (inc_is_active())
state |= M_INC_ACTIVE;
{"/Message/Delete..." , M_TARGET_EXIST|M_ALLOW_DELETE},
{"/Message/Cancel a news message" , M_TARGET_EXIST|M_ALLOW_DELETE|M_NEWS},
{"/Message/Mark" , M_TARGET_EXIST},
+ {"/Message/Mark/Mark as spam" , M_TARGET_EXIST|M_CAN_LEARN_SPAM},
+ {"/Message/Mark/Mark as ham" , M_TARGET_EXIST|M_CAN_LEARN_SPAM},
{"/Message/Re-edit" , M_HAVE_ACCOUNT|M_ALLOW_REEDIT},
{"/Tools/Add sender to address book" , M_SINGLE_TARGET_EXIST},
summary_mark_all_read(mainwin->summaryview);
}
+static void mark_as_spam_cb(MainWindow *mainwin, guint action,
+ GtkWidget *widget)
+{
+ summary_mark_as_spam(mainwin->summaryview, action, NULL);
+}
+
static void reedit_cb(MainWindow *mainwin, guint action, GtkWidget *widget)
{
summary_reedit(mainwin->summaryview);
M_HAVE_NEWS_ACCOUNT = 1 << 12,
M_HIDE_READ_MSG = 1 << 13,
M_DELAY_EXEC = 1 << 14,
- M_NOT_NEWS = 1 << 15
+ M_NOT_NEWS = 1 << 15,
+ M_CAN_LEARN_SPAM = 1 << 16
} SensitiveCond;
typedef enum
--- /dev/null
+/* XPM */
+static char * spam_xpm[] = {
+"10 10 4 1",
+" c None",
+". c #DF0B18",
+"+ c #CC0000",
+"@ c #D5050C",
+" .++++. ",
+" ++....+@ ",
+".+. .++.",
+"+. .+..+",
+"+. .+. .+",
+"+. .+. .+",
+"+..+. .+",
+".++. .+.",
+" ++....++ ",
+" .++++. "};
}
if (transport_setup(&trans, flags) != EX_OK) {
+ log_error("Spamassassin plugin couldn't connect to spamd.\n");
debug_print("failed to setup transport\n");
return FALSE;
}
int pid = 0;
int status;
- if (config.transport == SPAMASSASSIN_DISABLED)
+ if (config.transport == SPAMASSASSIN_DISABLED) {
+ log_error("Spamassassin plugin is disabled by its preferences.\n");
return FALSE;
-
+ }
debug_print("Filtering message %d\n", msginfo->msgnum);
if (message_callback != NULL)
message_callback(_("SpamAssassin: filtering message..."));
if (is_spam) {
debug_print("message is spam\n");
-
+ procmsg_msginfo_set_flags(msginfo, MSG_SPAM, 0);
if (config.receive_spam) {
FolderItem *save_folder;
save_folder = folder_get_default_trash();
procmsg_msginfo_unset_flags(msginfo, ~0, 0);
+ procmsg_msginfo_set_flags(msginfo, MSG_SPAM, 0);
folder_item_move_msg(save_folder, msginfo);
} else {
folder_item_remove_msg(msginfo->folder, msginfo->msgnum);
}
return TRUE;
+ } else {
+ debug_print("message is ham\n");
+ procmsg_msginfo_unset_flags(msginfo, MSG_SPAM, 0);
}
-
return FALSE;
}
return &config;
}
+void spamassassin_learn(MsgInfo *msginfo, GSList *msglist, gboolean spam)
+{
+ gchar *cmd = NULL;
+ gchar *file = NULL;
+ gboolean async = FALSE;
+
+ if (msginfo == NULL && msglist == NULL)
+ return;
+
+ if (msginfo) {
+ file = procmsg_get_message_file(msginfo);
+ if (file == NULL)
+ return;
+ cmd = g_strdup_printf("sa-learn %s %s",
+ spam?"--spam":"--ham", file);
+ }
+ if (msglist) {
+ GSList *cur;
+ MsgInfo *info;
+ cmd = g_strdup_printf("sa-learn %s", spam?"--spam":"--ham");
+ for (cur = msglist; cur; cur = cur->next) {
+ info = (MsgInfo *)cur->data;
+ gchar *tmpcmd = NULL;
+ gchar *tmpfile = get_tmp_file();
+
+ if (tmpfile &&
+ copy_file(procmsg_get_message_file(info), tmpfile, TRUE) == 0) {
+ tmpcmd = g_strconcat
+ (cmd, " ", tmpfile, NULL);
+ g_free(cmd);
+ cmd = tmpcmd;
+ }
+ if (tmpfile)
+ g_free(tmpfile);
+ }
+ async = TRUE;
+ }
+ if (cmd == NULL)
+ return;
+ debug_print("%s\n",cmd);
+ /* only run async if we have a list, or we could end up
+ * forking lots of perl processes and bury the machine */
+ execute_command_line(cmd, async);
+ g_free(cmd);
+
+}
+
void spamassassin_save_config(void)
{
PrefFile *pfile;
prefs_read_config(param, "SpamAssassin", rcpath, NULL);
g_free(rcpath);
spamassassin_gtk_init();
-
+
+ procmsg_register_spam_learner(spamassassin_learn);
+ procmsg_spam_set_folder(config.save_folder);
+
debug_print("Spamassassin plugin loaded\n");
+ if (config.transport == SPAMASSASSIN_DISABLED) {
+ log_error("Spamassassin plugin is loaded but disabled by its preferences.\n");
+ }
+
return 0;
}
g_free(config.hostname);
g_free(config.save_folder);
spamassassin_gtk_done();
-
+ procmsg_unregister_spam_learner(spamassassin_learn);
+ procmsg_spam_set_folder(NULL);
debug_print("Spamassassin plugin unloaded\n");
}
/* timeout */
config->timeout = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(page->timeout));
+ procmsg_spam_set_folder(config->save_folder);
spamassassin_save_config();
}
return tmp_msginfo;
}
+
+static GSList *spam_learners = NULL;
+
+void procmsg_register_spam_learner (void (*learn_func)(MsgInfo *info, GSList *list, gboolean spam))
+{
+ if (!g_slist_find(spam_learners, learn_func))
+ spam_learners = g_slist_append(spam_learners, learn_func);
+}
+
+void procmsg_unregister_spam_learner (void (*learn_func)(MsgInfo *info, GSList *list, gboolean spam))
+{
+ spam_learners = g_slist_remove(spam_learners, learn_func);
+}
+
+gboolean procmsg_spam_can_learn(void)
+{
+ return g_slist_length(spam_learners) > 0;
+}
+
+void procmsg_spam_learner_learn (MsgInfo *info, GSList *list, gboolean spam)
+{
+ GSList *cur = spam_learners;
+ for (; cur; cur = cur->next) {
+ void ((*func)(MsgInfo *info, GSList *list, gboolean spam)) = cur->data;
+ func(info, list, spam);
+ }
+}
+
+static gchar *spam_folder_item = NULL;
+void procmsg_spam_set_folder (const char *item_identifier)
+{
+ if (spam_folder_item)
+ g_free(spam_folder_item);
+ if (item_identifier)
+ spam_folder_item = g_strdup(item_identifier);
+ else
+ spam_folder_item = NULL;
+}
+
+FolderItem *procmsg_spam_get_folder (void)
+{
+ FolderItem *item = spam_folder_item ? folder_find_item_from_identifier(spam_folder_item) : NULL;
+ return item ? item : folder_get_default_trash();
+}
#define MSG_IGNORE_THREAD (1U << 10) /* ignore threads */
#define MSG_LOCKED (1U << 11) /* msg is locked */
#define MSG_RETRCPT_SENT (1U << 12) /* new one */
+#define MSG_SPAM (1U << 13) /* new one */
/* RESERVED */
#define MSG_RESERVED_CLAWS (1U << 30) /* for sylpheed-claws */
#define MSG_IS_IGNORE_THREAD(msg) (((msg).perm_flags & MSG_IGNORE_THREAD) != 0)
#define MSG_IS_RETRCPT_PENDING(msg) (((msg).perm_flags & MSG_RETRCPT_PENDING) != 0)
#define MSG_IS_RETRCPT_SENT(msg) (((msg).perm_flags & MSG_RETRCPT_SENT) != 0)
+#define MSG_IS_SPAM(msg) (((msg).perm_flags & MSG_SPAM) != 0)
#define MSGINFO_UPDATE_HOOKLIST "msginfo_update"
#define MAIL_FILTERING_HOOKLIST "mail_filtering_hooklist"
(MsgInfo *src_msginfo,
MimeInfo *mimeinfo);
+void procmsg_register_spam_learner (void (*learn_func)(MsgInfo *info, GSList *list, gboolean spam));
+void procmsg_unregister_spam_learner (void (*learn_func)(MsgInfo *info, GSList *list, gboolean spam));
+gboolean procmsg_spam_can_learn (void);
+void procmsg_spam_set_folder (const char *item_identifier);
+FolderItem *procmsg_spam_get_folder (void);
+void procmsg_spam_learner_learn (MsgInfo *msginfo, GSList *msglist, gboolean spam);
+
#endif /* __PROCMSG_H__ */
#include "pixmaps/drafts_close_mark.xpm"
#include "pixmaps/drafts_open_mark.xpm"
#include "pixmaps/dir_noselect.xpm"
+#include "pixmaps/spam.xpm"
#include "pixmaps/empty.xpm"
typedef struct _StockPixmapData StockPixmapData;
{trash_btn_xpm , NULL, NULL, "trash_btn", NULL},
{sylpheed_logo_xpm , NULL, NULL, "sylpheed_logo", NULL},
{dir_noselect_xpm , NULL, NULL, "dir_noselect" , NULL},
+ {spam_xpm , NULL, NULL, "spam" , NULL},
{empty_xpm , NULL, NULL, "empty" , NULL}
};
STOCK_PIXMAP_TRASH,
STOCK_PIXMAP_SYLPHEED_LOGO,
STOCK_PIXMAP_DIR_NOSELECT,
+ STOCK_PIXMAP_SPAM,
STOCK_PIXMAP_EMPTY, /* last entry */
N_STOCK_PIXMAPS
} StockPixmap;
static GdkBitmap *ignorethreadxpmmask;
static GdkPixmap *lockedxpm;
static GdkBitmap *lockedxpmmask;
+static GdkPixmap *spamxpm;
+static GdkBitmap *spamxpmmask;
static GdkPixmap *clipxpm;
static GdkBitmap *clipxpmmask;
{N_("/_Mark/Mark all read"), NULL, summary_mark_all_read, 0, NULL},
{N_("/_Mark/Ignore thread"), NULL, summary_ignore_thread, 0, NULL},
{N_("/_Mark/Unignore thread"), NULL, summary_unignore_thread, 0, NULL},
+ {N_("/_Mark/---"), NULL, NULL, 0, "<Separator>"},
+ {N_("/_Mark/Mark as _spam"), NULL, summary_mark_as_spam, 1, NULL},
+ {N_("/_Mark/Mark as _ham"), NULL, summary_mark_as_spam, 0, NULL},
+ {N_("/_Mark/---"), NULL, NULL, 0, "<Separator>"},
{N_("/_Mark/Lock"), NULL, summary_msgs_lock, 0, NULL},
{N_("/_Mark/Unlock"), NULL, summary_msgs_unlock, 0, NULL},
{N_("/Color la_bel"), NULL, NULL, 0, NULL},
&gpgsignedxpm, &gpgsignedxpmmask);
stock_pixmap_gdk(summaryview->ctree, STOCK_PIXMAP_CLIP_GPG_SIGNED,
&clipgpgsignedxpm, &clipgpgsignedxpmmask);
+ stock_pixmap_gdk(summaryview->ctree, STOCK_PIXMAP_SPAM,
+ &spamxpm, &spamxpmmask);
summary_set_fonts(summaryview);
{"/Mark/Ignore thread" , M_TARGET_EXIST},
{"/Mark/Lock" , M_TARGET_EXIST},
{"/Mark/Unlock" , M_TARGET_EXIST},
+ {"/Mark/Mark as spam" , M_TARGET_EXIST|M_CAN_LEARN_SPAM},
+ {"/Mark/Mark as ham" , M_TARGET_EXIST|M_CAN_LEARN_SPAM},
{"/Color label" , M_TARGET_EXIST},
{"/Add sender to address book" , M_SINGLE_TARGET_EXIST},
GTK_EVENTS_FLUSH();
msginfo = gtk_ctree_node_get_row_data(ctree, row);
-
+
if (new_window) {
MessageView *msgview;
}
gtk_ctree_node_set_foreground
(ctree, row, &summaryview->color_dim);
+ } else if (MSG_IS_SPAM(flags)) {
+ gtk_ctree_node_set_pixmap(ctree, row, col_pos[S_COL_MARK],
+ spamxpm, spamxpmmask);
} else if (MSG_IS_MARKED(flags)) {
gtk_ctree_node_set_pixmap(ctree, row, col_pos[S_COL_MARK],
markxpm, markxpmmask);
summary_status_show(summaryview);
}
+void summary_mark_as_spam(SummaryView *summaryview, guint action, GtkWidget *widget)
+{
+ GtkCTree *ctree = GTK_CTREE(summaryview->ctree);
+ GList *cur;
+ gboolean is_spam = action;
+ GSList *msgs = NULL;
+ gboolean immediate_exec = prefs_common.immediate_exec;
+
+ prefs_common.immediate_exec = FALSE;
+
+ START_LONG_OPERATION(summaryview);
+
+ for (cur = GTK_CLIST(ctree)->selection; cur != NULL && cur->data != NULL; cur = cur->next) {
+ GtkCTreeNode *row = GTK_CTREE_NODE(cur->data);
+ MsgInfo *msginfo = gtk_ctree_node_get_row_data(ctree, row);
+ if (is_spam) {
+ summary_msginfo_change_flags(msginfo, MSG_SPAM, 0, MSG_NEW|MSG_UNREAD, 0);
+ summary_move_row_to(summaryview, row, procmsg_spam_get_folder());
+ } else {
+ summary_msginfo_unset_flags(msginfo, MSG_SPAM, 0);
+ }
+ msgs = g_slist_prepend(msgs, msginfo);
+ }
+
+ procmsg_spam_learner_learn(NULL, msgs, is_spam);
+ g_slist_free(msgs);
+
+ prefs_common.immediate_exec = immediate_exec;
+
+ END_LONG_OPERATION(summaryview);
+
+ if (prefs_common.immediate_exec) {
+ summary_execute(summaryview);
+ }
+
+ summary_status_show(summaryview);
+}
+
+
static void summary_mark_row_as_unread(SummaryView *summaryview,
GtkCTreeNode *row)
{
!MSG_IS_COPY(msginfo->flags)) {
if (MSG_IS_MARKED(msginfo->flags)) {
summary_unmark_row(summaryview, row);
+ } else if (MSG_IS_SPAM(msginfo->flags)) {
+ summary_msginfo_unset_flags(msginfo, MSG_SPAM, 0);
+ procmsg_spam_learner_learn(msginfo, NULL, FALSE);
} else {
summary_mark_row(summaryview, row);
}
stock_pixmap_gdk(ctree, STOCK_PIXMAP_KEY, &keyxpm, &keyxpmmask);
stock_pixmap_gdk(ctree, STOCK_PIXMAP_GPG_SIGNED, &gpgsignedxpm, &gpgsignedxpmmask);
stock_pixmap_gdk(ctree, STOCK_PIXMAP_CLIP_GPG_SIGNED, &clipgpgsignedxpm, &clipgpgsignedxpmmask);
+ stock_pixmap_gdk(ctree, STOCK_PIXMAP_SPAM, &spamxpm, &spamxpmmask);
pixmap = stock_pixmap_widget(summaryview->hbox, STOCK_PIXMAP_DIR_OPEN);
gtk_box_pack_start(GTK_BOX(summaryview->hbox), pixmap, FALSE, FALSE, 4);
void summary_msgs_lock (SummaryView *summaryview);
void summary_msgs_unlock (SummaryView *summaryview);
void summary_mark_all_read (SummaryView *summaryview);
+void summary_mark_as_spam (SummaryView *summaryview,
+ guint action,
+ GtkWidget *widget);
void summary_add_address (SummaryView *summaryview);
void summary_select_all (SummaryView *summaryview);
void summary_unselect_all (SummaryView *summaryview);