/* * ======================================================================== * Copyright 2013-2022 Eduardo Chappa * Copyright 2006-2007 University of Washington * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * ======================================================================== */ /*====================================================================== flag.c Implements Pine message flag management routines ====*/ #include "../pith/headers.h" #include "../pith/flag.h" #include "../pith/pineelt.h" #include "../pith/icache.h" #include "../pith/mailindx.h" #include "../pith/mailcmd.h" #include "../pith/msgno.h" #include "../pith/thread.h" #include "../pith/sort.h" #include "../pith/news.h" #include "../pith/sequence.h" /* * Internal prototypes */ void flag_search(MAILSTREAM *, int, MsgNo, MSGNO_S *, long (*)(MAILSTREAM *)); long flag_search_sequence(MAILSTREAM *, MSGNO_S *, long, int); /*---------------------------------------------------------------------- Return sequence number based on given index that are search-worthy Args: stream -- msgmap -- msgno -- flags -- flags for msgline_hidden Result: 0 : index not search-worthy -1 : index out of bounds 1 - stream->nmsgs : sequence number to flag search ----*/ long flag_search_sequence(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, int flags) { long rawno = msgno; return((msgno > stream->nmsgs || msgno <= 0 || (msgmap && !(rawno = mn_m2raw(msgmap, msgno)))) ? -1L /* out of range! */ : ((get_lflag(stream, NULL, rawno, MN_EXLD) || (msgmap && msgline_hidden(stream, msgmap, msgno, flags))) ? 0L /* NOT interesting! */ : rawno)); } /*---------------------------------------------------------------------- Perform mail_search based on flag bits Args: stream -- flags -- start -- msgmap -- Result: if no set_* specified, call mail_search to light the searched bit for all the messages matching the given flags. If set_start specified, it is an index (possibly into set_msgmap) telling us where to search for invalid flag state hence when we return everything with the searched bit is interesting and everything with the valid bit lit is believably valid. ----*/ void flag_search(MAILSTREAM *stream, int flags, MsgNo set_start, MSGNO_S *set_msgmap, long int (*ping)(MAILSTREAM *)) { long n, i, new; char *seq; SEARCHPGM *pgm; SEARCHSET *full_set = NULL, **set; MESSAGECACHE *mc; extern MAILSTREAM *mm_search_stream; if(!stream) return; new = sp_new_mail_count(stream); /* Anything we don't already have flags for? */ if(set_start){ /* * Use elt's sequence bit to coalesce runs in ascending * sequence order... */ for(i = 1L; i <= stream->nmsgs; i++) if((mc = mail_elt(stream, i)) != NULL) mc->sequence = 0; for(i = set_start; (n = flag_search_sequence(stream, set_msgmap, i, MH_ANYTHD)) >= 0L; (flags & F_SRCHBACK) ? i-- : i++) if(n > 0L && n <= stream->nmsgs && (mc = mail_elt(stream, n)) && !mc->valid) mc->sequence = 1; /* Unroll searchset in ascending sequence order */ set = &full_set; for(i = 1L; i <= stream->nmsgs; i++) if((mc = mail_elt(stream, i)) && mc->sequence){ if(*set){ if(((*set)->last ? (*set)->last : (*set)->first)+1L == i) (*set)->last = i; else set = &(*set)->next; } if(!*set){ *set = mail_newsearchset(); (*set)->first = i; } } /* * No search-worthy messages?, prod the server for * any flag updates and clear the searched bits... */ if(full_set){ if(full_set->first == 1 && full_set->last == stream->nmsgs && full_set->next == NULL) mail_free_searchset(&full_set); } else{ for(i = 1L; i <= stream->nmsgs; i++) if((mc = mail_elt(stream, i)) != NULL) mc->searched = 0; if(ping) (*ping)(stream); /* prod server for any flag updates */ if(!(flags & F_NOFILT) && new != sp_new_mail_count(stream)){ process_filter_patterns(stream, sp_msgmap(stream), sp_new_mail_count(stream)); refresh_sort(stream, sp_msgmap(stream), SRT_NON); flag_search(stream, flags, set_start, set_msgmap, ping); } return; } } if((!is_imap_stream(stream) || modern_imap_stream(stream)) && !(IS_NEWS(stream))){ pgm = mail_newsearchpgm(); if(flags & F_SEEN) pgm->seen = 1; if(flags & F_UNSEEN) pgm->unseen = 1; if(flags & F_DEL) pgm->deleted = 1; if(flags & F_UNDEL) pgm->undeleted = 1; if(flags & F_ANS) pgm->answered = 1; if(flags & F_UNANS) pgm->unanswered = 1; if(flags & F_FLAG) pgm->flagged = 1; if(flags & F_UNFLAG) pgm->unflagged = 1; if(flags & (F_FWD | F_UNFWD)){ STRINGLIST **slpp; for(slpp = (flags & F_FWD) ? &pgm->keyword : &pgm->unkeyword; *slpp; slpp = &(*slpp)->next) ; *slpp = mail_newstringlist(); (*slpp)->text.data = (unsigned char *) cpystr(FORWARDED_FLAG); (*slpp)->text.size = (unsigned long) strlen(FORWARDED_FLAG); } if(flags & F_RECENT) pgm->recent = 1; if(flags & F_OR_SEEN){ SEARCHPGM *pgm2 = mail_newsearchpgm(); pgm2->or = mail_newsearchor(); pgm2->or->first = pgm; pgm2->or->second = mail_newsearchpgm(); pgm2->or->second->seen = 1; pgm = pgm2; } if(flags & F_OR_UNSEEN){ SEARCHPGM *pgm2 = mail_newsearchpgm(); pgm2->or = mail_newsearchor(); pgm2->or->first = pgm; pgm2->or->second = mail_newsearchpgm(); pgm2->or->second->unseen = 1; pgm = pgm2; } if(flags & F_OR_DEL){ SEARCHPGM *pgm2 = mail_newsearchpgm(); pgm2->or = mail_newsearchor(); pgm2->or->first = pgm; pgm2->or->second = mail_newsearchpgm(); pgm2->or->second->deleted = 1; pgm = pgm2; } if(flags & F_OR_UNDEL){ SEARCHPGM *pgm2 = mail_newsearchpgm(); pgm2->or = mail_newsearchor(); pgm2->or->first = pgm; pgm2->or->second = mail_newsearchpgm(); pgm2->or->second->undeleted = 1; pgm = pgm2; } if(flags & F_OR_FLAG){ SEARCHPGM *pgm2 = mail_newsearchpgm(); pgm2->or = mail_newsearchor(); pgm2->or->first = pgm; pgm2->or->second = mail_newsearchpgm(); pgm2->or->second->flagged = 1; pgm = pgm2; } if(flags & F_OR_UNFLAG){ SEARCHPGM *pgm2 = mail_newsearchpgm(); pgm2->or = mail_newsearchor(); pgm2->or->first = pgm; pgm2->or->second = mail_newsearchpgm(); pgm2->or->second->unflagged = 1; pgm = pgm2; } if(flags & F_OR_ANS){ SEARCHPGM *pgm2 = mail_newsearchpgm(); pgm2->or = mail_newsearchor(); pgm2->or->first = pgm; pgm2->or->second = mail_newsearchpgm(); pgm2->or->second->answered = 1; pgm = pgm2; } if(flags & F_OR_UNANS){ SEARCHPGM *pgm2 = mail_newsearchpgm(); pgm2->or = mail_newsearchor(); pgm2->or->first = pgm; pgm2->or->second = mail_newsearchpgm(); pgm2->or->second->unanswered = 1; pgm = pgm2; } if(flags & (F_OR_FWD | F_OR_UNFWD)){ STRINGLIST **slpp; SEARCHPGM *pgm2 = mail_newsearchpgm(); pgm2->or = mail_newsearchor(); pgm2->or->first = pgm; pgm2->or->second = mail_newsearchpgm(); for(slpp = (flags & F_OR_FWD) ? &pgm2->or->second->keyword : &pgm2->or->second->unkeyword; *slpp; slpp = &(*slpp)->next) ; *slpp = mail_newstringlist(); (*slpp)->text.data = (unsigned char *) cpystr(FORWARDED_FLAG); (*slpp)->text.size = (unsigned long) strlen(FORWARDED_FLAG); pgm = pgm2; } if(flags & F_OR_RECENT){ SEARCHPGM *pgm2 = mail_newsearchpgm(); pgm2->or = mail_newsearchor(); pgm2->or->first = pgm; pgm2->or->second = mail_newsearchpgm(); pgm2->or->second->recent = 1; pgm = pgm2; } pgm->msgno = full_set; pine_mail_search_full(mm_search_stream = stream, NULL, pgm, SE_NOPREFETCH | SE_FREE); if(!(flags & F_NOFILT) && new != sp_new_mail_count(stream)){ process_filter_patterns(stream, sp_msgmap(stream), sp_new_mail_count(stream)); flag_search(stream, flags, set_start, set_msgmap, ping); } } else{ if(full_set){ /* sequence bits of interesting msgs set */ mail_free_searchset(&full_set); } else{ /* light sequence bits of interesting msgs */ for(i = 1L; (n = flag_search_sequence(stream, set_msgmap, i, MH_ANYTHD)) >= 0L; i++) if(n > 0L && n <= stream->nmsgs && (mc = mail_elt(stream, n)) != NULL) mc->sequence = !mc->valid ? 1 : 0; } for(i = 1L; i <= stream->nmsgs; i++) if((mc = mail_elt(stream, i)) != NULL) mc->searched = 0; if((seq = build_sequence(stream, NULL, NULL)) != NULL){ pine_mail_fetch_flags(stream, seq, 0L); fs_give((void **) &seq); } } } /*---------------------------------------------------------------------- count messages on stream with specified system flag attributes Args: stream -- The stream/folder to look at message status flags -- flags on folder/stream to examine Result: count of messages flagged as requested Task: return count of message flagged as requested while being as server/network friendly as possible. Strategy: run thru flags to make sure they're all valid. If any invalid, do a search starting with the invalid message. If all valid, ping the server to let it know we're receptive to flag updates. At this ----------------------------------------------------------------------*/ long count_flagged(MAILSTREAM *stream, long int flags) { long n, count; MESSAGECACHE *mc; if(!stream) return(0L); flag_search(stream, flags, 1, NULL, pine_mail_ping); /* Paw thru once more since all should be updated */ for(n = 1L, count = 0L; n <= stream->nmsgs; n++) if((((mc = mail_elt(stream, n)) && mc->searched) || (mc && mc->valid && FLAG_MATCH(flags, mc, stream))) && !get_lflag(stream, NULL, n, MN_EXLD)){ mc->searched = 1; /* caller may be interested! */ count++; } return(count); } /*---------------------------------------------------------------------- Find the first message with the specified flags set Args: flags -- Flags in messagecache to match on stream -- The stream/folder to look at message status Result: Message number of first message with specified flags set or the number of the last message if none found. ----------------------------------------------------------------------*/ MsgNo first_sorted_flagged(long unsigned int flags, MAILSTREAM *stream, long int set_start, int opts) { MsgNo i, n, start_with, winner = 0L; MESSAGECACHE *mc; int last; MSGNO_S *msgmap; msgmap = sp_msgmap(stream); last = (opts & FSF_LAST); /* set_start only affects which search bits we light */ start_with = set_start ? set_start : (flags & F_SRCHBACK) ? mn_get_total(msgmap) : 1L; flag_search(stream, flags, start_with, msgmap, NULL); for(i = start_with; (n = flag_search_sequence(stream, msgmap, i, (opts & FSF_SKIP_CHID) ? 0 : MH_ANYTHD)) >= 0L; (flags & F_SRCHBACK) ? i-- : i++) if(n > 0L && n <= stream->nmsgs && (((mc = mail_elt(stream, n)) && mc->searched) || (mc && mc->valid && FLAG_MATCH(flags, mc, stream)))){ winner = i; if(!last) break; } if(winner == 0L && flags != F_UNDEL && flags != F_NONE){ dprint((4, "First_sorted_flagged didn't find a winner, look for undeleted\n")); winner = first_sorted_flagged(F_UNDEL, stream, 0L, opts | (mn_get_revsort(msgmap) ? 0 : FSF_LAST)); } if(winner == 0L && flags != F_NONE){ dprint((4, "First_sorted_flagged didn't find an undeleted, look for visible\n")); winner = first_sorted_flagged(F_NONE, stream, 0L, opts | (mn_get_revsort(msgmap) ? 0 : FSF_LAST)); } dprint((4, "First_sorted_flagged returning winner = %ld\n", winner)); return(winner ? winner : (mn_get_revsort(msgmap) ? 1L : mn_get_total(msgmap))); } /*---------------------------------------------------------------------- Find the next message with specified flags set Args: flags -- Flags in messagecache to match on stream -- The stream/folder to look at message status start -- Start looking after this message opts -- These bits are both input and output. On input the bit NSF_TRUST_FLAGS tells us whether we need to ping or not. On input, the bit NSF_SEARCH_BACK tells us that we want to know about matches <= start if we don't find any > start. On output, NSF_FLAG_MATCH is set if we matched a message. Returns: Message number of the matched message, if any; else the start # or the max_msgno if the mailbox changed dramatically. ----------------------------------------------------------------------*/ MsgNo next_sorted_flagged(long unsigned int flags, MAILSTREAM *stream, long int start, int *opts) { MsgNo i, n, dir; MESSAGECACHE *mc; int rev, fss_flags = 0; MSGNO_S *msgmap; msgmap = sp_msgmap(stream); /* * Search for the next thing the caller's interested in... */ fss_flags = (opts && *opts & NSF_SKIP_CHID) ? 0 : MH_ANYTHD; rev = (opts && *opts & NSF_SEARCH_BACK); dir = (rev ? -1L : 1L); flag_search(stream, flags | (rev ? F_SRCHBACK : 0), start + dir, msgmap, (opts && ((*opts) & NSF_TRUST_FLAGS)) ? NULL : pine_mail_ping); for(i = start + dir; (n = flag_search_sequence(stream, msgmap, i, fss_flags)) >= 0L; i += dir) if(n > 0L && n <= stream->nmsgs && (((mc = mail_elt(stream, n)) && mc->searched) || (mc && mc->valid && FLAG_MATCH(flags, mc, stream)))){ /* actually found a msg matching the flags */ if(opts) (*opts) |= NSF_FLAG_MATCH; return(i); } return(MIN(start, mn_get_total(msgmap))); } /*---------------------------------------------------------------------- get the requested LOCAL flag bits for the given pine message number Accepts: msgs - pointer to message manipulation struct n - message number to get f - bitmap of interesting flags Returns: non-zero if flag set, 0 if not set or no elt (error?) NOTE: this can be used to test system flags ----*/ int get_lflag(MAILSTREAM *stream, MSGNO_S *msgs, long int n, int f) { MESSAGECACHE *mc; PINELT_S *pelt; unsigned long rawno; rawno = msgs ? mn_m2raw(msgs, n) : n; if(!stream || rawno < 1L || rawno > stream->nmsgs) return(0); mc = mail_elt(stream, rawno); if(!mc || (pelt = (PINELT_S *) mc->sparep) == NULL) return(f ? 0 : 1); return((!f) ? !(pelt->hidden || pelt->excluded || pelt->selected || pelt->colhid || pelt->collapsed || pelt->searched) : (((f & MN_HIDE) ? pelt->hidden : 0) || ((f & MN_EXLD) ? pelt->excluded : 0) || ((f & MN_SLCT) ? pelt->selected : 0) || ((f & MN_STMP) ? pelt->tmp : 0) || ((f & MN_USOR) ? pelt->unsorted : 0) || ((f & MN_COLL) ? pelt->collapsed : 0) || ((f & MN_CHID) ? pelt->colhid : 0) || ((f & MN_CHID2) ? pelt->colhid2 : 0) || ((f & MN_SRCH) ? pelt->searched : 0))); } /*---------------------------------------------------------------------- set the requested LOCAL flag bits for the given pine message number Accepts: msgs - pointer to message manipulation struct n - message number to set f - bitmap of interesting flags v - value (on or off) flag should get Returns: our index number of first NOTE: this isn't to be used for setting IMAP system flags ----*/ int set_lflag(MAILSTREAM *stream, MSGNO_S *msgs, long int n, int f, int v) { MESSAGECACHE *mc; long rawno = 0L; PINETHRD_S *thrd, *topthrd = NULL; PINELT_S **peltp, *pelt; if(n < 1L || n > mn_get_total(msgs)) return(0L); if((rawno=mn_m2raw(msgs, n)) > 0L && stream && rawno <= stream->nmsgs && (mc = mail_elt(stream, rawno))){ int was_invisible, is_invisible; int chk_thrd_cnt = 0, thrd_was_visible = 0, was_hidden = 0, is_hidden; if(*(peltp = (PINELT_S **) &mc->sparep) == NULL){ *peltp = (PINELT_S *) fs_get(sizeof(PINELT_S)); memset(*peltp, 0, sizeof(PINELT_S)); } pelt = (*peltp); was_invisible = (pelt->hidden || pelt->colhid) ? 1 : 0; if((chk_thrd_cnt = ((msgs->visible_threads >= 0L) && THRD_INDX_ENABLED() && (f & MN_HIDE) && (pelt->hidden != v))) != 0){ thrd = fetch_thread(stream, rawno); if(thrd && thrd->top){ if(thrd->top == thrd->rawno) topthrd = thrd; else topthrd = fetch_thread(stream, thrd->top); } if(topthrd){ thrd_was_visible = thread_has_some_visible(stream, topthrd); was_hidden = pelt->hidden ? 1 : 0; } } if((f & MN_HIDE) && pelt->hidden != v){ pelt->hidden = v; msgs->flagged_hid += (v) ? 1L : -1L; if(pelt->hidden && THREADING() && !THRD_INDX() && stream == ps_global->mail_stream && ps_global->thread_disp_style == THREAD_MUTTLIKE) clear_index_cache_for_thread(stream, fetch_thread(stream, rawno), sp_msgmap(stream)); } if((f & MN_CHID) && pelt->colhid != v){ pelt->colhid = v; msgs->flagged_chid += (v) ? 1L : -1L; } if((f & MN_CHID2) && pelt->colhid2 != v){ pelt->colhid2 = v; msgs->flagged_chid2 += (v) ? 1L : -1L; } if((f & MN_COLL) && pelt->collapsed != v){ pelt->collapsed = v; msgs->flagged_coll += (v) ? 1L : -1L; } if((f & MN_USOR) && pelt->unsorted != v){ pelt->unsorted = v; msgs->flagged_usor += (v) ? 1L : -1L; } if((f & MN_EXLD) && pelt->excluded != v){ pelt->excluded = v; msgs->flagged_exld += (v) ? 1L : -1L; } if((f & MN_SLCT) && pelt->selected != v){ pelt->selected = v; msgs->flagged_tmp += (v) ? 1L : -1L; } if((f & MN_SRCH) && pelt->searched != v){ pelt->searched = v; msgs->flagged_srch += (v) ? 1L : -1L; } if((f & MN_STMP) && pelt->tmp != v){ pelt->tmp = v; msgs->flagged_stmp += (v) ? 1L : -1L; } is_invisible = (pelt->hidden || pelt->colhid) ? 1 : 0; if(was_invisible != is_invisible) msgs->flagged_invisible += (v) ? 1L : -1L; /* * visible_threads keeps track of how many of the max_thrdno threads * are visible and how many are MN_HIDE-hidden. */ if(chk_thrd_cnt && topthrd && (was_hidden != (is_hidden = pelt->hidden ? 1 : 0))){ if(!thrd_was_visible && !is_hidden){ /* it is visible now, increase count by one */ msgs->visible_threads++; } else if(thrd_was_visible && is_hidden){ /* thread may have been hidden, check */ if(!thread_has_some_visible(stream, topthrd)) msgs->visible_threads--; } /* else no change */ } } return(1); } /* * Copy value of flag from to flag to. */ void copy_lflags(MAILSTREAM *stream, MSGNO_S *msgmap, int from, int to) { unsigned long i; int hide; hide = ((to == MN_SLCT) && (any_lflagged(msgmap, MN_HIDE) > 0L)); set_lflags(stream, msgmap, to, 0); if(any_lflagged(msgmap, from)){ for(i = 1L; i <= mn_get_total(msgmap); i++) if(get_lflag(stream, msgmap, i, from)) set_lflag(stream, msgmap, i, to, 1); else if(hide) set_lflag(stream, msgmap, i, MN_HIDE, 1); } } /* * Set flag f to value v in all message. */ void set_lflags(MAILSTREAM *stream, MSGNO_S *msgmap, int f, int v) { unsigned long i; if((v == 0 && any_lflagged(msgmap, f)) || v ) for(i = 1L; i <= mn_get_total(msgmap); i++) set_lflag(stream, msgmap, i, f, v); } /*---------------------------------------------------------------------- return whether the given flag is set somewhere in the folder Accepts: msgs - pointer to message manipulation struct f - flag bitmap to act on Returns: number of messages with the given flag set. NOTE: the sum, if multiple flags tested, is bogus ----*/ long any_lflagged(MSGNO_S *msgs, int f) { if(!msgs) return(0L); if(f == MN_NONE) return(!(msgs->flagged_hid || msgs->flagged_exld || msgs->flagged_tmp || msgs->flagged_coll || msgs->flagged_chid || msgs->flagged_srch)); else if(f == (MN_HIDE | MN_CHID)) return(msgs->flagged_invisible); /* special non-bogus case */ else return(((f & MN_HIDE) ? msgs->flagged_hid : 0L) + ((f & MN_EXLD) ? msgs->flagged_exld : 0L) + ((f & MN_SLCT) ? msgs->flagged_tmp : 0L) + ((f & MN_SRCH) ? msgs->flagged_srch : 0L) + ((f & MN_STMP) ? msgs->flagged_stmp : 0L) + ((f & MN_COLL) ? msgs->flagged_coll : 0L) + ((f & MN_USOR) ? msgs->flagged_usor : 0L) + ((f & MN_CHID) ? msgs->flagged_chid : 0L) + ((f & MN_CHID2) ? msgs->flagged_chid2 : 0L)); }