1 /**
2 * @file
3 * The "currently-open" mailbox
4 *
5 * @authors
6 * Copyright (C) 2018 Richard Russon <rich@flatcap.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_ctx The "currently-open" mailbox
25 *
26 * The "currently-open" mailbox
27 */
28
29 #include "config.h"
30 #include <string.h>
31 #include "mutt/lib.h"
32 #include "config/lib.h"
33 #include "email/lib.h"
34 #include "core/lib.h"
35 #include "context.h"
36 #include "imap/lib.h"
37 #include "ncrypt/lib.h"
38 #include "pattern/lib.h"
39 #include "mutt_header.h"
40 #include "mutt_thread.h"
41 #include "mx.h"
42 #include "score.h"
43 #include "sort.h"
44
45 /**
46 * ctx_free - Free a Context
47 * @param[out] ptr Context to free
48 */
ctx_free(struct Context ** ptr)49 void ctx_free(struct Context **ptr)
50 {
51 if (!ptr || !*ptr)
52 return;
53
54 struct Context *ctx = *ptr;
55
56 struct EventContext ev_c = { ctx };
57 mutt_debug(LL_NOTIFY, "NT_CONTEXT_DELETE: %p\n", ctx);
58 notify_send(ctx->notify, NT_CONTEXT, NT_CONTEXT_DELETE, &ev_c);
59
60 if (ctx->mailbox)
61 notify_observer_remove(ctx->mailbox->notify, ctx_mailbox_observer, ctx);
62
63 mutt_thread_ctx_free(&ctx->threads);
64 notify_free(&ctx->notify);
65 FREE(&ctx->pattern);
66 mutt_pattern_free(&ctx->limit_pattern);
67
68 FREE(&ctx);
69 *ptr = NULL;
70 }
71
72 /**
73 * ctx_new - Create a new Context
74 * @param m Mailbox
75 * @retval ptr New Context
76 */
ctx_new(struct Mailbox * m)77 struct Context *ctx_new(struct Mailbox *m)
78 {
79 if (!m)
80 return NULL;
81
82 struct Context *ctx = mutt_mem_calloc(1, sizeof(struct Context));
83
84 ctx->notify = notify_new();
85 notify_set_parent(ctx->notify, NeoMutt->notify);
86 struct EventContext ev_c = { ctx };
87 mutt_debug(LL_NOTIFY, "NT_CONTEXT_ADD: %p\n", ctx);
88 notify_send(ctx->notify, NT_CONTEXT, NT_CONTEXT_ADD, &ev_c);
89 // If the Mailbox is closed, ctx->mailbox must be set to NULL
90 notify_observer_add(m->notify, NT_MAILBOX, ctx_mailbox_observer, ctx);
91
92 ctx->mailbox = m;
93 ctx->threads = mutt_thread_ctx_init(m);
94 ctx->msg_in_pager = -1;
95 ctx->collapsed = false;
96 ctx_update(ctx);
97
98 return ctx;
99 }
100
101 /**
102 * ctx_cleanup - Release memory and initialize a Context object
103 * @param ctx Context to cleanup
104 */
ctx_cleanup(struct Context * ctx)105 static void ctx_cleanup(struct Context *ctx)
106 {
107 FREE(&ctx->pattern);
108 mutt_pattern_free(&ctx->limit_pattern);
109 if (ctx->mailbox)
110 notify_observer_remove(ctx->mailbox->notify, ctx_mailbox_observer, ctx);
111
112 struct Notify *notify = ctx->notify;
113 struct Mailbox *m = ctx->mailbox;
114 memset(ctx, 0, sizeof(struct Context));
115 ctx->notify = notify;
116 ctx->mailbox = m;
117 }
118
119 /**
120 * ctx_update - Update the Context's message counts
121 * @param ctx Mailbox
122 *
123 * this routine is called to update the counts in the context structure
124 */
ctx_update(struct Context * ctx)125 void ctx_update(struct Context *ctx)
126 {
127 if (!ctx || !ctx->mailbox)
128 return;
129
130 struct Mailbox *m = ctx->mailbox;
131
132 mutt_hash_free(&m->subj_hash);
133 mutt_hash_free(&m->id_hash);
134
135 /* reset counters */
136 m->msg_unread = 0;
137 m->msg_flagged = 0;
138 m->msg_new = 0;
139 m->msg_deleted = 0;
140 m->msg_tagged = 0;
141 m->vcount = 0;
142 m->changed = false;
143
144 mutt_clear_threads(ctx->threads);
145
146 const bool c_score = cs_subset_bool(NeoMutt->sub, "score");
147 struct Email *e = NULL;
148 for (int msgno = 0; msgno < m->msg_count; msgno++)
149 {
150 e = m->emails[msgno];
151 if (!e)
152 continue;
153
154 if (WithCrypto)
155 {
156 /* NOTE: this _must_ be done before the check for mailcap! */
157 e->security = crypt_query(e->body);
158 }
159
160 if (ctx_has_limit(ctx))
161 {
162 e->vnum = -1;
163 }
164 else
165 {
166 m->v2r[m->vcount] = msgno;
167 e->vnum = m->vcount++;
168 }
169 e->msgno = msgno;
170
171 if (e->env->supersedes)
172 {
173 struct Email *e2 = NULL;
174
175 if (!m->id_hash)
176 m->id_hash = mutt_make_id_hash(m);
177
178 e2 = mutt_hash_find(m->id_hash, e->env->supersedes);
179 if (e2)
180 {
181 e2->superseded = true;
182 if (c_score)
183 mutt_score_message(ctx->mailbox, e2, true);
184 }
185 }
186
187 /* add this message to the hash tables */
188 if (m->id_hash && e->env->message_id)
189 mutt_hash_insert(m->id_hash, e->env->message_id, e);
190 if (m->subj_hash && e->env->real_subj)
191 mutt_hash_insert(m->subj_hash, e->env->real_subj, e);
192 mutt_label_hash_add(m, e);
193
194 if (c_score)
195 mutt_score_message(ctx->mailbox, e, false);
196
197 if (e->changed)
198 m->changed = true;
199 if (e->flagged)
200 m->msg_flagged++;
201 if (e->deleted)
202 m->msg_deleted++;
203 if (e->tagged)
204 m->msg_tagged++;
205 if (!e->read)
206 {
207 m->msg_unread++;
208 if (!e->old)
209 m->msg_new++;
210 }
211 }
212
213 /* rethread from scratch */
214 mutt_sort_headers(ctx->mailbox, ctx->threads, true, &ctx->vsize);
215 }
216
217 /**
218 * update_tables - Update a Context structure's internal tables
219 * @param ctx Mailbox
220 */
update_tables(struct Context * ctx)221 static void update_tables(struct Context *ctx)
222 {
223 if (!ctx || !ctx->mailbox)
224 return;
225
226 struct Mailbox *m = ctx->mailbox;
227
228 int i, j, padding;
229
230 /* update memory to reflect the new state of the mailbox */
231 m->vcount = 0;
232 ctx->vsize = 0;
233 m->msg_tagged = 0;
234 m->msg_deleted = 0;
235 m->msg_new = 0;
236 m->msg_unread = 0;
237 m->changed = false;
238 m->msg_flagged = 0;
239 padding = mx_msg_padding_size(m);
240 for (i = 0, j = 0; i < m->msg_count; i++)
241 {
242 if (!m->emails[i])
243 break;
244 const bool c_maildir_trash = cs_subset_bool(NeoMutt->sub, "maildir_trash");
245 if (!m->emails[i]->quasi_deleted &&
246 (!m->emails[i]->deleted || ((m->type == MUTT_MAILDIR) && c_maildir_trash)))
247 {
248 if (i != j)
249 {
250 m->emails[j] = m->emails[i];
251 m->emails[i] = NULL;
252 }
253 m->emails[j]->msgno = j;
254 if (m->emails[j]->vnum != -1)
255 {
256 m->v2r[m->vcount] = j;
257 m->emails[j]->vnum = m->vcount++;
258 struct Body *b = m->emails[j]->body;
259 ctx->vsize += b->length + b->offset - b->hdr_offset + padding;
260 }
261
262 m->emails[j]->changed = false;
263 m->emails[j]->env->changed = false;
264
265 if ((m->type == MUTT_MAILDIR) && c_maildir_trash)
266 {
267 if (m->emails[j]->deleted)
268 m->msg_deleted++;
269 }
270
271 if (m->emails[j]->tagged)
272 m->msg_tagged++;
273 if (m->emails[j]->flagged)
274 m->msg_flagged++;
275 if (!m->emails[j]->read)
276 {
277 m->msg_unread++;
278 if (!m->emails[j]->old)
279 m->msg_new++;
280 }
281
282 j++;
283 }
284 else
285 {
286 if ((m->type == MUTT_NOTMUCH) || (m->type == MUTT_MH) ||
287 (m->type == MUTT_MAILDIR) || (m->type == MUTT_IMAP))
288 {
289 mailbox_size_sub(m, m->emails[i]);
290 }
291 /* remove message from the hash tables */
292 if (m->subj_hash && m->emails[i]->env->real_subj)
293 mutt_hash_delete(m->subj_hash, m->emails[i]->env->real_subj, m->emails[i]);
294 if (m->id_hash && m->emails[i]->env->message_id)
295 mutt_hash_delete(m->id_hash, m->emails[i]->env->message_id, m->emails[i]);
296 mutt_label_hash_remove(m, m->emails[i]);
297
298 #ifdef USE_IMAP
299 if (m->type == MUTT_IMAP)
300 imap_notify_delete_email(m, m->emails[i]);
301 #endif
302
303 mailbox_gc_add(m->emails[i]);
304 m->emails[i] = NULL;
305 }
306 }
307 m->msg_count = j;
308 }
309
310 /**
311 * ctx_mailbox_observer - Notification that a Mailbox has changed - Implements ::observer_t - @ingroup observer_api
312 */
ctx_mailbox_observer(struct NotifyCallback * nc)313 int ctx_mailbox_observer(struct NotifyCallback *nc)
314 {
315 if ((nc->event_type != NT_MAILBOX) || !nc->global_data)
316 return -1;
317
318 struct Context *ctx = nc->global_data;
319
320 switch (nc->event_subtype)
321 {
322 case NT_MAILBOX_DELETE:
323 mutt_clear_threads(ctx->threads);
324 ctx_cleanup(ctx);
325 break;
326 case NT_MAILBOX_INVALID:
327 ctx_update(ctx);
328 break;
329 case NT_MAILBOX_UPDATE:
330 update_tables(ctx);
331 break;
332 case NT_MAILBOX_RESORT:
333 mutt_sort_headers(ctx->mailbox, ctx->threads, true, &ctx->vsize);
334 break;
335 default:
336 return 0;
337 }
338
339 mutt_debug(LL_DEBUG5, "mailbox done\n");
340 return 0;
341 }
342
343 /**
344 * message_is_tagged - Is a message in the index tagged (and within limit)
345 * @param e Email
346 * @retval true The message is both tagged and within limit
347 *
348 * If a limit is in effect, the message must be visible within it.
349 */
message_is_tagged(struct Email * e)350 bool message_is_tagged(struct Email *e)
351 {
352 return e->visible && e->tagged;
353 }
354
355 /**
356 * el_add_tagged - Get a list of the tagged Emails
357 * @param el Empty EmailList to populate
358 * @param ctx Current Mailbox
359 * @param e Current Email
360 * @param use_tagged Use tagged Emails
361 * @retval num Number of selected emails
362 * @retval -1 Error
363 */
el_add_tagged(struct EmailList * el,struct Context * ctx,struct Email * e,bool use_tagged)364 int el_add_tagged(struct EmailList *el, struct Context *ctx, struct Email *e, bool use_tagged)
365 {
366 int count = 0;
367
368 if (use_tagged)
369 {
370 if (!ctx || !ctx->mailbox || !ctx->mailbox->emails)
371 return -1;
372
373 struct Mailbox *m = ctx->mailbox;
374 for (size_t i = 0; i < m->msg_count; i++)
375 {
376 e = m->emails[i];
377 if (!e)
378 break;
379 if (!message_is_tagged(e))
380 continue;
381
382 struct EmailNode *en = mutt_mem_calloc(1, sizeof(*en));
383 en->email = e;
384 STAILQ_INSERT_TAIL(el, en, entries);
385 count++;
386 }
387 }
388 else
389 {
390 if (!e)
391 return -1;
392
393 struct EmailNode *en = mutt_mem_calloc(1, sizeof(*en));
394 en->email = e;
395 STAILQ_INSERT_TAIL(el, en, entries);
396 count = 1;
397 }
398
399 return count;
400 }
401
402 /**
403 * mutt_get_virt_email - Get a virtual Email
404 * @param m Mailbox
405 * @param vnum Virtual index number
406 * @retval ptr Email
407 * @retval NULL No Email selected, or bad index values
408 *
409 * This safely gets the result of the following:
410 * - `mailbox->emails[mailbox->v2r[vnum]]`
411 */
mutt_get_virt_email(struct Mailbox * m,int vnum)412 struct Email *mutt_get_virt_email(struct Mailbox *m, int vnum)
413 {
414 if (!m || !m->emails || !m->v2r)
415 return NULL;
416
417 if ((vnum < 0) || (vnum >= m->vcount))
418 return NULL;
419
420 int inum = m->v2r[vnum];
421 if ((inum < 0) || (inum >= m->msg_count))
422 return NULL;
423
424 return m->emails[inum];
425 }
426
427 /**
428 * ctx_has_limit - Is a limit active?
429 * @param ctx Context
430 * @retval true A limit is active
431 * @retval false No limit is active
432 */
ctx_has_limit(const struct Context * ctx)433 bool ctx_has_limit(const struct Context *ctx)
434 {
435 return ctx && ctx->pattern;
436 }
437
438 /**
439 * ctx_mailbox - Wrapper to get the mailbox in a Context, or NULL
440 * @param ctx Context
441 * @retval ptr The mailbox in the Context
442 * @retval NULL Context is NULL or doesn't have a mailbox
443 */
ctx_mailbox(struct Context * ctx)444 struct Mailbox *ctx_mailbox(struct Context *ctx)
445 {
446 return ctx ? ctx->mailbox : NULL;
447 }
448