From: Hoà Viêt Dinh Date: Sun, 6 May 2001 04:27:28 +0000 (+0000) Subject: scoring / expression matcher X-Git-Tag: VERSION_0_5_0_TEST~27 X-Git-Url: http://git.claws-mail.org/?p=claws.git;a=commitdiff_plain;h=3c4c27b16a411b04e083fef1f8ff5ca6977749a9 scoring / expression matcher --- diff --git a/ChangeLog.claws b/ChangeLog.claws index 84eac3625..4e90f0a79 100644 --- a/ChangeLog.claws +++ b/ChangeLog.claws @@ -1,3 +1,51 @@ +2001-05-06 [hoa] + + * src/Makefile.am + added scoring.c scoring.h + prefs_folder_item.c prefs_folder_item.h + matcher.c matcher.h + * src/defs.h + added FOLDERITEM_RC SCORING_RC + * src/folder.[ch] + added prefs field in FolderItem : + properties for each folder (PrefsFolderItem), + sorting type and mode were added as the first properties. + * src/main.c + read config for scoring + * added src/matcher.[ch] + gives functions to parse configuration file to do + matching on mails with a list of conditions. + * src/mainwindow.c + add option to sort by score + * src/prefs_common.[ch] + added preference option to show score and for + the score column size + * added src/prefs_folder_item.[ch] + properties for each folder (PrefsFolderItem), + sorting type and mode were added as the first properties. + * src/procheader.[ch] + added procheader_parse_header() + added procheader_header_free() + added procheader_headername_equal() + and made modification to use them. + * src/procmsg.[ch] + added score field to MsgInfo structure + * added src/scoring.[ch] + gives functions to parse configuration file to do + scoring on mails with a list of conditions. + * src/summaryview.[ch] + save the sorting type and mode for the folder + added column to display the score of the mail + added function summary_score_clicked() + sorting type and mode are restored to the folder + when it is reopen + * src/textview.c + use procheader_parse_header(), procheader_headername_equal() + removed memory leak in text_scan_header when freeing headers + + Scoring functions are implemented, an interface is needed + to configure that. + 2001-05-04 [alfons] * src/gtkstext.c: diff --git a/src/Makefile.am b/src/Makefile.am index 0ffa490e9..add50ca4e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -78,7 +78,10 @@ sylpheed_SOURCES = \ customheader.c customheader.h \ prefs_headers.c prefs_headers.h \ headers_display.c headers_display.h \ - prefs_display_headers.c prefs_display_headers.h + prefs_display_headers.c prefs_display_headers.h \ + scoring.c scoring.h \ + prefs_folder_item.c prefs_folder_item.h \ + matcher.c matcher.h EXTRA_DIST = \ pixmaps/clip.xpm \ diff --git a/src/defs.h b/src/defs.h index 5c277a24e..f68eef039 100644 --- a/src/defs.h +++ b/src/defs.h @@ -48,6 +48,8 @@ #define FILTER_RC "filterrc" #define HEADERS_RC "headersrc" #define HEADERS_DISPLAY_RC "headersdisplayrc" +#define FOLDERITEM_RC "folderitemrc" +#define SCORING_RC "scoringrc" #define MENU_RC "menurc" #define ADDRESS_BOOK "addressbook.xml" #define MANUAL_HTML_INDEX "sylpheed.html" diff --git a/src/folder.c b/src/folder.c index 8461ab705..0e5e5881b 100644 --- a/src/folder.c +++ b/src/folder.c @@ -143,6 +143,8 @@ FolderItem *folder_item_new(const gchar *name, const gchar *path) item->folder = NULL; item->data = NULL; + item->prefs = prefs_folder_item_new(); + return item; } @@ -822,6 +824,9 @@ static gboolean folder_build_tree(GNode *node, gpointer data) case F_TRASH: folder->trash = item; break; default: } + + prefs_folder_item_read_config(item); + node->data = item; xml_free_node(xmlnode); diff --git a/src/folder.h b/src/folder.h index dcecf8738..2951b481e 100644 --- a/src/folder.h +++ b/src/folder.h @@ -36,6 +36,7 @@ typedef struct _FolderItem FolderItem; #include "prefs_account.h" #include "session.h" #include "procmsg.h" +#include "prefs_folder_item.h" #define FOLDER(obj) ((Folder *)obj) #define FOLDER_TYPE(obj) (FOLDER(obj)->type) @@ -205,6 +206,8 @@ struct _FolderItem Folder *folder; gpointer data; + + PrefsFolderItem * prefs; }; Folder *folder_new (FolderType type, diff --git a/src/main.c b/src/main.c index 622528f98..549da44d7 100644 --- a/src/main.c +++ b/src/main.c @@ -204,6 +204,7 @@ int main(int argc, char *argv[]) prefs_filter_write_config(); prefs_display_headers_read_config(); prefs_display_headers_write_config(); + prefs_scoring_read_config(); #if USE_GPGME if (gpgme_check_engine()) { /* Also does some gpgme init */ diff --git a/src/mainwindow.c b/src/mainwindow.c index f9eb9e7cf..eacd7d5b5 100644 --- a/src/mainwindow.c +++ b/src/mainwindow.c @@ -501,6 +501,7 @@ static GtkItemFactoryEntry mainwin_entries[] = {N_("/_Summary/_Sort/Sort by _date"), NULL, sort_summary_cb, SORT_BY_DATE, NULL}, {N_("/_Summary/_Sort/Sort by _from"), NULL, sort_summary_cb, SORT_BY_FROM, NULL}, {N_("/_Summary/_Sort/Sort by _subject"),NULL, sort_summary_cb, SORT_BY_SUBJECT, NULL}, + {N_("/_Summary/_Sort/Sort by sco_re"), NULL, sort_summary_cb, SORT_BY_SCORE, NULL}, {N_("/_Summary/_Sort/---"), NULL, NULL, 0, ""}, {N_("/_Summary/_Sort/_Attract by subject"), NULL, attract_by_subject_cb, 0, NULL}, diff --git a/src/matcher.c b/src/matcher.c new file mode 100644 index 000000000..10b3603ad --- /dev/null +++ b/src/matcher.c @@ -0,0 +1,910 @@ +#include +#include +#include +#include +#include "utils.h" +#include "defs.h" +#include "procheader.h" +#include "matcher.h" + +struct _MatchParser { + gint id; + gchar * str; +}; + +typedef struct _MatchParser MatchParser; + +MatchParser matchparser_tab[] = { + /* msginfo */ + {SCORING_ALL, "all"}, + {SCORING_SUBJECT, "subject"}, + {SCORING_NOT_SUBJECT, "~subject"}, + {SCORING_FROM, "from"}, + {SCORING_NOT_FROM, "~from"}, + {SCORING_TO, "to"}, + {SCORING_NOT_TO, "~to"}, + {SCORING_CC, "cc"}, + {SCORING_NOT_CC, "~cc"}, + {SCORING_TO_OR_CC, "to_or_cc"}, + {SCORING_NOT_TO_AND_NOT_CC, "~to_or_cc"}, + {SCORING_AGE_SUP, "age_sup"}, + {SCORING_AGE_INF, "age_inf"}, + {SCORING_NEWSGROUPS, "newsgroups"}, + {SCORING_NOT_NEWSGROUPS, "~newsgroups"}, + + /* content have to be read */ + {SCORING_HEADER, "header"}, + {SCORING_NOT_HEADER, "~header"}, + {SCORING_MESSAGEHEADERS, "messageheaders"}, + {SCORING_NOT_MESSAGEHEADERS, "~messageheaders"}, + {SCORING_MESSAGE, "message"}, + {SCORING_NOT_MESSAGE, "~message"}, + {SCORING_BODY, "body"}, + {SCORING_NOT_BODY, "~body"}, + + /* match type */ + {SCORING_MATCHCASE, "matchcase"}, + {SCORING_MATCH, "match"}, + {SCORING_REGEXPCASE, "regexpcase"}, + {SCORING_REGEXP, "regexp"}, + + /* actions */ + {SCORING_SCORE, "score"}, +}; + +/* + syntax for matcher + + header "x-mailing" match "toto" + subject match "regexp" & to regexp "regexp" + subject match "regexp" | to regexpcase "regexp" | age_sup 5 + */ + +static gboolean matcher_is_blank(gchar ch); + +/* ******************* parser *********************** */ + +static gboolean matcher_is_blank(gchar ch) +{ + return (ch == ' ') || (ch == '\t'); +} + +/* parse for one condition */ + +MatcherProp * matcherprop_parse(gchar ** str) +{ + MatcherProp * prop; + gchar * tmp; + gint key; + gint age; + gchar * expr; + gint match; + gchar * header = NULL; + + tmp = * str; + key = matcher_parse_keyword(&tmp); + if (tmp == NULL) { + * str = NULL; + return NULL; + } + + switch (key) { + case SCORING_AGE_INF: + case SCORING_AGE_SUP: + age = matcher_parse_number(&tmp); + if (tmp == NULL) { + * str = NULL; + return NULL; + } + *str = tmp; + + prop = matcherprop_new(key, NULL, 0, NULL, age); + + return prop; + + case SCORING_ALL: + prop = matcherprop_new(key, NULL, 0, NULL, 0); + *str = tmp; + + return prop; + + case SCORING_SUBJECT: + case SCORING_NOT_SUBJECT: + case SCORING_FROM: + case SCORING_NOT_FROM: + case SCORING_TO: + case SCORING_NOT_TO: + case SCORING_CC: + case SCORING_NOT_CC: + case SCORING_TO_OR_CC: + case SCORING_NOT_TO_AND_NOT_CC: + case SCORING_NEWSGROUPS: + case SCORING_NOT_NEWSGROUPS: + case SCORING_MESSAGE: + case SCORING_NOT_MESSAGE: + case SCORING_MESSAGEHEADERS: + case SCORING_NOT_MESSAGEHEADERS: + case SCORING_BODY: + case SCORING_NOT_BODY: + case SCORING_HEADER: + case SCORING_NOT_HEADER: + if ((key == SCORING_HEADER) || (key == SCORING_NOT_HEADER)) { + header = matcher_parse_str(&tmp); + if (tmp == NULL) { + * str = NULL; + return NULL; + } + } + + match = matcher_parse_keyword(&tmp); + if (tmp == NULL) { + if (header) + g_free(header); + * str = NULL; + return NULL; + } + + switch(match) { + case SCORING_REGEXP: + case SCORING_REGEXPCASE: + expr = matcher_parse_regexp(&tmp); + if (tmp == NULL) { + if (header) + g_free(header); + * str = NULL; + return NULL; + } + *str = tmp; + prop = matcherprop_new(key, header, match, expr, 0); + g_free(expr); + + return prop; + case SCORING_MATCH: + case SCORING_MATCHCASE: + expr = matcher_parse_str(&tmp); + if (tmp == NULL) { + if (header) + g_free(header); + * str = NULL; + return NULL; + } + *str = tmp; + prop = matcherprop_new(key, header, match, expr, 0); + g_free(expr); + + return prop; + default: + if (header) + g_free(header); + * str = NULL; + return NULL; + } + default: + * str = NULL; + return NULL; + } +} + +gint matcher_parse_keyword(gchar ** str) +{ + gchar * p; + gchar * dup; + gchar * start; + gint i; + gint match; + + dup = alloca(strlen(* str) + 1); + p = dup; + strcpy(dup, * str); + + while (matcher_is_blank(*p)) + p++; + + start = p; + + match = -1; + for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ; + i++) { + if (strncasecmp(matchparser_tab[i].str, p, + strlen(matchparser_tab[i].str)) == 0) { + p += strlen(matchparser_tab[i].str); + match = i; + break; + } + } + + if (match == -1) { + * str = NULL; + return 0; + } + + *p = '\0'; + + *str += p - dup + 1; + return matchparser_tab[match].id; +} + +gint matcher_parse_number(gchar ** str) +{ + gchar * p; + gchar * dup; + gchar * start; + + dup = alloca(strlen(* str) + 1); + p = dup; + strcpy(dup, * str); + + while (matcher_is_blank(*p)) + p++; + + start = p; + + if (!isdigit(*p) && *p != '-' && *p != '+') { + *str = NULL; + return 0; + } + if (*p == '-' || *p == '+') + p++; + while (isdigit(*p)) + p++; + + *p = '\0'; + + *str += p - dup + 1; + return atoi(start); +} + +gboolean matcher_parse_boolean_op(gchar ** str) +{ + gchar * p; + + p = * str; + + while (matcher_is_blank(*p)) + p++; + + if (*p == '|') { + *str += p - * str + 1; + return FALSE; + } + else if (*p == '&') { + *str += p - * str + 1; + return TRUE; + } + else { + *str = NULL; + return FALSE; + } +} + +gchar * matcher_parse_regexp(gchar ** str) +{ + gchar * p; + gchar * dup; + gchar * start; + + dup = alloca(strlen(* str) + 1); + p = dup; + strcpy(dup, * str); + + while (matcher_is_blank(*p)) + p++; + + if (*p != '/') { + * str = NULL; + return NULL; + } + + p ++; + start = p; + while (*p != '/') { + if (*p == '\\') + p++; + p++; + } + *p = '\0'; + + *str += p - dup + 2; + return g_strdup(start); +} + +gchar * matcher_parse_str(gchar ** str) +{ + gchar * p; + gchar * dup; + gchar * start; + gchar * dest; + + dup = alloca(strlen(* str) + 1); + p = dup; + strcpy(dup, * str); + + while (matcher_is_blank(*p)) + p++; + + if (*p != '"') { + * str = NULL; + return NULL; + } + + p ++; + start = p; + dest = p; + while (*p != '"') { + if (*p == '\\') { + p++; + *dest = *p; + } + else + *dest = *p; + dest++; + p++; + } + *dest = '\0'; + + *str += dest - dup + 2; + return g_strdup(start); +} + +/* **************** data structure allocation **************** */ + + +MatcherProp * matcherprop_new(gint criteria, gchar * header, + gint matchtype, gchar * expr, + int age) +{ + MatcherProp * prop; + + prop = g_new0(MatcherProp, 1); + prop->criteria = criteria; + if (header != NULL) + prop->header = g_strdup(header); + else + prop->header = NULL; + if (expr != NULL) + prop->expr = g_strdup(expr); + else + prop->expr = NULL; + prop->matchtype = matchtype; + prop->preg = NULL; + prop->age = age; + prop->error = 0; + + return prop; +} + +void matcherprop_free(MatcherProp * prop) +{ + g_free(prop->expr); + if (prop->preg != NULL) { + regfree(prop->preg); + g_free(prop->preg); + } + g_free(prop); +} + + +/* ************** match ******************************/ + + +/* match the given string */ + +static gboolean matcherprop_string_match(MatcherProp * prop, gchar * str) +{ + gchar * str1; + + if (str == NULL) + str = ""; + + switch(prop->matchtype) { + case SCORING_REGEXP: + case SCORING_REGEXPCASE: + if (!prop->preg && (prop->error == 0)) { + prop->preg = g_new0(regex_t, 1); + if (regcomp(prop->preg, prop->expr, + REG_NOSUB | REG_EXTENDED + | ((prop->matchtype == SCORING_REGEXPCASE) + ? REG_ICASE : 0)) != 0) { + prop->error = 1; + g_free(prop->preg); + } + } + if (prop->preg == NULL) + return 0; + + if (regexec(prop->preg, str, 0, NULL, 0) == 0) + return 1; + else + return 0; + + case SCORING_MATCH: + return (strstr(str, prop->expr) != NULL); + + case SCORING_MATCHCASE: + g_strup(prop->expr); + str1 = alloca(strlen(str) + 1); + strcpy(str1, str); + g_strup(str1); + return (strstr(str1, prop->expr) != NULL); + + default: + return 0; + } +} + +/* match a message and his headers, hlist can be NULL if you don't + want to use headers */ + +gboolean matcherprop_match(MatcherProp * prop, MsgInfo * info) +{ + time_t t; + + switch(prop->criteria) { + case SCORING_ALL: + return 1; + case SCORING_SUBJECT: + return matcherprop_string_match(prop, info->subject); + case SCORING_NOT_SUBJECT: + return !matcherprop_string_match(prop, info->subject); + case SCORING_FROM: + return matcherprop_string_match(prop, info->from); + case SCORING_NOT_FROM: + return !matcherprop_string_match(prop, info->from); + case SCORING_TO: + return matcherprop_string_match(prop, info->to); + case SCORING_NOT_TO: + return !matcherprop_string_match(prop, info->to); + case SCORING_CC: + return matcherprop_string_match(prop, info->cc); + case SCORING_NOT_CC: + return !matcherprop_string_match(prop, info->cc); + case SCORING_TO_OR_CC: + return matcherprop_string_match(prop, info->to) + || matcherprop_string_match(prop, info->cc); + case SCORING_NOT_TO_AND_NOT_CC: + return !(matcherprop_string_match(prop, info->to) + || matcherprop_string_match(prop, info->cc)); + case SCORING_AGE_SUP: + t = time(NULL); + return (t - info->date_t) > prop->age; + case SCORING_AGE_INF: + t = time(NULL); + return (t - info->date_t) < prop->age; + case SCORING_NEWSGROUPS: + return matcherprop_string_match(prop, info->newsgroups); + case SCORING_NOT_NEWSGROUPS: + return !matcherprop_string_match(prop, info->newsgroups); + case SCORING_HEADER: + default: + return 0; + } +} + +/* ********************* MatcherList *************************** */ + + +/* parse for a list of conditions */ + +MatcherList * matcherlist_parse(gchar ** str) +{ + gchar * tmp; + MatcherProp * matcher; + GSList * matchers_list = NULL; + gboolean bool_and = TRUE; + gchar * save; + MatcherList * cond; + gboolean main_bool_and; + + tmp = * str; + + matcher = matcherprop_parse(&tmp); + if (tmp == NULL) { + * str = NULL; + return NULL; + } + matchers_list = g_slist_append(matchers_list, matcher); + while (matcher) { + save = tmp; + bool_and = matcher_parse_boolean_op(&tmp); + if (tmp == NULL) { + tmp = save; + matcher = NULL; + } + else { + main_bool_and = bool_and; + matcher = matcherprop_parse(&tmp); + if (tmp != NULL) { + matchers_list = + g_slist_append(matchers_list, matcher); + } + else { + * str = NULL; + return NULL; + } + } + } + + cond = matcherlist_new(matchers_list, main_bool_and); + + * str = tmp; + + return cond; +} + +MatcherList * matcherlist_new(GSList * matchers, gboolean bool_and) +{ + MatcherList * cond; + + cond = g_new0(MatcherList, 1); + + cond->matchers = matchers; + cond->bool_and = bool_and; + + return cond; +} + +void matcherlist_free(MatcherList * cond) +{ + GSList * l; + for(l = cond->matchers ; l != NULL ; l = g_slist_next(l)) { + matcherprop_free((MatcherProp *) l->data); + } + g_free(cond); +} + +/* + skip the headers + */ + +static void matcherprop_skip_headers(FILE *fp) +{ + gchar buf[BUFFSIZE]; + + while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) { + } +} + +/* + matcherprop_match_one_header + returns TRUE if buf matchs the MatchersProp criteria + */ + +static gboolean matcherprop_match_one_header(MatcherProp * matcher, + gchar * buf) +{ + gboolean result; + Header *header; + + switch(matcher->criteria) { + case SCORING_HEADER: + case SCORING_NOT_HEADER: + header = procheader_parse_header(buf); + if (procheader_headername_equal(header->name, + matcher->header)) { + if (matcher->criteria == SCORING_HEADER) + result = matcherprop_string_match(matcher, header->body); + else + result = !matcherprop_string_match(matcher, header->body); + procheader_header_free(header); + return result; + } + break; + case SCORING_MESSAGEHEADERS: + case SCORING_MESSAGE: + return matcherprop_string_match(matcher, buf); + case SCORING_NOT_MESSAGE: + case SCORING_NOT_MESSAGEHEADERS: + return !matcherprop_string_match(matcher, buf); + } + return FALSE; +} + +/* + matcherprop_criteria_header + returns TRUE if the headers must be matched + */ + +static gboolean matcherprop_criteria_headers(MatcherProp * matcher) +{ + switch(matcher->criteria) { + case SCORING_HEADER: + case SCORING_NOT_HEADER: + case SCORING_MESSAGEHEADERS: + case SCORING_NOT_MESSAGEHEADERS: + case SCORING_MESSAGE: + case SCORING_NOT_MESSAGE: + return TRUE; + default: + return FALSE; + } +} + +/* + matcherlist_match_one_header + returns TRUE if buf matchs the MatchersList criteria + */ + +static gboolean matcherlist_match_one_header(MatcherList * matchers, + gchar * buf, gboolean result) +{ + GSList * l; + + for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) { + MatcherProp * matcher = (MatcherProp *) l->data; + gboolean matched = FALSE; + gboolean partial_result; + + if (matcherprop_criteria_headers(matcher)) { + if (matcherprop_match_one_header(matcher, buf)) { + if (!matchers->bool_and) + return TRUE; + } + else { + if (matchers->bool_and) + return FALSE; + } + } + } + + return result; +} + +/* + matcherlist_match_headers + returns TRUE if one of the headers matchs the MatcherList criteria + */ + +static gboolean matcherlist_match_headers(MatcherList * matchers, FILE * fp, + gboolean result) +{ + gchar buf[BUFFSIZE]; + + while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) { + if (matcherlist_match_one_header(matchers, buf, result)) { + if (!matchers->bool_and) + return TRUE; + } + else { + if (matchers->bool_and) + return FALSE; + } + } + return result; +} + +/* + matcherprop_criteria_body + returns TRUE if the body must be matched + */ + +static gboolean matcherprop_criteria_body(MatcherProp * matcher) +{ + switch(matcher->criteria) { + case SCORING_BODY: + case SCORING_NOT_BODY: + case SCORING_MESSAGE: + case SCORING_NOT_MESSAGE: + return TRUE; + default: + return FALSE; + } +} + +/* + matcherprop_match_line + returns TRUE if the string matchs the MatcherProp criteria + */ + +static gboolean matcherprop_match_line(MatcherProp * matcher, gchar * line) +{ + switch(matcher->criteria) { + case SCORING_BODY: + case SCORING_MESSAGE: + return matcherprop_string_match(matcher, line); + case SCORING_NOT_BODY: + case SCORING_NOT_MESSAGE: + return !matcherprop_string_match(matcher, line); + } +} + +/* + matcherlist_match_line + returns TRUE if the string matchs the MatcherList criteria + */ + +static gboolean matcherlist_match_line(MatcherList * matchers, gchar * line, + gboolean result) +{ + GSList * l; + + for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) { + MatcherProp * matcher = (MatcherProp *) l->data; + + if (matcherprop_criteria_body(matcher)) { + if (matcherprop_match_line(matcher, line)) { + if (!matchers->bool_and) + return TRUE; + } + else { + if (matchers->bool_and) + return FALSE; + } + } + } + return result; +} + +/* + matcherlist_match_body + returns TRUE if one line of the body matchs the MatcherList criteria + */ + +static gboolean matcherlist_match_body(MatcherList * matchers, FILE * fp, + gboolean result) +{ + gchar buf[BUFFSIZE]; + + while (fgets(buf, sizeof(buf), fp) != NULL) { + if (matcherlist_match_line(matchers, buf, result)) { + if (!matchers->bool_and) + return TRUE; + } + else { + if (matchers->bool_and) + return FALSE; + } + } + return result; +} + +gboolean matcherlist_match_file(MatcherList * matchers, MsgInfo * info, + gboolean result) +{ + gboolean read_headers; + gboolean read_body; + GSList * l; + FILE * fp; + gchar * file; + + /* file need to be read ? */ + + read_headers = FALSE; + read_body = FALSE; + for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) { + MatcherProp * matcher = (MatcherProp *) l->data; + + if (matcherprop_criteria_headers(matcher)) + read_headers = TRUE; + if (matcherprop_criteria_body(matcher)) + read_body = TRUE; + } + + if (!read_headers && !read_body) + return result; + + file = procmsg_get_message_file_path(info); + g_return_if_fail(file != NULL); + + if ((fp = fopen(file, "r")) == NULL) { + FILE_OP_ERROR(file, "fopen"); + g_free(file); + return result; + } + + /* read the headers */ + + if (read_headers) { + if (matcherlist_match_headers(matchers, fp, result)) { + if (!matchers->bool_and) + result = TRUE; + } + else { + if (matchers->bool_and) + result = FALSE; + } + } + + /* read the body */ + if (read_body) { + if (matcherlist_match_body(matchers, fp, result)) { + if (!matchers->bool_and) + result = TRUE; + } + else { + if (matchers->bool_and) + result = FALSE; + } + } + + g_free(file); + + fclose(fp); + + return result; +} + +/* test a list of condition */ + +gboolean matcherlist_match(MatcherList * matchers, MsgInfo * info) +{ + GSList * l; + gboolean result; + + if (matchers->bool_and) + result = TRUE; + else + result = FALSE; + + /* test the condition on the file */ + + if (matcherlist_match_file(matchers, info, result)) { + if (!matchers->bool_and) + return TRUE; + } + else { + if (matchers->bool_and) + return FALSE; + } + + /* test the cached elements */ + + for(l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) { + MatcherProp * matcher = (MatcherProp *) l->data; + + if (matcherprop_match(matcher, info)) { + if (!matchers->bool_and) { + result = TRUE; + break; + } + } + else { + if (matchers->bool_and) { + result = FALSE; + break; + } + } + } + + return result; +} + +#ifdef 0 +static void matcherprop_print(MatcherProp * matcher) +{ + int i; + + if (matcher == NULL) { + printf("no matcher\n"); + return; + } + + switch (matcher->matchtype) { + case SCORING_MATCH: + printf("match\n"); + break; + case SCORING_REGEXP: + printf("regexp\n"); + break; + case SCORING_MATCHCASE: + printf("matchcase\n"); + break; + case SCORING_REGEXPCASE: + printf("regexpcase\n"); + break; + } + + for(i = 0 ; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)) ; + i++) { + if (matchparser_tab[i].id == matcher->criteria) + printf("%s\n", matchparser_tab[i].str); + } + + if (matcher->expr) + printf("expr : %s\n", matcher->expr); + + printf("age: %i\n", matcher->age); + + printf("compiled : %s\n", matcher->preg != NULL ? "yes" : "no"); + printf("error: %i\n", matcher->error); +} +#endif diff --git a/src/matcher.h b/src/matcher.h new file mode 100644 index 000000000..ff54bfbba --- /dev/null +++ b/src/matcher.h @@ -0,0 +1,80 @@ +#ifndef MATCHER_H + +#define MATCHER_H + +#include +#include +#include +#include "procmsg.h" + +enum { + SCORING_ALL, + SCORING_SUBJECT, + SCORING_NOT_SUBJECT, + SCORING_FROM, + SCORING_NOT_FROM, + SCORING_TO, + SCORING_NOT_TO, + SCORING_CC, + SCORING_NOT_CC, + SCORING_TO_OR_CC, + SCORING_NOT_TO_AND_NOT_CC, + SCORING_AGE_SUP, + SCORING_AGE_INF, + SCORING_NEWSGROUPS, + SCORING_NOT_NEWSGROUPS, + SCORING_HEADER, + SCORING_NOT_HEADER, + SCORING_MESSAGE, + SCORING_NOT_MESSAGE, + SCORING_MESSAGEHEADERS, + SCORING_NOT_MESSAGEHEADERS, + SCORING_BODY, + SCORING_NOT_BODY, + SCORING_SCORE, + SCORING_MATCH, + SCORING_REGEXP, + SCORING_MATCHCASE, + SCORING_REGEXPCASE +}; + +struct _MatcherProp { + int matchtype; + int criteria; + gchar * header; + gchar * expr; + int age; + regex_t * preg; + int error; +}; + +typedef struct _MatcherProp MatcherProp; + +struct _MatcherList { + GSList * matchers; + gboolean bool_and; +}; + +typedef struct _MatcherList MatcherList; + +MatcherProp * matcherprop_new(gint criteria, gchar * header, + gint matchtype, gchar * expr, + int age); +void matcherprop_free(MatcherProp * prop); +MatcherProp * matcherprop_parse(gchar ** str); + +gboolean matcherprop_match(MatcherProp * prop, MsgInfo * info); + +MatcherList * matcherlist_new(GSList * matchers, gboolean bool_and); +void matcherlist_free(MatcherList * cond); +MatcherList * matcherlist_parse(gchar ** str); + +gboolean matcherlist_match(MatcherList * cond, MsgInfo * info); + +gint matcher_parse_keyword(gchar ** str); +gint matcher_parse_number(gchar ** str); +gboolean matcher_parse_boolean_op(gchar ** str); +gchar * matcher_parse_regexp(gchar ** str); +gchar * matcher_parse_str(gchar ** str); + +#endif diff --git a/src/prefs_common.c b/src/prefs_common.c index ef505e4e8..5f9c597a9 100644 --- a/src/prefs_common.c +++ b/src/prefs_common.c @@ -309,6 +309,8 @@ static PrefParam param[] = { NULL, NULL, NULL}, {"show_number", "TRUE", &prefs_common.show_number, P_BOOL, NULL, NULL, NULL}, + {"show_score", "TRUE", &prefs_common.show_score, P_BOOL, + NULL, NULL, NULL}, {"show_size", "FALSE", &prefs_common.show_size, P_BOOL, NULL, NULL, NULL}, {"show_date", "TRUE", &prefs_common.show_date, P_BOOL, @@ -343,6 +345,8 @@ static PrefParam param[] = { NULL, NULL, NULL}, {"summary_col_number", "40", &prefs_common.summary_col_number, P_INT, NULL, NULL, NULL}, + {"summary_col_score", "40", &prefs_common.summary_col_score, + P_INT, NULL, NULL, NULL}, {"summary_col_size", "48", &prefs_common.summary_col_size, P_INT, NULL, NULL, NULL}, {"summary_col_date", "120", &prefs_common.summary_col_date, P_INT, @@ -2243,6 +2247,7 @@ void prefs_summary_display_item_set(void) SET_ACTIVE(S_COL_UNREAD, show_unread); SET_ACTIVE(S_COL_MIME, show_mime); SET_ACTIVE(S_COL_NUMBER, show_number); + SET_ACTIVE(S_COL_SCORE, show_score); SET_ACTIVE(S_COL_SIZE, show_size); SET_ACTIVE(S_COL_DATE, show_date); SET_ACTIVE(S_COL_FROM, show_from); @@ -2256,6 +2261,7 @@ void prefs_summary_display_item_set(void) GET_ACTIVE(S_COL_UNREAD, show_unread); GET_ACTIVE(S_COL_MIME, show_mime); GET_ACTIVE(S_COL_NUMBER, show_number); + GET_ACTIVE(S_COL_SCORE, show_score); GET_ACTIVE(S_COL_SIZE, show_size); GET_ACTIVE(S_COL_DATE, show_date); GET_ACTIVE(S_COL_FROM, show_from); @@ -2306,6 +2312,7 @@ static void prefs_summary_display_item_dialog_create(gboolean *cancelled) SET_CHECK_BUTTON(S_COL_UNREAD, _("Unread")); SET_CHECK_BUTTON(S_COL_MIME, _("MIME")); SET_CHECK_BUTTON(S_COL_NUMBER, _("Number")); + SET_CHECK_BUTTON(S_COL_SCORE, _("Score")); SET_CHECK_BUTTON(S_COL_SIZE, _("Size")); SET_CHECK_BUTTON(S_COL_DATE, _("Date")); SET_CHECK_BUTTON(S_COL_FROM, _("From")); diff --git a/src/prefs_common.h b/src/prefs_common.h index cfb9d01fc..86595b15f 100644 --- a/src/prefs_common.h +++ b/src/prefs_common.h @@ -82,6 +82,7 @@ struct _PrefsCommon gboolean show_unread; gboolean show_mime; gboolean show_number; + gboolean show_score; gboolean show_size; gboolean show_date; gboolean show_from; @@ -107,6 +108,7 @@ struct _PrefsCommon gint summary_col_date; gint summary_col_from; gint summary_col_subject; + gint summary_col_score; gint mainview_x; gint mainview_y; diff --git a/src/prefs_folder_item.c b/src/prefs_folder_item.c new file mode 100644 index 000000000..c2bbdf086 --- /dev/null +++ b/src/prefs_folder_item.c @@ -0,0 +1,137 @@ +#include "folder.h" +#include "prefs_folder_item.h" +#include "summaryview.h" +#include "prefs.h" +#include "defs.h" + +PrefsFolderItem tmp_prefs; + +static PrefParam param[] = { + {"sort_by_number", "FALSE", &tmp_prefs.sort_by_number, P_BOOL, + NULL, NULL, NULL}, + {"sort_by_size", "FALSE", &tmp_prefs.sort_by_size, P_BOOL, + NULL, NULL, NULL}, + {"sort_by_date", "FALSE", &tmp_prefs.sort_by_date, P_BOOL, + NULL, NULL, NULL}, + {"sort_by_from", "FALSE", &tmp_prefs.sort_by_from, P_BOOL, + NULL, NULL, NULL}, + {"sort_by_subject", "FALSE", &tmp_prefs.sort_by_subject, P_BOOL, + NULL, NULL, NULL}, + {"sort_by_score", "FALSE", &tmp_prefs.sort_by_score, P_BOOL, + NULL, NULL, NULL}, + {"sort_descending", "FALSE", &tmp_prefs.sort_descending, P_BOOL, + NULL, NULL, NULL}, + {NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL} +}; + +void prefs_folder_item_read_config(FolderItem * item) +{ + gchar * path; + + path = folder_item_get_path(item); + prefs_read_config(param, path, FOLDERITEM_RC); + g_free(path); + + * item->prefs = tmp_prefs; +} + +void prefs_folder_item_save_config(FolderItem * item) +{ + gchar * path; + + tmp_prefs = * item->prefs; + + path = folder_item_get_path(item); + prefs_save_config(param, path, FOLDERITEM_RC); + g_free(path); +} + +void prefs_folder_item_set_config(FolderItem * item, + int sort_type, gint sort_mode) +{ + tmp_prefs = * item->prefs; + + tmp_prefs.sort_by_number = FALSE; + tmp_prefs.sort_by_size = FALSE; + tmp_prefs.sort_by_date = FALSE; + tmp_prefs.sort_by_from = FALSE; + tmp_prefs.sort_by_subject = FALSE; + tmp_prefs.sort_by_score = FALSE; + + switch (sort_mode) { + case SORT_BY_NUMBER: + tmp_prefs.sort_by_number = TRUE; + break; + case SORT_BY_SIZE: + tmp_prefs.sort_by_size = TRUE; + break; + case SORT_BY_DATE: + tmp_prefs.sort_by_date = TRUE; + break; + case SORT_BY_FROM: + tmp_prefs.sort_by_from = TRUE; + break; + case SORT_BY_SUBJECT: + tmp_prefs.sort_by_subject = TRUE; + break; + case SORT_BY_SCORE: + tmp_prefs.sort_by_score = TRUE; + break; + } + tmp_prefs.sort_descending = (sort_type == GTK_SORT_DESCENDING); + + * item->prefs = tmp_prefs; +} + +PrefsFolderItem * prefs_folder_item_new(void) +{ + PrefsFolderItem * prefs; + + prefs = g_new0(PrefsFolderItem, 1); + + tmp_prefs.sort_by_number = FALSE; + tmp_prefs.sort_by_size = FALSE; + tmp_prefs.sort_by_date = FALSE; + tmp_prefs.sort_by_from = FALSE; + tmp_prefs.sort_by_subject = FALSE; + tmp_prefs.sort_by_score = FALSE; + tmp_prefs.sort_descending = FALSE; + + * prefs = tmp_prefs; + + return prefs; +} + +void prefs_folder_item_free(PrefsFolderItem * prefs) +{ + g_free(prefs); +} + +gint prefs_folder_item_get_sort_mode(FolderItem * item) +{ + tmp_prefs = * item->prefs; + + if (tmp_prefs.sort_by_number) + return SORT_BY_NUMBER; + if (tmp_prefs.sort_by_size) + return SORT_BY_SIZE; + if (tmp_prefs.sort_by_date) + return SORT_BY_DATE; + if (tmp_prefs.sort_by_from) + return SORT_BY_FROM; + if (tmp_prefs.sort_by_subject) + return SORT_BY_SUBJECT; + if (tmp_prefs.sort_by_score) + return SORT_BY_SCORE; + return SORT_BY_NONE; +} + +gint prefs_folder_item_get_sort_type(FolderItem * item) +{ + tmp_prefs = * item->prefs; + + if (tmp_prefs.sort_descending) + return GTK_SORT_DESCENDING; + else + return GTK_SORT_ASCENDING; +} diff --git a/src/prefs_folder_item.h b/src/prefs_folder_item.h new file mode 100644 index 000000000..84fa4d531 --- /dev/null +++ b/src/prefs_folder_item.h @@ -0,0 +1,32 @@ +#ifndef PREFS_FOLDER_ITEM_H + +#define PREFS_FOLDER_ITEM_H + +#include "folder.h" +#include + +struct _PrefsFolderItem { + gchar * directory; + + gboolean sort_by_number; + gboolean sort_by_size; + gboolean sort_by_date; + gboolean sort_by_from; + gboolean sort_by_subject; + gboolean sort_by_score; + + gboolean sort_descending; + + GSList * scoring; +}; + +typedef struct _PrefsFolderItem PrefsFolderItem; + +void prefs_folder_item_read_config(FolderItem * item); +void prefs_folder_item_save_config(FolderItem * item); +void prefs_folder_item_set_config(FolderItem * item, + int sort_type, gint sort_mode); +PrefsFolderItem * prefs_folder_item_new(void); +void prefs_folder_item_free(PrefsFolderItem * prefs); + +#endif diff --git a/src/procheader.c b/src/procheader.c index 76ba1409d..ba5133ff2 100644 --- a/src/procheader.c +++ b/src/procheader.c @@ -192,6 +192,63 @@ gchar *procheader_get_unfolded_line(gchar *buf, gint len, FILE *fp) return buf; } +/* + tests whether two headers are equal +*/ + +gboolean procheader_headername_equal(char * hdr1, char * hdr2) +{ + int len1; + int len2; + + len1 = strlen(hdr1); + len2 = strlen(hdr2); + if ((hdr1[len1 - 1] == ':') || (hdr1[len1 - 1] == ' ')) + len1--; + if ((hdr2[len2 - 1] == ':') || (hdr2[len2 - 1] == ' ')) + len2--; + if (len1 != len2) + return 0; + return (strncasecmp(hdr1, hdr2, len1) == 0); +} + +void procheader_header_free(Header * header) +{ + g_free(header->name); + g_free(header->body); + g_free(header); +} + +/* + parse headers, for example : + From: dinh@enseirb.fr becomes : + header->name = "From:" + header->body = "dinh@enseirb.fr" + */ + +Header * procheader_parse_header(gchar * buf) +{ + gchar tmp[BUFFSIZE]; + gchar *p = buf; + Header * header; + + if ((*buf == ':') || (*buf == ' ')) + return NULL; + + for (p = buf; *p ; p++) { + if ((*p == ':') || (*p == ' ')) { + header = g_new(Header, 1); + header->name = g_strndup(buf, p - buf + 1); + p++; + while (*p == ' ' || *p == '\t') p++; + conv_unmime_header(tmp, sizeof(tmp), p, NULL); + header->body = g_strdup(tmp); + return header; + } + } + return NULL; +} + GSList *procheader_get_header_list(const gchar *file) { FILE *fp; @@ -206,20 +263,9 @@ GSList *procheader_get_header_list(const gchar *file) } while (procheader_get_unfolded_line(buf, sizeof(buf), fp) != NULL) { - if (*buf == ':') continue; - for (p = buf; *p && *p != ' '; p++) { - if (*p == ':') { - header = g_new(Header, 1); - header->name = g_strndup(buf, p - buf); - p++; - while (*p == ' ' || *p == '\t') p++; - conv_unmime_header(tmp, sizeof(tmp), p, NULL); - header->body = g_strdup(tmp); - - hlist = g_slist_append(hlist, header); - break; - } - } + header = procheader_parse_header(buf); + if (header != NULL) + hlist = g_slist_append(hlist, header); } fclose(fp); @@ -233,9 +279,7 @@ void procheader_header_list_destroy(GSList *hlist) while (hlist != NULL) { header = hlist->data; - g_free(header->name); - g_free(header->body); - g_free(header); + procheader_header_free(header); hlist = g_slist_remove(hlist, header); } } diff --git a/src/procheader.h b/src/procheader.h index 49f17e719..58753272f 100644 --- a/src/procheader.h +++ b/src/procheader.h @@ -66,5 +66,9 @@ time_t procheader_date_parse (gchar *dest, void procheader_date_get_localtime (gchar *dest, gint len, const time_t timer); +Header * procheader_parse_header (gchar * buf); + +gboolean procheader_headername_equal (char * hdr1, char * hdr2); +void procheader_header_free (Header * header); #endif /* __PROCHEADER_H__ */ diff --git a/src/procmsg.c b/src/procmsg.c index ec7015854..000d4c3b2 100644 --- a/src/procmsg.c +++ b/src/procmsg.c @@ -755,6 +755,8 @@ MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo) MEMBDUP(xface); MEMBDUP(dispositionnotificationto); + MEMBCOPY(score); + return newmsginfo; } diff --git a/src/procmsg.h b/src/procmsg.h index ddc716db0..f4e80e8f0 100644 --- a/src/procmsg.h +++ b/src/procmsg.h @@ -124,6 +124,8 @@ struct _MsgInfo gchar *dispositionnotificationto; + int score; + /* used only for encrypted messages */ gchar *plaintext_file; guint decryption_failed : 1; diff --git a/src/scoring.c b/src/scoring.c new file mode 100644 index 000000000..8511e10de --- /dev/null +++ b/src/scoring.c @@ -0,0 +1,184 @@ +#include +#include +#include +#include +#include +#include "intl.h" +#include "utils.h" +#include "defs.h" +#include "procheader.h" +#include "matcher.h" +#include "scoring.h" + +#define PREFSBUFSIZE 1024 + +GSList * prefs_scoring = NULL; + +ScoringProp * scoringprop_parse(gchar ** str) +{ + gchar * tmp; + gchar * save; + gint key; + ScoringProp * scoring; + gint score; + MatcherList * matchers; + + tmp = * str; + + matchers = matcherlist_parse(&tmp); + if (tmp == NULL) { + * str = NULL; + return NULL; + } + + key = matcher_parse_keyword(&tmp); + + if (tmp == NULL) { + matcherlist_free(matchers); + * str = NULL; + return NULL; + } + + if (key != SCORING_SCORE) { + matcherlist_free(matchers); + * str = NULL; + return NULL; + } + + score = matcher_parse_number(&tmp); + + if (tmp == NULL) { + matcherlist_free(matchers); + * str = NULL; + return NULL; + } + + scoring = scoringprop_new(matchers, score); + + * str = tmp; + return scoring; +} + + +ScoringProp * scoringprop_new(MatcherList * matchers, int score) +{ + ScoringProp * scoring; + + scoring = g_new0(ScoringProp, 1); + scoring->matchers = matchers; + scoring->score = score; + + return scoring; +} + +void scoringprop_free(ScoringProp * prop) +{ + matcherlist_free(prop->matchers); + g_free(prop); +} + +gint scoringprop_score_message(ScoringProp * scoring, MsgInfo * info) +{ + if (matcherlist_match(scoring->matchers, info)) + return scoring->score; + else + return 0; +} + +gint score_message(GSList * scoring_list, MsgInfo * info) +{ + gint score = 0; + gint add_score; + GSList * l; + + for(l = scoring_list ; l != NULL ; l = g_slist_next(l)) { + ScoringProp * scoring = (ScoringProp *) l->data; + + add_score = (scoringprop_score_message(scoring, info)); + if (add_score == MAX_SCORE || add_score == MIN_SCORE) { + score = add_score; + break; + } + score += add_score; + } + return score; +} + +#ifdef 0 +static void scoringprop_print(ScoringProp * prop) +{ + GSList * l; + + if (prop == NULL) { + printf("no scoring\n"); + return; + } + + printf("----- scoring ------\n"); + for(l = prop->matchers ; l != NULL ; l = g_slist_next(l)) { + matcherprop_print((MatcherProp *) l->data); + } + printf("cond: %s\n", prop->bool_and ? "and" : "or"); + printf("score: %i\n", prop->score); +} +#endif + +/* + syntax for scoring config + + file ~/.sylpheed/scoringrc + + header "x-mailing" match "toto" score -10 + subject match "regexp" & to regexp "regexp" score 50 + subject match "regexp" | to regexpcase "regexp" | age_sup 5 score 30 + + if score is = MIN_SCORE (-999), no more match is done in the list + if score is = MAX_SCORE (-999), no more match is done in the list + */ + +void prefs_scoring_read_config(void) +{ + gchar *rcpath; + FILE *fp; + gchar buf[PREFSBUFSIZE]; + + debug_print(_("Reading headers configuration...\n")); + + rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, SCORING_RC, NULL); + if ((fp = fopen(rcpath, "r")) == NULL) { + if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen"); + g_free(rcpath); + prefs_scoring = NULL; + return; + } + g_free(rcpath); + + /* remove all scoring */ + while (prefs_scoring != NULL) { + ScoringProp * scoring = (ScoringProp *) prefs_scoring->data; + scoringprop_free(scoring); + prefs_scoring = g_slist_remove(prefs_scoring, scoring); + } + + while (fgets(buf, sizeof(buf), fp) != NULL) { + ScoringProp * scoring; + gchar * tmp; + + g_strchomp(buf); + + if (*buf != '#') { + tmp = buf; + scoring = scoringprop_parse(&tmp); + if (tmp != NULL) { + prefs_scoring = g_slist_append(prefs_scoring, + scoring); + } + else { + // debug + g_warning(_("syntax error : %s\n"), buf); + } + } + } + + fclose(fp); +} diff --git a/src/scoring.h b/src/scoring.h new file mode 100644 index 000000000..2d7122f09 --- /dev/null +++ b/src/scoring.h @@ -0,0 +1,33 @@ +#ifndef SCORING_H + +#define SCORING_H + +#include +#include "matcher.h" +#include "procmsg.h" + +#define MAX_SCORE 999 +#define MIN_SCORE -999 + +struct _ScoringProp { + MatcherList * matchers; + int score; +}; + +typedef struct _ScoringProp ScoringProp; + +extern GSList * prefs_scoring; + + +ScoringProp * scoringprop_new(MatcherList * matchers, int score); +void scoringprop_free(ScoringProp * prop); +gint scoringprop_score_message(ScoringProp * scoring, MsgInfo * info); + +ScoringProp * scoringprop_parse(gchar ** str); + + +gint score_message(GSList * scoring_list, MsgInfo * info); + +void prefs_scoring_read_config(void); + +#endif diff --git a/src/summaryview.c b/src/summaryview.c index 20ffd22f0..13bc81db1 100644 --- a/src/summaryview.c +++ b/src/summaryview.c @@ -72,6 +72,7 @@ #include "filter.h" #include "folder.h" #include "addressbook.h" +#include "scoring.h" #include "pixmaps/dir-open.xpm" #include "pixmaps/mark.xpm" @@ -253,6 +254,8 @@ static void summary_add_sender_to_cb (SummaryView *summaryview, static void summary_num_clicked (GtkWidget *button, SummaryView *summaryview); +static void summary_score_clicked (GtkWidget *button, + SummaryView *summaryview); static void summary_size_clicked (GtkWidget *button, SummaryView *summaryview); static void summary_date_clicked (GtkWidget *button, @@ -290,6 +293,9 @@ static gint summary_cmp_by_from (GtkCList *clist, static gint summary_cmp_by_subject (GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2); +static gint summary_cmp_by_score (GtkCList *clist, + gconstpointer ptr1, + gconstpointer ptr2); GtkTargetEntry summary_drag_types[1] = { @@ -371,7 +377,8 @@ SummaryView *summary_create(void) titles[S_COL_FROM] = "From"; titles[S_COL_SUBJECT] = "Subject"; } - titles[S_COL_SIZE] = _("Size"); + titles[S_COL_SIZE] = _("Size"); + titles[S_COL_SCORE] = _("Score"); ctree = gtk_sctree_new_with_titles(N_SUMMARY_COLS, S_COL_SUBJECT, titles); gtk_scrolled_window_set_hadjustment(GTK_SCROLLED_WINDOW(scrolledwin), @@ -388,6 +395,8 @@ SummaryView *summary_create(void) GTK_JUSTIFY_CENTER); gtk_clist_set_column_justification(GTK_CLIST(ctree), S_COL_NUMBER, GTK_JUSTIFY_RIGHT); + gtk_clist_set_column_justification(GTK_CLIST(ctree), S_COL_SCORE, + GTK_JUSTIFY_RIGHT); gtk_clist_set_column_justification(GTK_CLIST(ctree), S_COL_SIZE, GTK_JUSTIFY_RIGHT); gtk_clist_set_column_width(GTK_CLIST(ctree), S_COL_MARK, @@ -398,6 +407,8 @@ SummaryView *summary_create(void) SUMMARY_COL_MIME_WIDTH); gtk_clist_set_column_width(GTK_CLIST(ctree), S_COL_NUMBER, prefs_common.summary_col_number); + gtk_clist_set_column_width(GTK_CLIST(ctree), S_COL_SCORE, + prefs_common.summary_col_score); gtk_clist_set_column_width(GTK_CLIST(ctree), S_COL_SIZE, prefs_common.summary_col_size); gtk_clist_set_column_width(GTK_CLIST(ctree), S_COL_DATE, @@ -428,6 +439,11 @@ SummaryView *summary_create(void) "clicked", GTK_SIGNAL_FUNC(summary_num_clicked), summaryview); + gtk_signal_connect + (GTK_OBJECT(GTK_CLIST(ctree)->column[S_COL_SCORE].button), + "clicked", + GTK_SIGNAL_FUNC(summary_score_clicked), + summaryview); gtk_signal_connect (GTK_OBJECT(GTK_CLIST(ctree)->column[S_COL_SIZE].button), "clicked", @@ -580,6 +596,9 @@ gboolean summary_show(SummaryView *summaryview, FolderItem *item, gchar *buf; gboolean is_refresh; guint prev_msgnum = 0; + GSList *cur; + gint sort_mode; + gint sort_type; STATUSBAR_POP(summaryview->mainwin); @@ -641,6 +660,12 @@ gboolean summary_show(SummaryView *summaryview, FolderItem *item, mlist = item->folder->get_msg_list(item->folder, item, !update_cache); + for(cur = mlist ; cur != NULL ; cur = g_slist_next(cur)) { + MsgInfo * msginfo = (MsgInfo *) cur->data; + + msginfo->score = score_message(prefs_scoring, msginfo); + } + STATUSBAR_POP(summaryview->mainwin); /* set ctree and hash table from the msginfo list @@ -705,6 +730,20 @@ gboolean summary_show(SummaryView *summaryview, FolderItem *item, main_window_cursor_normal(summaryview->mainwin); + /* sort before */ + sort_mode = prefs_folder_item_get_sort_mode(item); + sort_type = prefs_folder_item_get_sort_type(item); + + if (sort_mode != SORT_BY_NONE) { + summaryview->sort_mode = sort_mode; + if (sort_type == GTK_SORT_DESCENDING) + summaryview->sort_type = GTK_SORT_ASCENDING; + else + summaryview->sort_type = GTK_SORT_DESCENDING; + + summary_sort(summaryview, sort_mode); + } + return TRUE; } @@ -1298,6 +1337,9 @@ void summary_sort(SummaryView *summaryview, SummarySortType type) case SORT_BY_SUBJECT: cmp_func = (GtkCListCompareFunc)summary_cmp_by_subject; break; + case SORT_BY_SCORE: + cmp_func = (GtkCListCompareFunc)summary_cmp_by_score; + break; default: return; } @@ -1324,6 +1366,11 @@ void summary_sort(SummaryView *summaryview, SummarySortType type) gtk_ctree_node_moveto(ctree, summaryview->selected, -1, 0.5, 0); //gtkut_ctree_set_focus_row(ctree, summaryview->selected); + prefs_folder_item_set_config(summaryview->folder_item, + summaryview->sort_type, + summaryview->sort_mode); + prefs_folder_item_save_config(summaryview->folder_item); + debug_print(_("done.\n")); STATUSBAR_POP(summaryview->mainwin); @@ -1495,6 +1542,7 @@ static void summary_set_header(gchar *text[], MsgInfo *msginfo) text[S_COL_MIME] = NULL; text[S_COL_NUMBER] = itos(msginfo->msgnum); text[S_COL_SIZE] = to_human_readable(msginfo->size); + text[S_COL_SCORE] = itos(msginfo->score); if (msginfo->date_t) { procheader_date_get_localtime(date_modified, @@ -2842,6 +2890,9 @@ static void summary_col_resized(GtkCList *clist, gint column, gint width, case S_COL_NUMBER: prefs_common.summary_col_number = width; break; + case S_COL_SCORE: + prefs_common.summary_col_score = width; + break; case S_COL_SIZE: prefs_common.summary_col_size = width; break; @@ -2962,6 +3013,12 @@ static void summary_num_clicked(GtkWidget *button, SummaryView *summaryview) summary_sort(summaryview, SORT_BY_NUMBER); } +static void summary_score_clicked(GtkWidget *button, + SummaryView *summaryview) +{ + summary_sort(summaryview, SORT_BY_SCORE); +} + static void summary_size_clicked(GtkWidget *button, SummaryView *summaryview) { summary_sort(summaryview, SORT_BY_SIZE); @@ -2991,6 +3048,7 @@ void summary_change_display_item(SummaryView *summaryview) gtk_clist_set_column_visibility(clist, S_COL_UNREAD, prefs_common.show_unread); gtk_clist_set_column_visibility(clist, S_COL_MIME, prefs_common.show_mime); gtk_clist_set_column_visibility(clist, S_COL_NUMBER, prefs_common.show_number); + gtk_clist_set_column_visibility(clist, S_COL_SCORE, prefs_common.show_score); gtk_clist_set_column_visibility(clist, S_COL_SIZE, prefs_common.show_size); gtk_clist_set_column_visibility(clist, S_COL_DATE, prefs_common.show_date); gtk_clist_set_column_visibility(clist, S_COL_FROM, prefs_common.show_from); @@ -3122,3 +3180,19 @@ static gint summary_cmp_by_subject(GtkCList *clist, return strcasecmp(msginfo1->subject, msginfo2->subject); } + +static gint summary_cmp_by_score(GtkCList *clist, + gconstpointer ptr1, gconstpointer ptr2) +{ + MsgInfo *msginfo1 = ((GtkCListRow *)ptr1)->data; + MsgInfo *msginfo2 = ((GtkCListRow *)ptr2)->data; + int diff; + + /* if score are equal, sort by date */ + + diff = msginfo1->score - msginfo2->score; + if (diff != 0) + return diff; + else + return summary_cmp_by_date(clist, ptr1, ptr2); +} diff --git a/src/summaryview.h b/src/summaryview.h index 42d504212..19f5008b3 100644 --- a/src/summaryview.h +++ b/src/summaryview.h @@ -43,13 +43,14 @@ typedef enum S_COL_UNREAD = 1, S_COL_MIME = 2, S_COL_NUMBER = 3, - S_COL_SIZE = 4, - S_COL_DATE = 5, - S_COL_FROM = 6, - S_COL_SUBJECT = 7 + S_COL_SCORE = 4, + S_COL_SIZE = 5, + S_COL_DATE = 6, + S_COL_FROM = 7, + S_COL_SUBJECT = 8 } SummaryColumnPos; -#define N_SUMMARY_COLS 8 +#define N_SUMMARY_COLS 9 typedef enum { @@ -58,7 +59,8 @@ typedef enum SORT_BY_SIZE, SORT_BY_DATE, SORT_BY_FROM, - SORT_BY_SUBJECT + SORT_BY_SUBJECT, + SORT_BY_SCORE } SummarySortType; typedef enum diff --git a/src/textview.c b/src/textview.c index 49e6170a1..82906f085 100644 --- a/src/textview.c +++ b/src/textview.c @@ -938,22 +938,6 @@ enum H_ORGANIZATION = 11, }; -static gboolean hdrequal(char * hdr1, char * hdr2) -{ - int len1; - int len2; - - len1 = strlen(hdr1); - len2 = strlen(hdr2); - if (hdr1[len1 - 1] == ':') - len1--; - if (hdr2[len2 - 1] == ':') - len2--; - if (len1 != len2) - return 0; - return (strncasecmp(hdr1, hdr2, len1) == 0); -} - static GPtrArray *textview_scan_header(TextView *textview, FILE *fp) { /* @@ -1011,20 +995,9 @@ static GPtrArray *textview_scan_header(TextView *textview, FILE *fp) gchar * p; Header *header; - if (*buf == ':') continue; - for (p = buf; *p && *p != ' '; p++) { - if (*p == ':') { - header = g_new(Header, 1); - header->name = g_strndup(buf, p - buf + 1); - p++; - /* while (*p == ' ' || *p == '\t') p++; */ - conv_unmime_header(tmp, sizeof(tmp), p, NULL); - header->body = g_strdup(tmp); - - g_ptr_array_add(headers, header); - break; - } - } + header = procheader_parse_header(buf); + if (header != NULL) + g_ptr_array_add(headers, header); } sorted_headers = g_ptr_array_new(); @@ -1033,12 +1006,11 @@ static GPtrArray *textview_scan_header(TextView *textview, FILE *fp) HeaderDisplayProp * dp = (HeaderDisplayProp *) l->data; for(i = 0 ; i < headers->len ; i++) { Header * header = g_ptr_array_index(headers, i); - if (hdrequal(header->name, dp->name)) { + if (procheader_headername_equal(header->name, + dp->name)) { if (dp->hidden) { g_ptr_array_remove_index(headers, i); - g_free(header->body); - g_free(header->name); - g_free(header); + procheader_header_free(header); i--; } else { @@ -1052,14 +1024,20 @@ static GPtrArray *textview_scan_header(TextView *textview, FILE *fp) } if (prefs_display_headers.show_other_headers) { - for(i = 0 ; i < headers->len ; i++) { - Header * header = g_ptr_array_index(headers, i); + while (headers->len != 0) { + Header * header = g_ptr_array_index(headers, 0); g_ptr_array_add(sorted_headers, header); + g_ptr_array_remove_index(headers, 0); } } - g_ptr_array_free(headers, TRUE); - + for(i = 0 ; i < headers->len ; i++) { + Header * header = g_ptr_array_index(headers, i); + procheader_header_free(header); + } + + g_ptr_array_free(headers, FALSE); + return sorted_headers; } @@ -1079,6 +1057,8 @@ static void textview_show_header(TextView *textview, GPtrArray *headers) gtk_text_insert(text, textview->boldfont, NULL, NULL, header->name, -1); + gtk_text_insert(text, textview->boldfont, NULL, NULL, + " ", -1); if (prefs_common.enable_color && (strncmp(header->name, "X-Mailer", 8) == 0 || strncmp(header->name, "X-Newsreader", 12) == 0) &&