1 /**
2 * @file
3 * Routines for adding user scores to emails
4 *
5 * @authors
6 * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
7 *
8 * @copyright
9 * This program is free software: you can redistribute it and/or modify it under
10 * the terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 2 of the License, or (at your option) any later
12 * version.
13 *
14 * This program is distributed in the hope that it will be useful, but WITHOUT
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 * details.
18 *
19 * You should have received a copy of the GNU General Public License along with
20 * this program. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 /**
24 * @page neo_score Routines for adding user scores to emails
25 *
26 * Routines for adding user scores to emails
27 */
28
29 #include "config.h"
30 #include <stdbool.h>
31 #include <stdlib.h>
32 #include "mutt/lib.h"
33 #include "config/lib.h"
34 #include "email/lib.h"
35 #include "core/lib.h"
36 #include "mutt.h"
37 #include "score.h"
38 #include "pattern/lib.h"
39 #include "context.h"
40 #include "init.h"
41 #include "mutt_globals.h"
42 #include "mutt_thread.h"
43 #include "options.h"
44 #include "protos.h"
45
46 /**
47 * struct Score - Scoring rule for email
48 */
49 struct Score
50 {
51 char *str;
52 struct PatternList *pat;
53 int val;
54 bool exact; ///< if this rule matches, don't evaluate any more
55 struct Score *next;
56 };
57
58 static struct Score *ScoreList = NULL;
59
60 /**
61 * mutt_check_rescore - Do the emails need to have their scores recalculated?
62 * @param m Mailbox
63 */
mutt_check_rescore(struct Mailbox * m)64 void mutt_check_rescore(struct Mailbox *m)
65 {
66 const bool c_score = cs_subset_bool(NeoMutt->sub, "score");
67 if (OptNeedRescore && c_score)
68 {
69 const short c_sort = cs_subset_sort(NeoMutt->sub, "sort");
70 const short c_sort_aux = cs_subset_sort(NeoMutt->sub, "sort_aux");
71 if (((c_sort & SORT_MASK) == SORT_SCORE) || ((c_sort_aux & SORT_MASK) == SORT_SCORE))
72 {
73 OptNeedResort = true;
74 if (mutt_using_threads())
75 OptSortSubthreads = true;
76 }
77
78 mutt_debug(LL_NOTIFY, "NT_SCORE: %p\n", m);
79 notify_send(m->notify, NT_SCORE, 0, NULL);
80 }
81 OptNeedRescore = false;
82 }
83
84 /**
85 * mutt_parse_score - Parse the 'score' command - Implements Command::parse() - @ingroup command_parse
86 */
mutt_parse_score(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)87 enum CommandResult mutt_parse_score(struct Buffer *buf, struct Buffer *s,
88 intptr_t data, struct Buffer *err)
89 {
90 struct Score *ptr = NULL, *last = NULL;
91 char *pattern = NULL, *pc = NULL;
92
93 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
94 if (!MoreArgs(s))
95 {
96 mutt_buffer_printf(err, _("%s: too few arguments"), "score");
97 return MUTT_CMD_WARNING;
98 }
99 pattern = mutt_buffer_strdup(buf);
100 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
101 if (MoreArgs(s))
102 {
103 FREE(&pattern);
104 mutt_buffer_printf(err, _("%s: too many arguments"), "score");
105 return MUTT_CMD_WARNING;
106 }
107
108 /* look for an existing entry and update the value, else add it to the end
109 * of the list */
110 for (ptr = ScoreList, last = NULL; ptr; last = ptr, ptr = ptr->next)
111 if (mutt_str_equal(pattern, ptr->str))
112 break;
113 if (!ptr)
114 {
115 struct PatternList *pat =
116 mutt_pattern_comp(ctx_mailbox(Context), Context ? Context->menu : NULL,
117 pattern, MUTT_PC_NO_FLAGS, err);
118 if (!pat)
119 {
120 FREE(&pattern);
121 return MUTT_CMD_ERROR;
122 }
123 ptr = mutt_mem_calloc(1, sizeof(struct Score));
124 if (last)
125 last->next = ptr;
126 else
127 ScoreList = ptr;
128 ptr->pat = pat;
129 ptr->str = pattern;
130 }
131 else
132 {
133 /* 'buf' arg was cleared and 'pattern' holds the only reference;
134 * as here 'ptr' != NULL -> update the value only in which case
135 * ptr->str already has the string, so pattern should be freed. */
136 FREE(&pattern);
137 }
138 pc = buf->data;
139 if (*pc == '=')
140 {
141 ptr->exact = true;
142 pc++;
143 }
144 if (mutt_str_atoi(pc, &ptr->val) < 0)
145 {
146 FREE(&pattern);
147 mutt_buffer_strcpy(err, _("Error: score: invalid number"));
148 return MUTT_CMD_ERROR;
149 }
150 OptNeedRescore = true;
151 return MUTT_CMD_SUCCESS;
152 }
153
154 /**
155 * mutt_score_message - Apply scoring to an email
156 * @param m Mailbox
157 * @param e Email
158 * @param upd_mbox If true, update the Mailbox too
159 */
mutt_score_message(struct Mailbox * m,struct Email * e,bool upd_mbox)160 void mutt_score_message(struct Mailbox *m, struct Email *e, bool upd_mbox)
161 {
162 struct Score *tmp = NULL;
163 struct PatternCache cache = { 0 };
164
165 e->score = 0; /* in case of re-scoring */
166 for (tmp = ScoreList; tmp; tmp = tmp->next)
167 {
168 if (mutt_pattern_exec(SLIST_FIRST(tmp->pat), MUTT_MATCH_FULL_ADDRESS, NULL, e, &cache) > 0)
169 {
170 if (tmp->exact || (tmp->val == 9999) || (tmp->val == -9999))
171 {
172 e->score = tmp->val;
173 break;
174 }
175 e->score += tmp->val;
176 }
177 }
178 if (e->score < 0)
179 e->score = 0;
180
181 const short c_score_threshold_delete =
182 cs_subset_number(NeoMutt->sub, "score_threshold_delete");
183 const short c_score_threshold_flag =
184 cs_subset_number(NeoMutt->sub, "score_threshold_flag");
185 const short c_score_threshold_read =
186 cs_subset_number(NeoMutt->sub, "score_threshold_read");
187
188 if (e->score <= c_score_threshold_delete)
189 mutt_set_flag_update(m, e, MUTT_DELETE, true, upd_mbox);
190 if (e->score <= c_score_threshold_read)
191 mutt_set_flag_update(m, e, MUTT_READ, true, upd_mbox);
192 if (e->score >= c_score_threshold_flag)
193 mutt_set_flag_update(m, e, MUTT_FLAG, true, upd_mbox);
194 }
195
196 /**
197 * mutt_parse_unscore - Parse the 'unscore' command - Implements Command::parse() - @ingroup command_parse
198 */
mutt_parse_unscore(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)199 enum CommandResult mutt_parse_unscore(struct Buffer *buf, struct Buffer *s,
200 intptr_t data, struct Buffer *err)
201 {
202 struct Score *tmp = NULL, *last = NULL;
203
204 while (MoreArgs(s))
205 {
206 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
207 if (mutt_str_equal("*", buf->data))
208 {
209 for (tmp = ScoreList; tmp;)
210 {
211 last = tmp;
212 tmp = tmp->next;
213 mutt_pattern_free(&last->pat);
214 FREE(&last);
215 }
216 ScoreList = NULL;
217 }
218 else
219 {
220 for (tmp = ScoreList; tmp; last = tmp, tmp = tmp->next)
221 {
222 if (mutt_str_equal(buf->data, tmp->str))
223 {
224 if (last)
225 last->next = tmp->next;
226 else
227 ScoreList = tmp->next;
228 mutt_pattern_free(&tmp->pat);
229 FREE(&tmp);
230 /* there should only be one score per pattern, so we can stop here */
231 break;
232 }
233 }
234 }
235 }
236 OptNeedRescore = true;
237 return MUTT_CMD_SUCCESS;
238 }
239