Coverage Report

Created: 2023-06-07 06:15

/src/neomutt/score.c
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
}