* For mailing lists, Alpine adds a description of the type of link
[alpine.git] / pith / sequence.c
blob6112ed9513a553c0f7ba358594431bae41da51dd
1 /*
2 * ========================================================================
3 * Copyright 2013-2022 Eduardo Chappa
4 * Copyright 2006-2007 University of Washington
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
15 #include "../pith/headers.h"
16 #include "../pith/sequence.h"
17 #include "../pith/state.h"
18 #include "../pith/conf.h"
19 #include "../pith/msgno.h"
20 #include "../pith/flag.h"
21 #include "../pith/thread.h"
22 #include "../pith/icache.h"
26 * Internal prototypes
30 /*----------------------------------------------------------------------
31 Build comma delimited list of selected messages
33 Args: stream -- mail stream to use for flag testing
34 msgmap -- message number struct of to build selected messages in
35 count -- pointer to place to write number of comma delimited
36 mark -- mark manually undeleted so we don't refilter right away
38 Returns: malloc'd string containing sequence, else NULL if
39 no messages in msgmap with local "selected" flag.
40 ----*/
41 char *
42 selected_sequence(MAILSTREAM *stream, MSGNO_S *msgmap, long int *count, int mark)
44 long i;
45 MESSAGECACHE *mc;
47 if(!stream)
48 return(NULL);
51 * The plan here is to use the c-client elt's "sequence" bit
52 * to work around any orderings or exclusions in pine's internal
53 * mapping that might cause the sequence to be artificially
54 * lengthy. It's probably cheaper to run down the elt list
55 * twice rather than call nm_raw2m() for each message as
56 * we run down the elt list once...
58 for(i = 1L; i <= stream->nmsgs; i++)
59 if((mc = mail_elt(stream, i)) != NULL)
60 mc->sequence = 0;
62 for(i = 1L; i <= mn_get_total(msgmap); i++)
63 if(get_lflag(stream, msgmap, i, MN_SLCT)){
64 long rawno;
65 int exbits = 0;
68 * Forget we knew about it, and set "add to sequence"
69 * bit...
71 clear_index_cache_ent(stream, i, 0);
72 rawno = mn_m2raw(msgmap, i);
73 if(rawno > 0L && rawno <= stream->nmsgs
74 && (mc = mail_elt(stream, rawno)))
75 mc->sequence = 1;
78 * Mark this message manually flagged so we don't re-filter it
79 * with a filter which only sets flags.
81 if(mark){
82 if(msgno_exceptions(stream, rawno, "0", &exbits, FALSE))
83 exbits |= MSG_EX_MANUNDEL;
84 else
85 exbits = MSG_EX_MANUNDEL;
87 msgno_exceptions(stream, rawno, "0", &exbits, TRUE);
91 return(build_sequence(stream, NULL, count));
95 /*----------------------------------------------------------------------
96 Build comma delimited list of messages current in msgmap which have all
97 flags matching the arguments
99 Args: stream -- mail stream to use for flag testing
100 msgmap -- only consider messages selected in this msgmap
101 flag -- flags to match against
102 count -- pointer to place to return number of comma delimited
103 mark -- mark index cache entry changed, and count state change
104 kw_on -- if flag contains F_KEYWORD, this is
105 the array of keywords to be checked
106 kw_off -- if flag contains F_UNKEYWORD, this is
107 the array of keywords to be checked
109 Returns: malloc'd string containing sequence, else NULL if
110 no messages in msgmap with local "selected" flag (a flag
111 of zero means all current msgs).
112 ----*/
113 char *
114 currentf_sequence(MAILSTREAM *stream, MSGNO_S *msgmap, long int flag,
115 long int *count, int mark, char **kw_on, char **kw_off)
117 char *seq, **t;
118 long i, rawno;
119 int exbits;
120 MESSAGECACHE *mc;
122 if(!stream)
123 return(NULL);
125 /* First, make sure elts are valid for all the interesting messages */
126 if((seq = invalid_elt_sequence(stream, msgmap)) != NULL){
127 pine_mail_fetch_flags(stream, seq, NIL);
128 fs_give((void **) &seq);
131 for(i = 1L; i <= stream->nmsgs; i++)
132 if((mc = mail_elt(stream, i)) != NULL)
133 mc->sequence = 0; /* clear "sequence" bits */
135 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){
136 /* if not already set, go on... */
137 rawno = mn_m2raw(msgmap, i);
138 mc = (rawno > 0L && rawno <= stream->nmsgs)
139 ? mail_elt(stream, rawno) : NULL;
140 if(!mc)
141 continue;
143 if((flag == 0)
144 || ((flag & F_DEL) && mc->deleted)
145 || ((flag & F_UNDEL) && !mc->deleted)
146 || ((flag & F_SEEN) && mc->seen)
147 || ((flag & F_UNSEEN) && !mc->seen)
148 || ((flag & F_ANS) && mc->answered)
149 || ((flag & F_UNANS) && !mc->answered)
150 || ((flag & F_FLAG) && mc->flagged)
151 || ((flag & F_UNFLAG) && !mc->flagged)){
152 mc->sequence = 1; /* set "sequence" flag */
155 /* check for forwarded status */
156 if(!mc->sequence
157 && (flag & F_FWD)
158 && user_flag_is_set(stream, rawno, FORWARDED_FLAG)){
159 mc->sequence = 1;
162 if(!mc->sequence
163 && (flag & F_UNFWD)
164 && !user_flag_is_set(stream, rawno, FORWARDED_FLAG)){
165 mc->sequence = 1;
168 /* check for user keywords or not */
169 if(!mc->sequence && flag & F_KEYWORD && kw_on){
170 for(t = kw_on; !mc->sequence && *t; t++)
171 if(user_flag_is_set(stream, rawno, *t))
172 mc->sequence = 1;
174 else if(!mc->sequence && flag & F_UNKEYWORD && kw_off){
175 for(t = kw_off; !mc->sequence && *t; t++)
176 if(!user_flag_is_set(stream, rawno, *t))
177 mc->sequence = 1;
180 if(mc->sequence){
181 if(mark){
182 if(THRD_INDX()){
183 PINETHRD_S *thrd;
184 long t;
186 /* clear thread index line instead of index index line */
187 thrd = fetch_thread(stream, mn_m2raw(msgmap, i));
188 if(thrd && thrd->top
189 && (thrd=fetch_thread(stream,thrd->top))
190 && (t = mn_raw2m(msgmap, thrd->rawno)))
191 clear_index_cache_ent(stream, t, 0);
193 else
194 clear_index_cache_ent(stream, i, 0);/* force new index line */
197 * Mark this message manually flagged so we don't re-filter it
198 * with a filter which only sets flags.
200 exbits = 0;
201 if(msgno_exceptions(stream, rawno, "0", &exbits, FALSE))
202 exbits |= MSG_EX_MANUNDEL;
203 else
204 exbits = MSG_EX_MANUNDEL;
206 msgno_exceptions(stream, rawno, "0", &exbits, TRUE);
211 return(build_sequence(stream, NULL, count));
215 /*----------------------------------------------------------------------
216 Return sequence numbers of messages with invalid MESSAGECACHEs
218 Args: stream -- mail stream to use for flag testing
219 msgmap -- message number struct of to build selected messages in
221 Returns: malloc'd string containing sequence, else NULL if
222 no messages in msgmap with local "selected" flag (a flag
223 of zero means all current msgs).
224 ----*/
225 char *
226 invalid_elt_sequence(MAILSTREAM *stream, MSGNO_S *msgmap)
228 long i, rawno;
229 MESSAGECACHE *mc;
231 if(!stream)
232 return(NULL);
234 for(i = 1L; i <= stream->nmsgs; i++)
235 if((mc = mail_elt(stream, i)) != NULL)
236 mc->sequence = 0; /* clear "sequence" bits */
238 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap))
239 if((rawno = mn_m2raw(msgmap, i)) > 0L && rawno <= stream->nmsgs
240 && (mc = mail_elt(stream, rawno)) && !mc->valid)
241 mc->sequence = 1;
243 return(build_sequence(stream, NULL, NULL));
247 /*----------------------------------------------------------------------
248 Build comma delimited list of messages with elt "sequence" bit set
250 Args: stream -- mail stream to use for flag testing
251 msgmap -- struct containing sort to build sequence in
252 count -- pointer to place to write number of comma delimited
253 NOTE: if non-zero, it's a clue as to how many messages
254 have the sequence bit lit.
256 Returns: malloc'd string containing sequence, else NULL if
257 no messages in msgmap with elt's "sequence" bit set
258 ----*/
259 char *
260 build_sequence(MAILSTREAM *stream, MSGNO_S *msgmap, long int *count)
262 #define SEQ_INCREMENT 128
263 long n = 0L, i, x, lastn = 0L, runstart = 0L;
264 size_t size = SEQ_INCREMENT;
265 char *seq = NULL, *p;
266 MESSAGECACHE *mc;
268 if(!stream)
269 return(NULL);
271 if(count){
272 if(*count > 0L)
273 size = MAX(size, MIN((*count) * 4, 16384));
275 *count = 0L;
278 for(x = 1L; x <= stream->nmsgs; x++){
279 if(msgmap){
280 if((i = mn_m2raw(msgmap, x)) == 0L)
281 continue;
283 else
284 i = x;
286 if(i > 0L && i <= stream->nmsgs
287 && (mc = mail_elt(stream, i)) && mc->sequence){
288 n++;
289 if(!seq) /* initialize if needed */
290 seq = p = fs_get(size);
293 * This code will coalesce the ascending runs of
294 * sequence numbers, but fails to break sequences
295 * into a reasonably sensible length for imapd's to
296 * swallow (reasonable addtition to c-client?)...
298 if(lastn){ /* if may be in a run */
299 if(lastn + 1L == i){ /* and its the next raw num */
300 lastn = i; /* skip writing anything... */
301 continue;
303 else if(runstart != lastn){
304 *p++ = (runstart + 1L == lastn) ? ',' : ':';
305 sstrncpy(&p, long2string(lastn), size-(p-seq));
306 } /* wrote end of run */
309 runstart = lastn = i; /* remember last raw num */
311 if(n > 1L && (p-seq) < size) /* !first num, write delim */
312 *p++ = ',';
314 if(size - (p - seq) < 16){ /* room for two more nums? */
315 size_t offset = p - seq; /* grow the sequence array */
316 size += SEQ_INCREMENT;
317 fs_resize((void **)&seq, size);
318 p = seq + offset;
321 sstrncpy(&p, long2string(i), size-(p-seq)); /* write raw number */
325 if(lastn && runstart != lastn){ /* were in a run? */
326 if((p-seq) < size) /* !first num, write delim */
327 *p++ = (runstart + 1L == lastn) ? ',' : ':';
329 sstrncpy(&p, long2string(lastn), size-(p-seq)); /* write the trailing num */
332 if(seq && (p-seq) < size) /* if sequence, tie it off */
333 *p = '\0';
335 if(seq)
336 seq[size-1] = '\0';
338 if(count)
339 *count = n;
341 return(seq);
345 /*----------------------------------------------------------------------
346 If any messages flagged "selected", fake the "currently selected" array
348 Args: map -- message number struct of to build selected messages in
350 OK folks, here's the tradeoff: either all the functions have to
351 know if the user wants to deal with the "current" hilited message
352 or the list of currently "selected" messages, *or* we just
353 wrap the call to these functions with some glue that tweeks
354 what these functions see as the "current" message list, and let them
355 do their thing.
356 ----*/
358 pseudo_selected(MAILSTREAM *stream, MSGNO_S *map)
360 long i, later = 0L;
362 if(any_lflagged(map, MN_SLCT)){
363 map->hilited = mn_m2raw(map, mn_get_cur(map));
365 for(i = 1L; i <= mn_get_total(map); i++)
366 if(get_lflag(stream, map, i, MN_SLCT)){
367 if(!later++){
368 mn_set_cur(map, i);
370 else{
371 mn_add_cur(map, i);
375 return(1);
378 return(0);
382 /*----------------------------------------------------------------------
383 Antidote for the monkey business committed above
385 Args: map -- message number struct of to build selected messages in
387 ----*/
388 void
389 restore_selected(MSGNO_S *map)
391 if(map->hilited){
392 mn_reset_cur(map, mn_raw2m(map, map->hilited));
393 map->hilited = 0L;
399 * Return a search set which can be used to limit the search to a smaller set,
400 * for performance reasons. The caller must still work correctly if we return
401 * the whole set (or NULL) here, because we may not be able to send the full
402 * search set over IMAP. In cases where the search set description is getting
403 * too large we send a search set which contains all of the relevant messages.
404 * It may contain more.
406 * Args stream
407 * narrow -- If set, we are narrowing our selection (restrict to
408 * messages with MN_SLCT already set) or if not set,
409 * we are broadening (so we may look only at messages
410 * with MN_SLCT not set)
412 * Returns - allocated search set or NULL. Caller is responsible for freeing it.
414 SEARCHSET *
415 limiting_searchset(MAILSTREAM *stream, int narrow)
417 long n, run;
418 int cnt = 0;
419 SEARCHSET *full_set = NULL, **set;
422 * If we're talking to anything other than a server older than
423 * imap 4rev1, build a searchset otherwise it'll choke.
425 if(!(is_imap_stream(stream) && !modern_imap_stream(stream))){
426 for(n = 1L, set = &full_set, run = 0L; n <= stream->nmsgs; n++)
427 /* test for end of run */
428 if(get_lflag(stream, NULL, n, MN_EXLD)
429 || (narrow && !get_lflag(stream, NULL, n, MN_SLCT))
430 || (!narrow && get_lflag(stream, NULL, n, MN_SLCT))){
431 if(run){ /* previous selected? */
432 set = &(*set)->next;
433 run = 0L;
436 else if(run++){ /* next in run */
437 (*set)->last = n;
439 else{ /* start of run */
440 *set = mail_newsearchset();
441 (*set)->first = (*set)->last = n;
444 * Make this last set cover the rest of the messages.
445 * We could be fancier about this but it probably isn't
446 * worth the trouble.
448 if(++cnt > 100){
449 (*set)->last = stream->nmsgs;
450 break;
455 return(full_set);