Line | Count | Source (jump to first uncovered line) |
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 "index/lib.h" |
39 | | #include "parse/lib.h" |
40 | | #include "pattern/lib.h" |
41 | | #include "globals.h" // IWYU pragma: keep |
42 | | #include "mutt_thread.h" |
43 | | #include "protos.h" |
44 | | |
45 | | /** |
46 | | * struct Score - Scoring rule for email |
47 | | */ |
48 | | struct Score |
49 | | { |
50 | | char *str; |
51 | | struct PatternList *pat; |
52 | | int val; |
53 | | bool exact; ///< If this rule matches, don't evaluate any more |
54 | | struct Score *next; ///< Linked list |
55 | | }; |
56 | | |
57 | | /// Linked list of email scoring rules |
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 | | */ |
64 | | void mutt_check_rescore(struct Mailbox *m) |
65 | 0 | { |
66 | 0 | const bool c_score = cs_subset_bool(NeoMutt->sub, "score"); |
67 | 0 | if (OptNeedRescore && c_score) |
68 | 0 | { |
69 | 0 | const enum SortType c_sort = cs_subset_sort(NeoMutt->sub, "sort"); |
70 | 0 | const enum SortType c_sort_aux = cs_subset_sort(NeoMutt->sub, "sort_aux"); |
71 | 0 | if (((c_sort & SORT_MASK) == SORT_SCORE) || ((c_sort_aux & SORT_MASK) == SORT_SCORE)) |
72 | 0 | { |
73 | 0 | OptNeedResort = true; |
74 | 0 | if (mutt_using_threads()) |
75 | 0 | OptSortSubthreads = true; |
76 | 0 | } |
77 | |
|
78 | 0 | mutt_debug(LL_NOTIFY, "NT_SCORE: %p\n", m); |
79 | 0 | notify_send(m->notify, NT_SCORE, 0, NULL); |
80 | 0 | } |
81 | 0 | OptNeedRescore = false; |
82 | 0 | } |
83 | | |
84 | | /** |
85 | | * mutt_parse_score - Parse the 'score' command - Implements Command::parse() - @ingroup command_parse |
86 | | */ |
87 | | enum CommandResult mutt_parse_score(struct Buffer *buf, struct Buffer *s, |
88 | | intptr_t data, struct Buffer *err) |
89 | 0 | { |
90 | 0 | struct Score *ptr = NULL, *last = NULL; |
91 | 0 | char *pattern = NULL, *pc = NULL; |
92 | |
|
93 | 0 | parse_extract_token(buf, s, TOKEN_NO_FLAGS); |
94 | 0 | if (!MoreArgs(s)) |
95 | 0 | { |
96 | 0 | buf_printf(err, _("%s: too few arguments"), "score"); |
97 | 0 | return MUTT_CMD_WARNING; |
98 | 0 | } |
99 | 0 | pattern = buf_strdup(buf); |
100 | 0 | parse_extract_token(buf, s, TOKEN_NO_FLAGS); |
101 | 0 | if (MoreArgs(s)) |
102 | 0 | { |
103 | 0 | FREE(&pattern); |
104 | 0 | buf_printf(err, _("%s: too many arguments"), "score"); |
105 | 0 | return MUTT_CMD_WARNING; |
106 | 0 | } |
107 | | |
108 | | /* look for an existing entry and update the value, else add it to the end |
109 | | * of the list */ |
110 | 0 | for (ptr = ScoreList, last = NULL; ptr; last = ptr, ptr = ptr->next) |
111 | 0 | if (mutt_str_equal(pattern, ptr->str)) |
112 | 0 | break; |
113 | |
|
114 | 0 | if (ptr) |
115 | 0 | { |
116 | | /* 'buf' arg was cleared and 'pattern' holds the only reference; |
117 | | * as here 'ptr' != NULL -> update the value only in which case |
118 | | * ptr->str already has the string, so pattern should be freed. */ |
119 | 0 | FREE(&pattern); |
120 | 0 | } |
121 | 0 | else |
122 | 0 | { |
123 | 0 | struct MailboxView *mv_cur = get_current_mailbox_view(); |
124 | 0 | struct Menu *menu = get_current_menu(); |
125 | 0 | struct PatternList *pat = mutt_pattern_comp(mv_cur, menu, pattern, MUTT_PC_NO_FLAGS, err); |
126 | 0 | if (!pat) |
127 | 0 | { |
128 | 0 | FREE(&pattern); |
129 | 0 | return MUTT_CMD_ERROR; |
130 | 0 | } |
131 | 0 | ptr = mutt_mem_calloc(1, sizeof(struct Score)); |
132 | 0 | if (last) |
133 | 0 | last->next = ptr; |
134 | 0 | else |
135 | 0 | ScoreList = ptr; |
136 | 0 | ptr->pat = pat; |
137 | 0 | ptr->str = pattern; |
138 | 0 | } |
139 | 0 | pc = buf->data; |
140 | 0 | if (*pc == '=') |
141 | 0 | { |
142 | 0 | ptr->exact = true; |
143 | 0 | pc++; |
144 | 0 | } |
145 | 0 | if (!mutt_str_atoi_full(pc, &ptr->val)) |
146 | 0 | { |
147 | 0 | FREE(&pattern); |
148 | 0 | buf_strcpy(err, _("Error: score: invalid number")); |
149 | 0 | return MUTT_CMD_ERROR; |
150 | 0 | } |
151 | 0 | OptNeedRescore = true; |
152 | 0 | return MUTT_CMD_SUCCESS; |
153 | 0 | } |
154 | | |
155 | | /** |
156 | | * mutt_score_message - Apply scoring to an email |
157 | | * @param m Mailbox |
158 | | * @param e Email |
159 | | * @param upd_mbox If true, update the Mailbox too |
160 | | */ |
161 | | void mutt_score_message(struct Mailbox *m, struct Email *e, bool upd_mbox) |
162 | 0 | { |
163 | 0 | struct Score *tmp = NULL; |
164 | 0 | struct PatternCache cache = { 0 }; |
165 | |
|
166 | 0 | e->score = 0; /* in case of re-scoring */ |
167 | 0 | for (tmp = ScoreList; tmp; tmp = tmp->next) |
168 | 0 | { |
169 | 0 | if (mutt_pattern_exec(SLIST_FIRST(tmp->pat), MUTT_MATCH_FULL_ADDRESS, NULL, e, &cache) > 0) |
170 | 0 | { |
171 | 0 | if (tmp->exact || (tmp->val == 9999) || (tmp->val == -9999)) |
172 | 0 | { |
173 | 0 | e->score = tmp->val; |
174 | 0 | break; |
175 | 0 | } |
176 | 0 | e->score += tmp->val; |
177 | 0 | } |
178 | 0 | } |
179 | 0 | if (e->score < 0) |
180 | 0 | e->score = 0; |
181 | |
|
182 | 0 | const short c_score_threshold_delete = cs_subset_number(NeoMutt->sub, "score_threshold_delete"); |
183 | 0 | const short c_score_threshold_flag = cs_subset_number(NeoMutt->sub, "score_threshold_flag"); |
184 | 0 | const short c_score_threshold_read = cs_subset_number(NeoMutt->sub, "score_threshold_read"); |
185 | |
|
186 | 0 | if (e->score <= c_score_threshold_delete) |
187 | 0 | mutt_set_flag(m, e, MUTT_DELETE, true, upd_mbox); |
188 | 0 | if (e->score <= c_score_threshold_read) |
189 | 0 | mutt_set_flag(m, e, MUTT_READ, true, upd_mbox); |
190 | 0 | if (e->score >= c_score_threshold_flag) |
191 | 0 | mutt_set_flag(m, e, MUTT_FLAG, true, upd_mbox); |
192 | 0 | } |
193 | | |
194 | | /** |
195 | | * mutt_parse_unscore - Parse the 'unscore' command - Implements Command::parse() - @ingroup command_parse |
196 | | */ |
197 | | enum CommandResult mutt_parse_unscore(struct Buffer *buf, struct Buffer *s, |
198 | | intptr_t data, struct Buffer *err) |
199 | 0 | { |
200 | 0 | struct Score *tmp = NULL, *last = NULL; |
201 | |
|
202 | 0 | while (MoreArgs(s)) |
203 | 0 | { |
204 | 0 | parse_extract_token(buf, s, TOKEN_NO_FLAGS); |
205 | 0 | if (mutt_str_equal("*", buf->data)) |
206 | 0 | { |
207 | 0 | for (tmp = ScoreList; tmp;) |
208 | 0 | { |
209 | 0 | last = tmp; |
210 | 0 | tmp = tmp->next; |
211 | 0 | mutt_pattern_free(&last->pat); |
212 | 0 | FREE(&last); |
213 | 0 | } |
214 | 0 | ScoreList = NULL; |
215 | 0 | } |
216 | 0 | else |
217 | 0 | { |
218 | 0 | for (tmp = ScoreList; tmp; last = tmp, tmp = tmp->next) |
219 | 0 | { |
220 | 0 | if (mutt_str_equal(buf->data, tmp->str)) |
221 | 0 | { |
222 | 0 | if (last) |
223 | 0 | last->next = tmp->next; |
224 | 0 | else |
225 | 0 | ScoreList = tmp->next; |
226 | 0 | mutt_pattern_free(&tmp->pat); |
227 | 0 | FREE(&tmp); |
228 | | /* there should only be one score per pattern, so we can stop here */ |
229 | 0 | break; |
230 | 0 | } |
231 | 0 | } |
232 | 0 | } |
233 | 0 | } |
234 | 0 | OptNeedRescore = true; |
235 | 0 | return MUTT_CMD_SUCCESS; |
236 | 0 | } |