diff options
author | Eduardo Chappa <echappa@gmx.com> | 2013-02-03 00:59:38 -0700 |
---|---|---|
committer | Eduardo Chappa <echappa@gmx.com> | 2013-02-03 00:59:38 -0700 |
commit | 094ca96844842928810f14844413109fc6cdd890 (patch) | |
tree | e60efbb980f38ba9308ccb4fb2b77b87bbc115f3 /pith/folder.c | |
download | alpine-094ca96844842928810f14844413109fc6cdd890.tar.xz |
Initial Alpine Version
Diffstat (limited to 'pith/folder.c')
-rw-r--r-- | pith/folder.c | 2759 |
1 files changed, 2759 insertions, 0 deletions
diff --git a/pith/folder.c b/pith/folder.c new file mode 100644 index 00000000..d07604a0 --- /dev/null +++ b/pith/folder.c @@ -0,0 +1,2759 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: folder.c 1142 2008-08-13 17:22:21Z hubert@u.washington.edu $"; +#endif + +/* + * ======================================================================== + * Copyright 2006-2008 University of Washington + * Copyright 2013 Eduardo Chappa + * + * 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 + * + * ======================================================================== + */ + +#include "../pith/headers.h" +#include "../c-client/utf8aux.h" +#include "../pith/folder.h" +#include "../pith/state.h" +#include "../pith/context.h" +#include "../pith/init.h" +#include "../pith/conf.h" +#include "../pith/stream.h" +#include "../pith/imap.h" +#include "../pith/util.h" +#include "../pith/flag.h" +#include "../pith/status.h" +#include "../pith/busy.h" +#include "../pith/mailindx.h" + + +typedef struct _build_folder_list_data { + long mask; /* bitmap of responses to ignore */ + LISTARGS_S args; + LISTRES_S response; + int is_move_folder; + FLIST *list; +} BFL_DATA_S; + + +#define FCHUNK 64 + + +/* + * Internal prototypes + */ +void mail_list_exists(MAILSTREAM *, char *, int, long, void *, unsigned); +void init_incoming_folder_list(struct pine *, CONTEXT_S *); +void mail_list_filter(MAILSTREAM *, char *, int, long, void *, unsigned); +void mail_lsub_filter(MAILSTREAM *, char *, int, long, void *, unsigned); +int mail_list_in_collection(char **, char *, char *, char *); +char *folder_last_cmpnt(char *, int); +void free_folder_entries(FLIST **); +int folder_insert_sorted(int, int, int, FOLDER_S *, FLIST *, + int (*)(FOLDER_S *, FOLDER_S *)); +void folder_insert_index(FOLDER_S *, int, FLIST *); +void resort_folder_list(FLIST *flist); +int compare_folders_alpha_qsort(const qsort_t *a1, const qsort_t *a2); +int compare_folders_dir_alpha_qsort(const qsort_t *a1, const qsort_t *a2); +int compare_folders_alpha_dir_qsort(const qsort_t *a1, const qsort_t *a2); +int compare_names(const qsort_t *, const qsort_t *); +void init_incoming_unseen_data(struct pine *, FOLDER_S *f); + + +char * +folder_lister_desc(CONTEXT_S *cntxt, FDIR_S *fdp) +{ + char *p, *q; + unsigned char *fname; + + q = ((p = strstr(cntxt->context, "%s")) && !*(p+2) + && !strncmp(fdp->ref, cntxt->context, p - cntxt->context)) + ? fdp->ref + (p - cntxt->context) : fdp->ref; + fname = folder_name_decoded((unsigned char *) q); + /* Provide context in new collection header */ + snprintf(tmp_20k_buf, SIZEOF_20KBUF, "Dir: %s", fname ? (char *) fname : q); + if(fname) fs_give((void **)&fname); + + return(cpystr(tmp_20k_buf)); +} + + +void +reset_context_folders(CONTEXT_S *cntxt) +{ + CONTEXT_S *tc; + + for(tc = cntxt; tc && tc->prev ; tc = tc->prev) + ; /* start at beginning */ + + for( ; tc ; tc = tc->next){ + free_folder_list(tc); + + while(tc->dir->prev){ + FDIR_S *tp = tc->dir->prev; + free_fdir(&tc->dir, 0); + tc->dir = tp; + } + } +} + + +/* + * next_folder_dir - return a directory structure with the folders it + * contains + */ +FDIR_S * +next_folder_dir(CONTEXT_S *context, char *new_dir, int build_list, MAILSTREAM **streamp) +{ + char tmp[MAILTMPLEN], dir[3]; + FDIR_S *tmp_fp, *fp; + + fp = (FDIR_S *) fs_get(sizeof(FDIR_S)); + memset(fp, 0, sizeof(FDIR_S)); + (void) context_apply(tmp, context, new_dir, MAILTMPLEN); + dir[0] = context->dir->delim; + dir[1] = '\0'; + strncat(tmp, dir, sizeof(tmp)-1-strlen(tmp)); + fp->ref = cpystr(tmp); + fp->delim = context->dir->delim; + fp->view.internal = cpystr(NEWS_TEST(context) ? "*" : "%"); + fp->folders = init_folder_entries(); + fp->status = CNTXT_NOFIND; + tmp_fp = context->dir; /* temporarily rebind */ + context->dir = fp; + + if(build_list) + build_folder_list(streamp, context, NULL, NULL, + NEWS_TEST(context) ? BFL_LSUB : BFL_NONE); + + context->dir = tmp_fp; + return(fp); +} + + +/* + * Return which pinerc incoming folder #index is in. + */ +EditWhich +config_containing_inc_fldr(FOLDER_S *folder) +{ + char **t; + int i, keep_going = 1, inheriting = 0; + struct variable *v = &ps_global->vars[V_INCOMING_FOLDERS]; + + if(v->is_fixed) + return(None); + + /* is it in exceptions config? */ + if(v->post_user_val.l){ + for(i = 0, t=v->post_user_val.l; t[i]; i++){ + if(expand_variables(tmp_20k_buf, SIZEOF_20KBUF, t[i], 0)){ + keep_going = 0; + + if(!strcmp(tmp_20k_buf, INHERIT)) + inheriting = 1; + else if(folder->varhash == line_hash(tmp_20k_buf)) + return(Post); + } + } + } + + if(inheriting) + keep_going = 1; + + /* is it in main config? */ + if(keep_going && v->main_user_val.l){ + for(i = 0, t=v->main_user_val.l; t[i]; i++){ + if(expand_variables(tmp_20k_buf, SIZEOF_20KBUF, t[i], 0) && + folder->varhash == line_hash(tmp_20k_buf)) + return(Main); + } + } + + return(None); +} + + +/*---------------------------------------------------------------------- + Format the given folder name for display for the user + + Args: folder -- The folder name to fix up + +Not sure this always makes it prettier. It could do nice truncation if we +passed in a length. Right now it adds the path name of the mail +subdirectory if appropriate. + ----*/ + +char * +pretty_fn(char *folder) +{ + if(!strucmp(folder, ps_global->inbox_name)) + return(ps_global->inbox_name); + else + return(folder); +} + + +/*---------------------------------------------------------------------- + Return the path delimiter for the given folder on the given server + + Args: folder -- folder type for delimiter + + ----*/ +int +get_folder_delimiter(char *folder) +{ + MM_LIST_S ldata; + LISTRES_S response; + int ourstream = 0; + + memset(mm_list_info = &ldata, 0, sizeof(MM_LIST_S)); + ldata.filter = mail_list_response; + memset(ldata.data = &response, 0, sizeof(LISTRES_S)); + + if(*folder == '{' + && !(ldata.stream = sp_stream_get(folder, SP_MATCH)) + && !(ldata.stream = sp_stream_get(folder, SP_SAME))){ + if((ldata.stream = pine_mail_open(NULL,folder, + OP_HALFOPEN|OP_SILENT|SP_USEPOOL|SP_TEMPUSE, + NULL)) != NULL){ + ourstream++; + } + else{ + return(FEX_ERROR); + } + } + + pine_mail_list(ldata.stream, folder, "", NULL); + + if(ourstream) + pine_mail_close(ldata.stream); + + return(response.delim); +} + + +/*---------------------------------------------------------------------- + Check to see if folder exists in given context + + Args: cntxt -- context inwhich to interpret "file" arg + file -- name of folder to check + + Result: returns FEX_ISFILE if the folder exists and is a folder + FEX_ISDIR if the folder exists and is a directory + FEX_NOENT if it doesn't exist + FEX_ERROR on error + + The two existence return values above may be logically OR'd + + Uses mail_list to sniff out the existence of the requested folder. + The context string is just here for convenience. Checking for + folder's existence within a given context is probably more efficiently + handled outside this function for now using build_folder_list(). + + ----*/ +int +folder_exists(CONTEXT_S *cntxt, char *file) +{ + return(folder_name_exists(cntxt, file, NULL)); +} + + +/*---------------------------------------------------------------------- + Check to see if folder exists in given context + + Args: cntxt -- context in which to interpret "file" arg + file -- name of folder to check + name -- name of folder folder with context applied + + Result: returns FEX_ISFILE if the folder exists and is a folder + FEX_ISDIR if the folder exists and is a directory + FEX_NOENT if it doesn't exist + FEX_ERROR on error + + The two existence return values above may be logically OR'd + + Uses mail_list to sniff out the existence of the requested folder. + The context string is just here for convenience. Checking for + folder's existence within a given context is probably more efficiently + handled outside this function for now using build_folder_list(). + + ----*/ +int +folder_name_exists(CONTEXT_S *cntxt, char *file, char **fullpath) +{ + MM_LIST_S ldata; + EXISTDATA_S parms; + int we_cancel = 0, res; + char *p, reference[MAILTMPLEN], tmp[MAILTMPLEN], *tfolder = NULL; + + /* + * No folder means "inbox". + */ + if(*file == '{' && (p = strchr(file, '}')) && (!*(p+1))){ + size_t l; + + l = strlen(file)+strlen("inbox"); + tfolder = (char *) fs_get((l+1) * sizeof(char)); + snprintf(tfolder, l+1, "%s%s", file, "inbox"); + file = tfolder; + cntxt = NULL; + } + + mm_list_info = &ldata; /* tie down global reference */ + memset(&ldata, 0, sizeof(ldata)); + ldata.filter = mail_list_exists; + + ldata.stream = sp_stream_get(context_apply(tmp, cntxt, file, sizeof(tmp)), + SP_SAME); + + memset(ldata.data = &parms, 0, sizeof(EXISTDATA_S)); + + /* + * If no preset reference string, must be at top of context + */ + if(cntxt && context_isambig(file)){ + /* inbox in first context is the real inbox */ + if(ps_global->context_list == cntxt && !strucmp(file, ps_global->inbox_name)){ + reference[0] = '\0'; + parms.args.reference = reference; + } + else if(!(parms.args.reference = cntxt->dir->ref)){ + char *p; + + if((p = strstr(cntxt->context, "%s")) != NULL){ + strncpy(parms.args.reference = reference, + cntxt->context, + MIN(p - cntxt->context, sizeof(reference)-1)); + reference[MIN(p - cntxt->context, sizeof(reference)-1)] = '\0'; + if(*(p += 2)) + parms.args.tail = p; + } + else + parms.args.reference = cntxt->context; + } + + parms.fullname = fullpath; + } + + ps_global->mm_log_error = 0; + ps_global->noshow_error = 1; + + we_cancel = busy_cue(NULL, NULL, 1); + + parms.args.name = file; + + res = pine_mail_list(ldata.stream, parms.args.reference, parms.args.name, + &ldata.options); + + if(we_cancel) + cancel_busy_cue(-1); + + ps_global->noshow_error = 0; + + if(cntxt && cntxt->dir && parms.response.delim) + cntxt->dir->delim = parms.response.delim; + + if(tfolder) + fs_give((void **)&tfolder); + return(((res == FALSE) || ps_global->mm_log_error) + ? FEX_ERROR + : (((parms.response.isfile) + ? FEX_ISFILE : 0 ) + | ((parms.response.isdir) + ? FEX_ISDIR : 0) + | ((parms.response.ismarked) + ? FEX_ISMARKED : 0) + | ((parms.response.unmarked) + ? FEX_UNMARKED : 0))); +} + + +void +mail_list_exists(MAILSTREAM *stream, char *mailbox, int delim, long int attribs, + void *data, unsigned int options) +{ + if(delim) + ((EXISTDATA_S *) data)->response.delim = delim; + + if(mailbox && *mailbox){ + if(!(attribs & LATT_NOSELECT)){ + ((EXISTDATA_S *) data)->response.isfile = 1; + ((EXISTDATA_S *) data)->response.count += 1; + } + + if(!(attribs & LATT_NOINFERIORS)){ + ((EXISTDATA_S *) data)->response.isdir = 1; + ((EXISTDATA_S *) data)->response.count += 1; + } + + if(attribs & LATT_MARKED) + ((EXISTDATA_S *) data)->response.ismarked = 1; + + /* don't mark #move folders unmarked */ + if(attribs & LATT_UNMARKED && !(options & PML_IS_MOVE_MBOX)) + ((EXISTDATA_S *) data)->response.unmarked = 1; + + if(attribs & LATT_HASCHILDREN) + ((EXISTDATA_S *) data)->response.haschildren = 1; + + if(attribs & LATT_HASNOCHILDREN) + ((EXISTDATA_S *) data)->response.hasnochildren = 1; + + if(((EXISTDATA_S *) data)->fullname + && ((((EXISTDATA_S *) data)->args.reference[0] != '\0') + ? struncmp(((EXISTDATA_S *) data)->args.reference, mailbox, + strlen(((EXISTDATA_S *)data)->args.reference)) + : struncmp(((EXISTDATA_S *) data)->args.name, mailbox, + strlen(((EXISTDATA_S *) data)->args.name)))){ + char *p; + size_t alloclen; + size_t len = (((stream && stream->mailbox) + ? strlen(stream->mailbox) : 0) + + strlen(((EXISTDATA_S *) data)->args.reference) + + strlen(((EXISTDATA_S *) data)->args.name) + + strlen(mailbox)) * sizeof(char); + + /* + * Fully qualify (in the c-client name structure sense) + * anything that's not in the context of the "reference"... + */ + if(*((EXISTDATA_S *) data)->fullname) + fs_give((void **) ((EXISTDATA_S *) data)->fullname); + + alloclen = len; + *((EXISTDATA_S *) data)->fullname = (char *) fs_get(alloclen); + if(*mailbox != '{' + && stream && stream->mailbox && *stream->mailbox == '{' + && (p = strindex(stream->mailbox, '}'))){ + len = (p - stream->mailbox) + 1; + strncpy(*((EXISTDATA_S *) data)->fullname, + stream->mailbox, MIN(len,alloclen)); + p = *((EXISTDATA_S *) data)->fullname + len; + } + else + p = *((EXISTDATA_S *) data)->fullname; + + strncpy(p, mailbox, alloclen-(p-(*((EXISTDATA_S *) data)->fullname))); + (*((EXISTDATA_S *) data)->fullname)[alloclen-1] = '\0';; + } + } +} + + +void +mail_list_response(MAILSTREAM *stream, char *mailbox, int delim, long int attribs, + void *data, unsigned int options) +{ + int counted = 0; + + if(delim) + ((LISTRES_S *) data)->delim = delim; + + if(mailbox && *mailbox){ + if(!(attribs & LATT_NOSELECT)){ + counted++; + ((LISTRES_S *) data)->isfile = 1; + ((LISTRES_S *) data)->count += 1; + } + + if(!(attribs & LATT_NOINFERIORS)){ + ((LISTRES_S *) data)->isdir = 1; + + if(!counted) + ((LISTRES_S *) data)->count += 1; + } + + if(attribs & LATT_HASCHILDREN) + ((LISTRES_S *) data)->haschildren = 1; + + if(attribs & LATT_HASNOCHILDREN) + ((LISTRES_S *) data)->hasnochildren = 1; + } +} + + +char * +folder_as_breakout(CONTEXT_S *cntxt, char *name) +{ + if(context_isambig(name)){ /* if simple check doesn't pan out */ + char tmp[2*MAILTMPLEN], *p, *f; /* look harder */ + + if(!cntxt->dir->delim){ + (void) context_apply(tmp, cntxt, "", sizeof(tmp)/2); + cntxt->dir->delim = get_folder_delimiter(tmp); + } + + if((p = strindex(name, cntxt->dir->delim)) != NULL){ + if(p == name){ /* assumption 6,321: delim is root */ + if(cntxt->context[0] == '{' + && (p = strindex(cntxt->context, '}'))){ + strncpy(tmp, cntxt->context, + MIN((p - cntxt->context) + 1, sizeof(tmp)/2)); + tmp[MIN((p - cntxt->context) + 1, sizeof(tmp)/2)] = '\0'; + strncpy(&tmp[MIN((p - cntxt->context) + 1, sizeof(tmp)/2)], + name, sizeof(tmp)/2-strlen(tmp)); + tmp[sizeof(tmp)-1] = '\0'; + return(cpystr(tmp)); + } + else + return(cpystr(name)); + } + else{ /* assumption 6,322: no create ~foo */ + strncpy(tmp, name, MIN(p - name, MAILTMPLEN)); + /* lop off trailingpath */ + tmp[MIN(p - name, sizeof(tmp)/2)] = '\0'; + f = NULL; + (void)folder_name_exists(cntxt, tmp, &f); + if(f){ + snprintf(tmp, sizeof(tmp), "%s%s",f,p); + tmp[sizeof(tmp)-1] = '\0'; + fs_give((void **) &f); + return(cpystr(tmp)); + } + } + } + } + + return(NULL); +} + + +/*---------------------------------------------------------------------- + Initialize global list of contexts for folder collections. + + Interprets collections defined in the pinerc and orders them for + pine's use. Parses user-provided context labels and sets appropriate + use flags and the default prototype for that collection. + (See find_folders for how the actual folder list is found). + + ----*/ +void +init_folders(struct pine *ps) +{ + CONTEXT_S *tc, *top = NULL, **clist; + int i, prime = 0; + + clist = ⊤ + + /* + * If no incoming folders are config'd, but the user asked for + * them via feature, make sure at least "inbox" ends up there... + */ + if(F_ON(F_ENABLE_INCOMING, ps) && !ps->VAR_INCOMING_FOLDERS){ + ps->VAR_INCOMING_FOLDERS = (char **)fs_get(2 * sizeof(char *)); + ps->VAR_INCOMING_FOLDERS[0] = cpystr(ps->inbox_name); + ps->VAR_INCOMING_FOLDERS[1] = NULL; + } + + /* + * Build context that's a list of folders the user's defined + * as receiveing new messages. At some point, this should + * probably include adding a prefix with the new message count. + * fake new context... + */ + if(ps->VAR_INCOMING_FOLDERS && ps->VAR_INCOMING_FOLDERS[0] + && (tc = new_context("Incoming-Folders []", NULL))){ + tc->dir->status &= ~CNTXT_NOFIND; + tc->use |= CNTXT_INCMNG; /* mark this as incoming collection */ + if(tc->label) + fs_give((void **) &tc->label); + + /* TRANSLATORS: a label */ + tc->label = cpystr(_("Incoming Message Folders")); + + *clist = tc; + clist = &tc->next; + + init_incoming_folder_list(ps, tc); + } + + /* + * Build list of folder collections. Because of the way init.c + * works, we're guaranteed at least a default. Also write any + * "bogus format" messages... + */ + for(i = 0; ps->VAR_FOLDER_SPEC && ps->VAR_FOLDER_SPEC[i] ; i++) + if((tc = new_context(ps->VAR_FOLDER_SPEC[i], &prime)) != NULL){ + *clist = tc; /* add it to list */ + clist = &tc->next; /* prepare for next */ + tc->var.v = &ps->vars[V_FOLDER_SPEC]; + tc->var.i = i; + } + + + /* + * Whoah cowboy!!! Guess we couldn't find a valid folder + * collection??? + */ + if(!prime) + panic(_("No folder collections defined")); + + /* + * At this point, insert the INBOX mapping as the leading + * folder entry of the first collection... + */ + init_inbox_mapping(ps->VAR_INBOX_PATH, top); + + set_news_spec_current_val(TRUE, TRUE); + + /* + * If news groups, loop thru list adding to collection list + */ + for(i = 0; ps->VAR_NEWS_SPEC && ps->VAR_NEWS_SPEC[i] ; i++) + if(ps->VAR_NEWS_SPEC[i][0] + && (tc = new_context(ps->VAR_NEWS_SPEC[i], NULL))){ + *clist = tc; /* add it to list */ + clist = &tc->next; /* prepare for next */ + tc->var.v = &ps->vars[V_NEWS_SPEC]; + tc->var.i = i; + } + + ps->context_list = top; /* init pointers */ + ps->context_current = (top->use & CNTXT_INCMNG) ? top->next : top; + ps->context_last = NULL; + /* Tie up all the previous pointers */ + for(; top; top = top->next) + if(top->next) + top->next->prev = top; + +#ifdef DEBUG + dump_contexts(); +#endif +} + + +/* + * Add incoming list of folders to context. + */ +void +init_incoming_folder_list(struct pine *ps, CONTEXT_S *cntxt) +{ + int i; + char *folder_string, *nickname; + FOLDER_S *f; + + for(i = 0; ps->VAR_INCOMING_FOLDERS[i] ; i++){ + /* + * Parse folder line for nickname and folder name. + * No nickname on line is OK. + */ + get_pair(ps->VAR_INCOMING_FOLDERS[i], &nickname, &folder_string,0,0); + + /* + * Allow for inbox to be specified in the incoming list, but + * don't let it show up along side the one magically inserted + * above! + */ + if(!folder_string || !strucmp(ps->inbox_name, folder_string)){ + if(folder_string) + fs_give((void **)&folder_string); + + if(nickname) + fs_give((void **)&nickname); + + continue; + } + else if(update_bboard_spec(folder_string, tmp_20k_buf, SIZEOF_20KBUF)){ + fs_give((void **) &folder_string); + folder_string = cpystr(tmp_20k_buf); + } + + f = new_folder(folder_string, + line_hash(ps->VAR_INCOMING_FOLDERS[i])); + f->isfolder = 1; + fs_give((void **)&folder_string); + + if(nickname){ + if(strucmp(ps->inbox_name, nickname)){ + f->nickname = nickname; + f->name_len = strlen(f->nickname); + } + else + fs_give((void **)&nickname); + } + + init_incoming_unseen_data(ps, f); + + folder_insert(f->nickname + && (strucmp(f->nickname, ps->inbox_name) == 0) + ? -1 : folder_total(FOLDERS(cntxt)), + f, FOLDERS(cntxt)); + } +} + + +void +init_incoming_unseen_data(struct pine *ps, FOLDER_S *f) +{ + int j, check_this = 0; + + if(!f) + return; + + /* see if this folder is in the monitoring list */ + if(F_ON(F_ENABLE_INCOMING_CHECKING, ps)){ + if(!ps->VAR_INCCHECKLIST) + check_this++; /* everything in by default */ + else{ + for(j = 0; !check_this && ps->VAR_INCCHECKLIST[j]; j++){ + if((f->nickname && !strucmp(ps->VAR_INCCHECKLIST[j],f->nickname)) + || (f->name && !strucmp(ps->VAR_INCCHECKLIST[j],f->name))) + check_this++; + } + } + } + + if(check_this) + f->last_unseen_update = LUU_INIT; + else + f->last_unseen_update = LUU_NEVERCHK; +} + + +void +reinit_incoming_folder_list(struct pine *ps, CONTEXT_S *context) +{ + free_folder_entries(&(FOLDERS(context))); + FOLDERS(context) = init_folder_entries(); + init_incoming_folder_list(ps_global, context); + init_inbox_mapping(ps_global->VAR_INBOX_PATH, context); +} + + +/* + * + */ +void +init_inbox_mapping(char *path, CONTEXT_S *cntxt) +{ + FOLDER_S *f; + int check_this, j; + + /* + * If mapping already exists, blast it and replace it below... + */ + if((f = folder_entry(0, FOLDERS(cntxt))) + && f->nickname && !strcmp(f->nickname, ps_global->inbox_name)) + folder_delete(0, FOLDERS(cntxt)); + + if(path){ + f = new_folder(path, 0); + f->nickname = cpystr(ps_global->inbox_name); + f->name_len = strlen(f->nickname); + } + else + f = new_folder(ps_global->inbox_name, 0); + + f->isfolder = 1; + + /* see if this folder is in the monitoring list */ + check_this = 0; + if(F_ON(F_ENABLE_INCOMING_CHECKING, ps_global) && ps_global->VAR_INCOMING_FOLDERS && ps_global->VAR_INCOMING_FOLDERS[0]){ + if(!ps_global->VAR_INCCHECKLIST) + check_this++; /* everything in by default */ + else{ + for(j = 0; !check_this && ps_global->VAR_INCCHECKLIST[j]; j++){ + if((f->nickname && !strucmp(ps_global->VAR_INCCHECKLIST[j],f->nickname)) + || (f->name && !strucmp(ps_global->VAR_INCCHECKLIST[j],f->name))) + check_this++; + } + } + } + + if(check_this) + f->last_unseen_update = LUU_INIT; + else + f->last_unseen_update = LUU_NEVERCHK; + + folder_insert(0, f, FOLDERS(cntxt)); +} + + +FDIR_S * +new_fdir(char *ref, char *view, int wildcard) +{ + FDIR_S *rv = (FDIR_S *) fs_get(sizeof(FDIR_S)); + + memset((void *) rv, 0, sizeof(FDIR_S)); + + /* Monkey with the view to make sure it has wildcard? */ + if(view && *view){ + rv->view.user = cpystr(view); + + /* + * This is sorta hairy since, for simplicity we allow + * users to use '*' in the view, but for mail + * we really mean '%' as def'd in 2060... + */ + if(wildcard == '*'){ /* must be #news. */ + if(strindex(view, wildcard)) + rv->view.internal = cpystr(view); + } + else{ /* must be mail */ + char *p; + + if((p = strpbrk(view, "*%")) != NULL){ + rv->view.internal = p = cpystr(view); + while((p = strpbrk(p, "*%")) != NULL) + *p++ = '%'; /* convert everything to '%' */ + } + } + + if(!rv->view.internal){ + size_t l; + + l = strlen(view)+2; + rv->view.internal = (char *) fs_get((l+1) * sizeof(char)); + snprintf(rv->view.internal, l+1, "%c%s%c", wildcard, view, wildcard); + } + } + else{ + rv->view.internal = (char *) fs_get(2 * sizeof(char)); + snprintf(rv->view.internal, 2, "%c", wildcard); + } + + if(ref) + rv->ref = ref; + + rv->folders = init_folder_entries(); + rv->status = CNTXT_NOFIND; + return(rv); +} + + +void +free_fdir(FDIR_S **f, int recur) +{ + if(f && *f){ + if((*f)->prev && recur) + free_fdir(&(*f)->prev, 1); + + if((*f)->ref) + fs_give((void **)&(*f)->ref); + + if((*f)->view.user) + fs_give((void **)&(*f)->view.user); + + if((*f)->view.internal) + fs_give((void **)&(*f)->view.internal); + + if((*f)->desc) + fs_give((void **)&(*f)->desc); + + free_folder_entries(&(*f)->folders); + fs_give((void **) f); + } +} + + +int +update_bboard_spec(char *bboard, char *buf, size_t buflen) +{ + char *p = NULL, *origbuf; + int nntp = 0, bracket = 0; + + origbuf = buf; + + if(*bboard == '*' + || (*bboard == '{' && (p = strindex(bboard, '}')) && *(p+1) == '*')){ + /* server name ? */ + if(p || (*(bboard+1) == '{' && (p = strindex(++bboard, '}')))) + while(bboard <= p && (buf-origbuf < buflen)) /* copy it */ + if((*buf++ = *bboard++) == '/' && !strncmp(bboard, "nntp", 4)) + nntp++; + + if(*bboard == '*') + bboard++; + + if(!nntp) + /* + * See if path portion looks newsgroup-ish while being aware + * of the "view" portion of the spec... + */ + for(p = bboard; *p; p++) + if(*p == '['){ + if(bracket) /* only one set allowed! */ + break; + else + bracket++; + } + else if(*p == ']'){ + if(bracket != 1) /* must be closing bracket */ + break; + else + bracket++; + } + else if(!(isalnum((unsigned char) *p) || strindex(".-", *p))) + break; + + snprintf(buf, buflen-(buf-origbuf), "%s%s%s", + (!nntp && *p) ? "#public" : "#news.", + (!nntp && *p && *bboard != '/') ? "/" : "", + bboard); + + return(1); + } + + return(0); +} + + +/* + * build_folder_list - call mail_list to fetch us a list of folders + * from the given context. + */ +void +build_folder_list(MAILSTREAM **stream, CONTEXT_S *context, char *pat, char *content, int flags) +{ + MM_LIST_S ldata; + BFL_DATA_S response; + int local_open = 0, we_cancel = 0, resort = 0; + char reference[2*MAILTMPLEN], *p; + + if(!(context->dir->status & CNTXT_NOFIND) + || (context->dir->status & CNTXT_PARTFIND)) + return; /* find already done! */ + + dprint((7, "build_folder_list: %s %s\n", + context ? context->context : "NULL", + pat ? pat : "NULL")); + + we_cancel = busy_cue(NULL, NULL, 1); + + /* + * Set up the pattern of folder name's to match within the + * given context. + */ + if(!pat || ((*pat == '*' || *pat == '%') && *(pat+1) == '\0')){ + context->dir->status &= ~CNTXT_NOFIND; /* let'em know we tried */ + pat = context->dir->view.internal; + } + else + context->use |= CNTXT_PARTFIND; /* or are in a partial find */ + + memset(mm_list_info = &ldata, 0, sizeof(MM_LIST_S)); + ldata.filter = (NEWS_TEST(context)) ? mail_lsub_filter : mail_list_filter; + memset(ldata.data = &response, 0, sizeof(BFL_DATA_S)); + response.list = FOLDERS(context); + + if(flags & BFL_FLDRONLY) + response.mask = LATT_NOSELECT; + + /* + * if context is associated with a server, prepare a stream for + * sending our request. + */ + if(*context->context == '{'){ + /* + * Try using a stream we've already got open... + */ + if(stream && *stream + && !(ldata.stream = same_stream(context->context, *stream))){ + pine_mail_close(*stream); + *stream = NULL; + } + + if(!ldata.stream) + ldata.stream = sp_stream_get(context->context, SP_MATCH); + + if(!ldata.stream) + ldata.stream = sp_stream_get(context->context, SP_SAME); + + /* gotta open a new one? */ + if(!ldata.stream){ + ldata.stream = mail_cmd_stream(context, &local_open); + if(stream) + *stream = ldata.stream; + } + + dprint((ldata.stream ? 7 : 1, "build_folder_list: mail_open(%s) %s.\n", + context->server ? context->server : "?", + ldata.stream ? "OK" : "FAILED")); + + if(!ldata.stream){ + context->use &= ~CNTXT_PARTFIND; /* unset partial find bit */ + if(we_cancel) + cancel_busy_cue(-1); + + return; + } + } + else if(stream && *stream){ /* no server, simple case */ + if(!sp_flagged(*stream, SP_LOCKED)) + pine_mail_close(*stream); + + *stream = NULL; + } + + /* + * If preset reference string, we're somewhere in the hierarchy. + * ELSE we must be at top of context... + */ + response.args.name = pat; + if(!(response.args.reference = context->dir->ref)){ + if((p = strstr(context->context, "%s")) != NULL){ + strncpy(response.args.reference = reference, + context->context, + MIN(p - context->context, sizeof(reference)/2)); + reference[MIN(p - context->context, sizeof(reference)/2)] = '\0'; + if(*(p += 2)) + response.args.tail = p; + } + else + response.args.reference = context->context; + } + + if(flags & BFL_SCAN) + mail_scan(ldata.stream, response.args.reference, + response.args.name, content); + else if(flags & BFL_LSUB) + mail_lsub(ldata.stream, response.args.reference, response.args.name); + else{ + set_read_predicted(1); + pine_mail_list(ldata.stream, response.args.reference, response.args.name, + &ldata.options); + set_read_predicted(0); + } + + if(context->dir && response.response.delim) + context->dir->delim = response.response.delim; + + if(!(flags & (BFL_LSUB|BFL_SCAN)) && F_ON(F_QUELL_EMPTY_DIRS, ps_global)){ + LISTRES_S listres; + FOLDER_S *f; + int i; + + for(i = 0; i < folder_total(response.list); i++) + if((f = folder_entry(i, FOLDERS(context)))->isdir){ + /* + * we don't need to do a list if we know already + * whether there are children or not. + */ + if(!f->haschildren && !f->hasnochildren){ + memset(ldata.data = &listres, 0, sizeof(LISTRES_S)); + ldata.filter = mail_list_response; + + if(context->dir->ref) + snprintf(reference, sizeof(reference), "%s%s", context->dir->ref, f->name); + else + context_apply(reference, context, f->name, sizeof(reference)/2); + + /* append the delimiter to the reference */ + for(p = reference; *p; p++) + ; + + *p++ = context->dir->delim; + *p = '\0'; + + pine_mail_list(ldata.stream, reference, "%", NULL); + + /* anything interesting inside? */ + f->hasnochildren = (listres.count <= 1L); + f->haschildren = !f->hasnochildren;; + } + + if(f->hasnochildren){ + if(f->isfolder){ + f->isdir = 0; + if(ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_FIRST + || ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_LAST) + resort = 1; + } + /* + * We don't want to hide directories + * that are only directories even if they are + * empty. We only want to hide the directory + * piece of a dual-use folder when there are + * no children in the directory (and the user + * most likely thinks of it as a folder instead + * of a folder and a directory). + */ + else if(f->isdir && f->isdual){ + folder_delete(i, FOLDERS(context)); + i--; + } + } + } + } + + if(resort) + resort_folder_list(response.list); + + if(local_open && !stream) + pine_mail_close(ldata.stream); + + if(context->use & CNTXT_PRESRV) + folder_select_restore(context); + + context->use &= ~CNTXT_PARTFIND; /* unset partial list bit */ + if(we_cancel) + cancel_busy_cue(-1); +} + + +/* + * Validate LIST response to command issued from build_folder_list + * + */ +void +mail_list_filter(MAILSTREAM *stream, char *mailbox, int delim, long int attribs, void *data, unsigned int options) +{ + BFL_DATA_S *ld = (BFL_DATA_S *) data; + FOLDER_S *new_f = NULL, *dual_f = NULL; + int suppress_folder_add = 0; + + if(!ld) + return; + + if(delim) + ld->response.delim = delim; + + /* test against mask of DIS-allowed attributes */ + if((ld->mask & attribs)){ + dprint((3, "mail_list_filter: failed attribute test")); + return; + } + + /* + * First, make sure response fits our "reference" arg + * NOTE: build_folder_list can't supply breakout? + */ + if(!mail_list_in_collection(&mailbox, ld->args.reference, + ld->args.name, ld->args.tail)) + return; + + /* ignore dotfolders unless told not to */ + if(F_OFF(F_ENABLE_DOT_FOLDERS, ps_global) && *mailbox == '.'){ + dprint((3, "mail_list_filter: dotfolder disallowed")); + return; + } + + /* + * If this response is INBOX we have to handle it specially. + * The special cases are: + * + * Incoming folders are enabled and this INBOX is in the Incoming + * folders list already. We don't want to list it here, as well, + * unless it is also a directory. + * + * Incoming folders are not enabled, but we inserted INBOX into + * this primary collection (with init_inbox_mapping()) so it is + * already accounted for unless it is also a directory. + * + * Incoming folders are not enabled, but we inserted INBOX into + * the primary collection (which we are not looking at here) so + * it is already accounted for there. We'll add it as a directory + * as well here if that is what is called for. + */ + if(!strucmp(mailbox, ps_global->inbox_name)){ + int ftotal, i; + FOLDER_S *f; + char fullname[1000], tmp1[1000], tmp2[1000], *l1, *l2; + + /* fullname is the name of the mailbox in the list response */ + if(ld->args.reference){ + strncpy(fullname, ld->args.reference, sizeof(fullname)-1); + fullname[sizeof(fullname)-1] = '\0'; + } + else + fullname[0] = '\0'; + + strncat(fullname, mailbox, sizeof(fullname)-strlen(fullname)-1); + fullname[sizeof(fullname)-1] = '\0'; + + /* check if Incoming Folders are enabled */ + if(ps_global->context_list && ps_global->context_list->use & CNTXT_INCMNG){ + int this_inbox_is_in_incoming = 0; + + /* + * Figure out if this INBOX is already in the Incoming list. + */ + + /* compare fullname to each incoming folder */ + ftotal = folder_total(FOLDERS(ps_global->context_list)); + for(i = 0; i < ftotal && !this_inbox_is_in_incoming; i++){ + f = folder_entry(i, FOLDERS(ps_global->context_list)); + if(f && f->name){ + if(same_remote_mailboxes(fullname, f->name) + || ((!IS_REMOTE(fullname) && !IS_REMOTE(f->name)) + && (l1=mailboxfile(tmp1,fullname)) + && (l2=mailboxfile(tmp2,f->name)) + && !strcmp(l1,l2))) + this_inbox_is_in_incoming++; + } + } + + if(this_inbox_is_in_incoming){ + /* + * Don't add a folder for this, only a directory if called for. + * If it isn't a directory, skip it. + */ + if(!(delim && !(attribs & LATT_NOINFERIORS))) + return; + + suppress_folder_add++; + } + } + else{ + int inbox_is_in_this_collection = 0; + + /* is INBOX already in this folder list? */ + ftotal = folder_total(ld->list); + for(i = 0; i < ftotal && !inbox_is_in_this_collection; i++){ + f = folder_entry(i, ld->list); + if(!strucmp(FLDR_NAME(f), ps_global->inbox_name)) + inbox_is_in_this_collection++; + } + + if(inbox_is_in_this_collection){ + + /* + * Inbox is already inserted in this collection. Unless + * it is also a directory, we are done. + */ + if(!(delim && !(attribs & LATT_NOINFERIORS))) + return; + + /* + * Since it is also a directory, what we do depends on + * the F_SEP feature. If that feature is not set we just + * want to mark the existing entry dual-use. If F_SEP is + * set we want to add a new directory entry. + */ + if(F_ON(F_SEPARATE_FLDR_AS_DIR, ps_global)){ + f->isdir = 0; + f->haschildren = 0; + f->hasnochildren = 0; + /* fall through and add a new directory */ + } + else{ + /* mark existing entry dual-use and return */ + ld->response.count++; + ld->response.isdir = 1; + f->isdir = 1; + if(attribs & LATT_HASCHILDREN) + f->haschildren = 1; + + if(attribs & LATT_HASNOCHILDREN) + f->hasnochildren = 1; + + return; + } + + suppress_folder_add++; + } + else{ + int found_it = 0; + int this_inbox_is_primary_inbox = 0; + + /* + * See if this INBOX is the same as the INBOX we inserted + * in the primary collection. + */ + + /* first find the existing INBOX entry */ + ftotal = folder_total(FOLDERS(ps_global->context_list)); + for(i = 0; i < ftotal && !found_it; i++){ + f = folder_entry(i, FOLDERS(ps_global->context_list)); + if(!strucmp(FLDR_NAME(f), ps_global->inbox_name)) + found_it++; + } + + if(found_it && f && f->name){ + if(same_remote_mailboxes(fullname, f->name) + || ((!IS_REMOTE(fullname) && !IS_REMOTE(f->name)) + && (l1=mailboxfile(tmp1,fullname)) + && (l2=mailboxfile(tmp2,f->name)) + && !strcmp(l1,l2))) + this_inbox_is_primary_inbox++; + } + + if(this_inbox_is_primary_inbox){ + /* + * Don't add a folder for this, only a directory if called for. + * If it isn't a directory, skip it. + */ + if(!(delim && !(attribs & LATT_NOINFERIORS))) + return; + + suppress_folder_add++; + } + } + } + } + + /* is it a mailbox? */ + if(!(attribs & LATT_NOSELECT) && !suppress_folder_add){ + ld->response.count++; + ld->response.isfile = 1; + new_f = new_folder(mailbox, 0); + new_f->isfolder = 1; + + if(F_ON(F_SEPARATE_FLDR_AS_DIR, ps_global)){ + folder_insert(-1, new_f, ld->list); + dual_f = new_f; + new_f = NULL; + } + } + + /* directory? */ + if(delim && !(attribs & LATT_NOINFERIORS)){ + ld->response.count++; + ld->response.isdir = 1; + + if(!new_f) + new_f = new_folder(mailbox, 0); + + new_f->isdir = 1; + if(attribs & LATT_HASCHILDREN) + new_f->haschildren = 1; + if(attribs & LATT_HASNOCHILDREN) + new_f->hasnochildren = 1; + + /* + * When we have F_SEPARATE_FLDR_AS_DIR we still want to know + * whether the name really represents both so that we don't + * inadvertently delete both when the user meant one or the + * other. + */ + if(dual_f){ + if(attribs & LATT_HASCHILDREN) + dual_f->haschildren = 1; + + if(attribs & LATT_HASNOCHILDREN) + dual_f->hasnochildren = 1; + + dual_f->isdual = 1; + new_f->isdual = 1; + } + } + + if(new_f) + folder_insert(-1, new_f, ld->list); + + if(attribs & LATT_MARKED) + ld->response.ismarked = 1; + + /* don't mark #move folders unmarked */ + if(attribs & LATT_UNMARKED && !(options & PML_IS_MOVE_MBOX)) + ld->response.unmarked = 1; + + if(attribs & LATT_HASCHILDREN) + ld->response.haschildren = 1; + + if(attribs & LATT_HASNOCHILDREN) + ld->response.hasnochildren = 1; +} + + +/* + * Validate LSUB response to command issued from build_folder_list + * + */ +void +mail_lsub_filter(MAILSTREAM *stream, char *mailbox, int delim, long int attribs, + void *data, unsigned int options) +{ + BFL_DATA_S *ld = (BFL_DATA_S *) data; + FOLDER_S *new_f = NULL; + char *ref; + + if(delim) + ld->response.delim = delim; + + /* test against mask of DIS-allowed attributes */ + if((ld->mask & attribs)){ + dprint((3, "mail_lsub_filter: failed attribute test")); + return; + } + + /* Normalize mailbox and reference strings re: namespace */ + if(!strncmp(mailbox, "#news.", 6)) + mailbox += 6; + + if(!strncmp(ref = ld->args.reference, "#news.", 6)) + ref += 6; + + if(!mail_list_in_collection(&mailbox, ref, ld->args.name, ld->args.tail)) + return; + + if(!(attribs & LATT_NOSELECT)){ + ld->response.count++; + ld->response.isfile = 1; + new_f = new_folder(mailbox, 0); + new_f->isfolder = 1; + folder_insert(F_ON(F_READ_IN_NEWSRC_ORDER, ps_global) + ? folder_total(ld->list) : -1, + new_f, ld->list); + } + + /* We don't support directories in #news */ +} + +/* human readable name for a folder. memory freed by caller + * This is a jacket to conversion from modified utf7 to utf8. + */ +unsigned char *folder_name_decoded(unsigned char *mailbox) +{ + unsigned char *s; + s = (unsigned char *) utf8_from_mutf7((unsigned char *) mailbox); + if (s == NULL) s = (unsigned char *) cpystr(mailbox); + return s; +} + +int +mail_list_in_collection(char **mailbox, char *ref, char *name, char *tail) +{ + int boxlen, reflen, taillen; + char *p; + + boxlen = strlen(*mailbox); + reflen = ref ? strlen(ref) : 0; + taillen = tail ? strlen(tail) : 0; + + if(boxlen + && (reflen ? !struncmp(*mailbox, ref, reflen) + : (p = strpbrk(name, "%*")) + ? !struncmp(*mailbox, name, p - name) + : !strucmp(*mailbox,name)) + && (!taillen + || (taillen < boxlen - reflen + && !strucmp(&(*mailbox)[boxlen - taillen], tail)))){ + if(taillen) + (*mailbox)[boxlen - taillen] = '\0'; + + if(*(*mailbox += reflen)) + return(TRUE); + } + /* + * else don't worry about context "breakouts" since + * build_folder_list doesn't let the user introduce + * one... + */ + + return(FALSE); +} + + +/* + * rebuild_folder_list -- free up old list and re-issue commands to build + * a new list. + */ +void +refresh_folder_list(CONTEXT_S *context, int nodirs, int startover, MAILSTREAM **streamp) +{ + if(startover) + free_folder_list(context); + + build_folder_list(streamp, context, NULL, NULL, + (NEWS_TEST(context) ? BFL_LSUB : BFL_NONE) + | ((nodirs) ? BFL_FLDRONLY : BFL_NONE)); +} + + +/* + * free_folder_list - loop thru the context's lists of folders + * clearing all entries without nicknames + * (as those were user provided) AND reset the + * context's find flag. + * + * NOTE: if fetched() information (e.g., like message counts come back + * in bboard collections), we may want to have a check before + * executing the loop and setting the FIND flag. + */ +void +free_folder_list(CONTEXT_S *cntxt) +{ + int n, i; + + /* + * In this case, don't blast the list as it was given to us by the + * user and not the result of a mail_list call... + */ + if(cntxt->use & CNTXT_INCMNG) + return; + + if(cntxt->use & CNTXT_PRESRV) + folder_select_preserve(cntxt); + + for(n = folder_total(FOLDERS(cntxt)), i = 0; n > 0; n--) + if(folder_entry(i, FOLDERS(cntxt))->nickname) + i++; /* entry wasn't from LIST */ + else + folder_delete(i, FOLDERS(cntxt)); + + cntxt->dir->status |= CNTXT_NOFIND; /* do find next time... */ + /* or add the fake entry */ + cntxt->use &= ~(CNTXT_PSEUDO | CNTXT_PRESRV | CNTXT_ZOOM); +} + + +/* + * default_save_context - return the default context for saved messages + */ +CONTEXT_S * +default_save_context(CONTEXT_S *cntxt) +{ + while(cntxt) + if((cntxt->use) & CNTXT_SAVEDFLT) + return(cntxt); + else + cntxt = cntxt->next; + + return(NULL); +} + + + +/* + * folder_complete - foldername completion routine + * + * Result: returns 0 if the folder doesn't have a any completetion + * 1 if the folder has a completion (*AND* "name" is + * replaced with the completion) + * + */ +int +folder_complete(CONTEXT_S *context, char *name, size_t namelen, int *completions) +{ + return(folder_complete_internal(context, name, namelen, completions, FC_NONE)); +} + + +/* + * + */ +int +folder_complete_internal(CONTEXT_S *context, char *name, size_t namelen, + int *completions, int flags) +{ + int i, match = -1, ftotal; + char tmp[MAXFOLDER+2], *a, *b, *fn, *pat; + FOLDER_S *f; + + if(completions) + *completions = 0; + + if(*name == '\0' || !context_isambig(name)) + return(0); + + if(!((context->use & CNTXT_INCMNG) || ALL_FOUND(context))){ + /* + * Build the folder list from scratch since we may need to + * traverse hierarchy... + */ + + free_folder_list(context); + snprintf(tmp, sizeof(tmp), "%s%c", name, NEWS_TEST(context) ? '*' : '%'); + build_folder_list(NULL, context, tmp, NULL, + (NEWS_TEST(context) & !(flags & FC_FORCE_LIST)) + ? BFL_LSUB : BFL_NONE); + } + + *tmp = '\0'; /* find uniq substring */ + ftotal = folder_total(FOLDERS(context)); + for(i = 0; i < ftotal; i++){ + f = folder_entry(i, FOLDERS(context)); + fn = FLDR_NAME(f); + pat = name; + if(!(NEWS_TEST(context) || (context->use & CNTXT_INCMNG))){ + fn = folder_last_cmpnt(fn, context->dir->delim); + pat = folder_last_cmpnt(pat, context->dir->delim); + } + + if(!strncmp(fn, pat, strlen(pat))){ + if(match != -1){ /* oh well, do best we can... */ + a = fn; + if(match >= 0){ + f = folder_entry(match, FOLDERS(context)); + fn = FLDR_NAME(f); + if(!NEWS_TEST(context)) + fn = folder_last_cmpnt(fn, context->dir->delim); + + strncpy(tmp, fn, sizeof(tmp)-1); + tmp[sizeof(tmp)-1] = '\0'; + } + + match = -2; + b = tmp; /* remember largest common text */ + while(*a && *b && *a == *b) + *b++ = *a++; + + *b = '\0'; + } + else + match = i; /* bingo?? */ + } + } + + if(match >= 0){ /* found! */ + f = folder_entry(match, FOLDERS(context)); + fn = FLDR_NAME(f); + if(!(NEWS_TEST(context) || (context->use & CNTXT_INCMNG))) + fn = folder_last_cmpnt(fn, context->dir->delim); + + strncpy(pat, fn, namelen-(pat-name)); + name[namelen-1] = '\0'; + if(f->isdir && !f->isfolder){ + name[i = strlen(name)] = context->dir->delim; + name[i+1] = '\0'; + } + } + else if(match == -2){ /* closest we could find */ + strncpy(pat, tmp, namelen-(pat-name)); + name[namelen-1] = '\0'; + } + + if(completions) + *completions = ftotal; + + if(!((context->use & CNTXT_INCMNG) || ALL_FOUND(context))) + free_folder_list(context); + + return((match >= 0) ? ftotal : 0); +} + + +char * +folder_last_cmpnt(char *s, int d) +{ + register char *p; + + if(d) + for(p = s; (p = strindex(p, d)); s = ++p) + ; + + return(s); +} + + +/* + * init_folder_entries - return a piece of memory suitable for attaching + * a list of folders... + * + */ +FLIST * +init_folder_entries(void) +{ + FLIST *flist = (FLIST *) fs_get(sizeof(FLIST)); + flist->folders = (FOLDER_S **) fs_get(FCHUNK * sizeof(FOLDER_S *)); + memset((void *)flist->folders, 0, (FCHUNK * sizeof(FOLDER_S *))); + flist->allocated = FCHUNK; + flist->used = 0; + return(flist); +} + + +/* + * new_folder - return a brand new folder entry, with the given name + * filled in. + * + * NOTE: THIS IS THE ONLY WAY TO PUT A NAME INTO A FOLDER ENTRY!!! + * STRCPY WILL NOT WORK!!! + */ +FOLDER_S * +new_folder(char *name, long unsigned int hash) +{ + FOLDER_S *tmp; + size_t l = strlen(name); + + tmp = (FOLDER_S *)fs_get(sizeof(FOLDER_S) + (l * sizeof(char))); + memset((void *)tmp, 0, sizeof(FOLDER_S)); + strncpy(tmp->name, name, l); + tmp->name[l] = '\0'; + tmp->name_len = (unsigned char) l; + tmp->varhash = hash; + return(tmp); +} + + +/* + * folder_entry - folder struct access routine. Permit reference to + * folder structs via index number. Serves two purposes: + * 1) easy way for callers to access folder data + * conveniently + * 2) allows for a custom manager to limit memory use + * under certain rather limited "operating systems" + * who shall renameless, but whose initials are DOS + * + * + */ +FOLDER_S * +folder_entry(int i, FLIST *flist) +{ + return((i >= flist->used) ? NULL:flist->folders[i]); +} + + +/* + * free_folder_entries - release all resources associated with the given + * list of folder entries + */ +void +free_folder_entries(FLIST **flist) +{ + register int i; + + if(!(flist && *flist)) + return; + + i = (*flist)->used; + while(i--){ + if((*flist)->folders[i]->nickname) + fs_give((void **) &(*flist)->folders[i]->nickname); + + fs_give((void **) &((*flist)->folders[i])); + } + + fs_give((void **) &((*flist)->folders)); + fs_give((void **) flist); +} + + +/* + * return the number of folders associated with the given folder list + */ +int +folder_total(FLIST *flist) +{ + return((int) flist->used); +} + + +/* + * return the index number of the given name in the given folder list + */ +int +folder_index(char *name, CONTEXT_S *cntxt, int flags) +{ + register int i = 0; + FOLDER_S *f; + char *fname; + + for(i = 0; (f = folder_entry(i, FOLDERS(cntxt))); i++) + if(((flags & FI_FOLDER) && (f->isfolder || (cntxt->use & CNTXT_INCMNG))) + || ((flags & FI_DIR) && f->isdir)){ + fname = FLDR_NAME(f); +#if defined(DOS) || defined(OS2) + if(flags & FI_RENAME){ /* case-dependent for rename */ + if(*name == *fname && strcmp(name, fname) == 0) + return(i); + } + else{ + if(toupper((unsigned char)(*name)) + == toupper((unsigned char)(*fname)) && strucmp(name, fname) == 0) + return(i); + } +#else + if(*name == *fname && strcmp(name, fname) == 0) + return(i); +#endif + } + + return(-1); +} + + +/* + * folder_is_nick - check to see if the given name is a nickname + * for some folder in the given context... + * + * NOTE: no need to check if mm_list_names has been done as + * nicknames can only be set by configuration... + */ +char * +folder_is_nick(char *nickname, FLIST *flist, int flags) +{ + register int i = 0; + FOLDER_S *f; + + if(!(nickname && *nickname && flist)) + return(NULL); + + while((f = folder_entry(i, flist)) != NULL){ + /* + * The second part of the OR is checking in a case-indep + * way for INBOX. It should be restricted to the context + * to which we add the INBOX folder, which would be either + * the Incoming Folders collection or the first collection + * if there is no Incoming collection. We don't need to check + * the collection because nickname assignment has already + * done that for us. Most folders don't have nicknames, only + * incoming folders and folders like inbox if not in incoming. + */ + if(f->nickname + && (!strcmp(nickname, f->nickname) + || (!strucmp(nickname, f->nickname) + && !strucmp(nickname, ps_global->inbox_name)))){ + char source[MAILTMPLEN], *target = NULL; + + /* + * If f is a maildrop, then we want to return the + * destination folder, not the whole #move thing. + */ + if(!(flags & FN_WHOLE_NAME) + && check_for_move_mbox(f->name, source, sizeof(source), &target)) + return(target); + else + return(f->name); + } + else + i++; + } + + return(NULL); +} + + +char * +folder_is_target_of_nick(char *longname, CONTEXT_S *cntxt) +{ + register int i = 0; + FOLDER_S *f; + FLIST *flist = NULL; + + if(cntxt && cntxt == ps_global->context_list) + flist = FOLDERS(cntxt); + + if(!(longname && *longname && flist)) + return(NULL); + + while((f = folder_entry(i, flist)) != NULL){ + if(f->nickname && f->name && !strcmp(longname, f->name)) + return(f->nickname); + else + i++; + } + + return(NULL); +} + + +/*---------------------------------------------------------------------- + Insert the given folder name into the sorted folder list + associated with the given context. Only allow ambiguous folder + names IF associated with a nickname. + + Args: index -- Index to insert at, OR insert in sorted order if -1 + folder -- folder structure to insert into list + flist -- folder list to insert folder into + + **** WARNING **** + DON'T count on the folder pointer being valid after this returns + *** ALL FOLDER ELEMENT READS SHOULD BE THRU folder_entry() *** + + ----*/ +int +folder_insert(int index, FOLDER_S *folder, FLIST *flist) +{ + /* requested index < 0 means add to sorted list */ + if(index < 0 && (index = folder_total(flist)) > 0) + index = folder_insert_sorted(index / 2, 0, index, folder, flist, + (ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_FIRST) + ? compare_folders_dir_alpha + : (ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_LAST) + ? compare_folders_alpha_dir + : compare_folders_alpha); + + folder_insert_index(folder, index, flist); + return(index); +} + + +/* + * folder_insert_sorted - Insert given folder struct into given list + * observing sorting specified by given + * comparison function + */ +int +folder_insert_sorted(int index, int min_index, int max_index, FOLDER_S *folder, + FLIST *flist, int (*compf)(FOLDER_S *, FOLDER_S *)) +{ + int i; + + return(((i = (*compf)(folder_entry(index, flist), folder)) == 0) + ? index + : (i < 0) + ? ((++index >= max_index) + ? max_index + : ((*compf)(folder_entry(index, flist), folder) > 0) + ? index + : folder_insert_sorted((index + max_index) / 2, index, + max_index, folder, flist, compf)) + : ((index <= min_index) + ? min_index + : folder_insert_sorted((min_index + index) / 2, min_index, index, + folder, flist, compf))); +} + + +/* + * folder_insert_index - Insert the given folder struct into the global list + * at the given index. + */ +void +folder_insert_index(FOLDER_S *folder, int index, FLIST *flist) +{ + register FOLDER_S **flp, **iflp; + + /* if index is beyond size, place at end of list */ + index = MIN(index, flist->used); + + /* grow array ? */ + if(flist->used + 1 > flist->allocated){ + flist->allocated += FCHUNK; + fs_resize((void **)&(flist->folders), + flist->allocated * sizeof(FOLDER_S *)); + } + + /* shift array left */ + iflp = &((flist->folders)[index]); + for(flp = &((flist->folders)[flist->used]); + flp > iflp; flp--) + flp[0] = flp[-1]; + + flist->folders[index] = folder; + flist->used += 1; +} + + +void +resort_folder_list(FLIST *flist) +{ + if(flist && folder_total(flist) > 1 && flist->folders) + qsort(flist->folders, folder_total(flist), sizeof(flist->folders[0]), + (ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_FIRST) + ? compare_folders_dir_alpha_qsort + : (ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_LAST) + ? compare_folders_alpha_dir_qsort + : compare_folders_alpha_qsort); +} + + +/*---------------------------------------------------------------------- + Removes a folder at the given index in the given context's + list. + +Args: index -- Index in folder list of folder to be removed + flist -- folder list + ----*/ +void +folder_delete(int index, FLIST *flist) +{ + register int i; + FOLDER_S *f; + + if(flist->used + && (index < 0 || index >= flist->used)) + return; /* bogus call! */ + + if((f = folder_entry(index, flist))->nickname) + fs_give((void **)&(f->nickname)); + + fs_give((void **) &(flist->folders[index])); + for(i = index; i < flist->used - 1; i++) + flist->folders[i] = flist->folders[i+1]; + + + flist->used -= 1; +} + + +/*---------------------------------------------------------------------- + compare two names for qsort, case independent + + Args: pointers to strings to compare + + Result: integer result of strcmp of the names. Uses simple + efficiency hack to speed the string comparisons up a bit. + + ----------------------------------------------------------------------*/ +int +compare_names(const qsort_t *x, const qsort_t *y) +{ + char *a = *(char **)x, *b = *(char **)y; + int r; +#define CMPI(X,Y) ((X)[0] - (Y)[0]) +#define UCMPI(X,Y) ((isupper((unsigned char)((X)[0])) \ + ? (X)[0] - 'A' + 'a' : (X)[0]) \ + - (isupper((unsigned char)((Y)[0])) \ + ? (Y)[0] - 'A' + 'a' : (Y)[0])) + + /*---- Inbox always sorts to the top ----*/ + if(UCMPI(a, ps_global->inbox_name) == 0 + && strucmp(a, ps_global->inbox_name) == 0) + return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : -1); + else if((UCMPI(b, ps_global->inbox_name)) == 0 + && strucmp(b, ps_global->inbox_name) == 0) + return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : 1); + + /*----- The sent-mail folder, is always next unless... ---*/ + else if(F_OFF(F_SORT_DEFAULT_FCC_ALPHA, ps_global) + && CMPI(a, ps_global->VAR_DEFAULT_FCC) == 0 + && strcmp(a, ps_global->VAR_DEFAULT_FCC) == 0) + return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : -1); + else if(F_OFF(F_SORT_DEFAULT_FCC_ALPHA, ps_global) + && CMPI(b, ps_global->VAR_DEFAULT_FCC) == 0 + && strcmp(b, ps_global->VAR_DEFAULT_FCC) == 0) + return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : 1); + + /*----- The saved-messages folder, is always next unless... ---*/ + else if(F_OFF(F_SORT_DEFAULT_SAVE_ALPHA, ps_global) + && CMPI(a, ps_global->VAR_DEFAULT_SAVE_FOLDER) == 0 + && strcmp(a, ps_global->VAR_DEFAULT_SAVE_FOLDER) == 0) + return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : -1); + else if(F_OFF(F_SORT_DEFAULT_SAVE_ALPHA, ps_global) + && CMPI(b, ps_global->VAR_DEFAULT_SAVE_FOLDER) == 0 + && strcmp(b, ps_global->VAR_DEFAULT_SAVE_FOLDER) == 0) + return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : 1); + + else + return((r = CMPI(a, b)) ? r : strcmp(a, b)); +} + + +/*---------------------------------------------------------------------- + compare two folder structs for ordering alphabetically + + Args: pointers to folder structs to compare + + Result: integer result of dir-bit and strcmp of the folders. + ----------------------------------------------------------------------*/ +int +compare_folders_alpha(FOLDER_S *f1, FOLDER_S *f2) +{ + int i; + char *f1name = FLDR_NAME(f1), + *f2name = FLDR_NAME(f2); + + return(((i = compare_names(&f1name, &f2name)) != 0) + ? i : (f2->isdir - f1->isdir)); +} + + +int +compare_folders_alpha_qsort(const qsort_t *a1, const qsort_t *a2) +{ + FOLDER_S *f1 = *((FOLDER_S **) a1); + FOLDER_S *f2 = *((FOLDER_S **) a2); + + return(compare_folders_alpha(f1, f2)); +} + + +/*---------------------------------------------------------------------- + compare two folder structs alphabetically with dirs first + + Args: pointers to folder structs to compare + + Result: integer result of dir-bit and strcmp of the folders. + ----------------------------------------------------------------------*/ +int +compare_folders_dir_alpha(FOLDER_S *f1, FOLDER_S *f2) +{ + int i; + + if((i = (f2->isdir - f1->isdir)) == 0){ + char *f1name = FLDR_NAME(f1), + *f2name = FLDR_NAME(f2); + + return(compare_names(&f1name, &f2name)); + } + + return(i); +} + + +int +compare_folders_dir_alpha_qsort(const qsort_t *a1, const qsort_t *a2) +{ + FOLDER_S *f1 = *((FOLDER_S **) a1); + FOLDER_S *f2 = *((FOLDER_S **) a2); + + return(compare_folders_dir_alpha(f1, f2)); +} + + +/*---------------------------------------------------------------------- + compare two folder structs alphabetically with dirs last + + Args: pointers to folder structs to compare + + Result: integer result of dir-bit and strcmp of the folders. + ----------------------------------------------------------------------*/ +int +compare_folders_alpha_dir(FOLDER_S *f1, FOLDER_S *f2) +{ + int i; + + if((i = (f1->isdir - f2->isdir)) == 0){ + char *f1name = FLDR_NAME(f1), + *f2name = FLDR_NAME(f2); + + return(compare_names(&f1name, &f2name)); + } + + return(i); +} + + +int +compare_folders_alpha_dir_qsort(const qsort_t *a1, const qsort_t *a2) +{ + FOLDER_S *f1 = *((FOLDER_S **) a1); + FOLDER_S *f2 = *((FOLDER_S **) a2); + + return(compare_folders_alpha_dir(f1, f2)); +} + + +/* + * Find incoming folders and update the unseen counts + * if necessary. + */ +void +folder_unseen_count_updater(unsigned long flags) +{ + CONTEXT_S *ctxt; + time_t oldest, started_checking; + int ftotal, i, first = -1; + FOLDER_S *f; + + /* + * We would only do this if there is an incoming collection, the + * user wants us to monitor, and we're in the folder screen. + */ + if(ps_global->in_folder_screen && F_ON(F_ENABLE_INCOMING_CHECKING, ps_global) + && (ctxt=ps_global->context_list) && ctxt->use & CNTXT_INCMNG + && (ftotal = folder_total(FOLDERS(ctxt)))){ + /* + * Search through the last_unseen_update times to find + * the one that was updated longest ago, and start with + * that one. We don't want to delay long doing these + * checks so may only check some of them each time we + * get called. An update time equal to 1 means don't check + * this folder at all. + */ + for(i = 0; i < ftotal; i++){ + f = folder_entry(i, FOLDERS(ctxt)); + if(f && LUU_YES(f->last_unseen_update)){ + first = i; + oldest = f->last_unseen_update; + break; + } + } + + /* + * Now first is the first in the list that + * should ever be checked. Next find the + * one that should be checked next, the one + * that was checked longest ago. + */ + if(first >= 0){ + for(i = 1; i < ftotal; i++){ + f = folder_entry(i, FOLDERS(ctxt)); + if(f && LUU_YES(f->last_unseen_update) && f->last_unseen_update < oldest){ + first = i; + oldest = f->last_unseen_update; + } + } + } + + /* now first is the next one to be checked */ + + started_checking = time(0); + + for(i = first; i < ftotal; i++){ + /* update the next one */ + f = folder_entry(i, FOLDERS(ctxt)); + if(f && LUU_YES(f->last_unseen_update) + && (flags & UFU_FORCE + /* or it's been long enough and we've not been in this function too long */ + || (((time(0) - f->last_unseen_update) >= ps_global->inc_check_interval) + && ((time(0) - started_checking) < MIN(4,ps_global->inc_check_timeout))))) + update_folder_unseen(f, ctxt, flags, NULL); + } + + for(i = 0; i < first; i++){ + f = folder_entry(i, FOLDERS(ctxt)); + if(f && LUU_YES(f->last_unseen_update) + && (flags & UFU_FORCE + || (((time(0) - f->last_unseen_update) >= ps_global->inc_check_interval) + && ((time(0) - started_checking) < MIN(4,ps_global->inc_check_timeout))))) + update_folder_unseen(f, ctxt, flags, NULL); + } + } +} + + +/* + * Update the count of unseen in the FOLDER_S struct + * for this folder. This will update if the time + * interval has passed or if the FORCE flag is set. + */ +void +update_folder_unseen(FOLDER_S *f, CONTEXT_S *ctxt, unsigned long flags, + MAILSTREAM *this_is_the_stream) +{ + time_t now; + int orig_valid; + int use_imap_interval = 0; + int stream_is_open = 0; + unsigned long orig_unseen, orig_new, orig_tot; + char mailbox_name[MAILTMPLEN]; + char *target = NULL; + DRIVER *d; + + if(!f || !LUU_YES(f->last_unseen_update)) + return; + + now = time(0); + context_apply(mailbox_name, ctxt, f->name, MAILTMPLEN); + + if(!mailbox_name[0]) + return; + + if(check_for_move_mbox(mailbox_name, NULL, 0, &target)){ + MAILSTREAM *strm; + + /* + * If this maildrop is the currently open stream use that. + * I'm not altogether sure that this is a good way to + * check this. + */ + if(target + && ((strm=ps_global->mail_stream) + && strm->snarf.name + && (!strcmp(target,strm->mailbox) + || !strcmp(target,strm->original_mailbox)))){ + stream_is_open++; + } + } + else{ + MAILSTREAM *m = NULL; + + stream_is_open = (this_is_the_stream + || (m=sp_stream_get(mailbox_name, SP_MATCH | SP_RO_OK)) + || ((m=ps_global->mail_stream) && !sp_dead_stream(m) + && same_stream_and_mailbox(mailbox_name,m)) + || (!IS_REMOTE(mailbox_name) + && (m=already_open_stream(mailbox_name, AOS_NONE)))) ? 1 : 0; + + if(stream_is_open){ + if(!this_is_the_stream) + this_is_the_stream = m; + } + else{ + /* + * If it's IMAP or local we use a shorter interval. + */ + d = mail_valid(NIL, mailbox_name, (char *) NIL); + if((d && !strcmp(d->name, "imap")) || !IS_REMOTE(mailbox_name)) + use_imap_interval++; + } + } + + /* + * Update if forced, or if it's been a while, or if we have a + * stream open to this mailbox already. + */ + if(flags & UFU_FORCE + || stream_is_open + || ((use_imap_interval + && (now - f->last_unseen_update) >= ps_global->inc_check_interval) + || ((now - f->last_unseen_update) >= ps_global->inc_second_check_interval))){ + unsigned long tot, uns, new; + unsigned long *totp = NULL, *unsp = NULL, *newp = NULL; + + orig_valid = f->unseen_valid; + orig_unseen = f->unseen; + orig_new = f->new; + orig_tot = f->total; + + if(F_ON(F_INCOMING_CHECKING_RECENT, ps_global)) + newp = &new; + else + unsp = &uns; + + if(F_ON(F_INCOMING_CHECKING_TOTAL, ps_global)) + totp = &tot; + + f->unseen_valid = 0; + + dprint((9, "update_folder_unseen(%s)", FLDR_NAME(f))); + if(get_recent_in_folder(mailbox_name, newp, unsp, totp, this_is_the_stream)){ + f->last_unseen_update = time(0); + f->unseen_valid = 1; + if(unsp) + f->unseen = uns; + + if(newp) + f->new = new; + + if(totp) + f->total = tot; + + if(!orig_valid){ + dprint((9, "update_folder_unseen(%s): original: %s%s%s%s", + FLDR_NAME(f), + F_ON(F_INCOMING_CHECKING_RECENT,ps_global) ? "new=" : "unseen=", + F_ON(F_INCOMING_CHECKING_RECENT,ps_global) ? comatose(f->new) : comatose(f->unseen), + F_ON(F_INCOMING_CHECKING_TOTAL,ps_global) ? " tot=" : "", + F_ON(F_INCOMING_CHECKING_TOTAL,ps_global) ? comatose(f->total) : "")); + } + + if(orig_valid + && ((F_ON(F_INCOMING_CHECKING_RECENT, ps_global) + && orig_new != f->new) + || + (F_OFF(F_INCOMING_CHECKING_RECENT, ps_global) + && orig_unseen != f->unseen) + || + (F_ON(F_INCOMING_CHECKING_TOTAL, ps_global) + && orig_tot != f->total))){ + + if(ps_global->in_folder_screen) + ps_global->noticed_change_in_unseen = 1; + + dprint((9, "update_folder_unseen(%s): changed: %s%s%s%s", + FLDR_NAME(f), + F_ON(F_INCOMING_CHECKING_RECENT,ps_global) ? "new=" : "unseen=", + F_ON(F_INCOMING_CHECKING_RECENT,ps_global) ? comatose(f->new) : comatose(f->unseen), + F_ON(F_INCOMING_CHECKING_TOTAL,ps_global) ? " tot=" : "", + F_ON(F_INCOMING_CHECKING_TOTAL,ps_global) ? comatose(f->total) : "")); + + if(flags & UFU_ANNOUNCE + && ((F_ON(F_INCOMING_CHECKING_RECENT, ps_global) + && orig_new < f->new) + || + (F_OFF(F_INCOMING_CHECKING_RECENT, ps_global) + && orig_unseen < f->unseen))){ + if(F_ON(F_INCOMING_CHECKING_RECENT, ps_global)) + q_status_message3(SM_ASYNC, 1, 3, "%s: %s %s", + FLDR_NAME(f), comatose(f->new), + _("new")); + else + q_status_message3(SM_ASYNC, 1, 3, "%s: %s %s", + FLDR_NAME(f), comatose(f->unseen), + _("unseen")); + } + } + } + else + f->last_unseen_update = LUU_NOMORECHK; /* no further checking */ + } +} + + +void +update_folder_unseen_by_stream(MAILSTREAM *strm, unsigned long flags) +{ + CONTEXT_S *ctxt; + int ftotal, i; + char mailbox_name[MAILTMPLEN]; + char *cn, tmp[MAILTMPLEN]; + FOLDER_S *f; + + /* + * Attempt to figure out which incoming folder this stream + * is open to, if any, so we can update the unseen counters. + */ + if(strm + && F_ON(F_ENABLE_INCOMING_CHECKING, ps_global) + && (ctxt=ps_global->context_list) && ctxt->use & CNTXT_INCMNG + && (ftotal = folder_total(FOLDERS(ctxt)))){ + for(i = 0; i < ftotal; i++){ + f = folder_entry(i, FOLDERS(ctxt)); + context_apply(mailbox_name, ctxt, f->name, MAILTMPLEN); + if(same_stream_and_mailbox(mailbox_name, strm) + || (!IS_REMOTE(mailbox_name) && (cn=mailboxfile(tmp,mailbox_name)) && (*cn) && (!strcmp(cn, strm->mailbox) || !strcmp(cn, strm->original_mailbox)))){ + /* if we failed earlier on this one, give it another go */ + if(f->last_unseen_update == LUU_NOMORECHK) + init_incoming_unseen_data(ps_global, f); + + update_folder_unseen(f, ctxt, flags, strm); + return; + } + } + } +} + + +/* + * Find the number of new, unseen, and the total number of + * messages in mailbox_name. + * If the corresponding arg is NULL it will skip the work + * necessary for that flag. + * + * Returns 1 if successful, 0 if not. + */ +int +get_recent_in_folder(char *mailbox_name, long unsigned int *new, + long unsigned int *unseen, long unsigned int *total, + MAILSTREAM *this_is_the_stream) +{ + MAILSTREAM *strm = NIL; + unsigned long tot, nw, uns; + int gotit = 0; + int maildrop = 0; + char *target = NULL; + MSGNO_S *msgmap; + long excluded, flags; + extern MAILSTATUS mm_status_result; + + dprint((9, "get_recent_in_folder(%s)", mailbox_name ? mailbox_name : "?")); + + if(check_for_move_mbox(mailbox_name, NULL, 0, &target)){ + + maildrop++; + + /* + * If this maildrop is the currently open stream use that. + */ + if(target + && ((strm=ps_global->mail_stream) + && strm->snarf.name + && (!strcmp(target,strm->mailbox) + || !strcmp(target,strm->original_mailbox)))){ + gotit++; + msgmap = sp_msgmap(strm); + excluded = any_lflagged(msgmap, MN_EXLD); + + tot = strm->nmsgs - excluded; + if(tot){ + if(new){ + if(sp_recent_since_visited(strm) == 0) + nw = 0; + else + nw = count_flagged(strm, F_RECENT | F_UNSEEN | F_UNDEL); + } + + if(unseen) + uns = count_flagged(strm, F_UNSEEN | F_UNDEL); + } + else{ + nw = 0; + uns = 0; + } + } + /* else fall through to just open it case */ + } + + /* do we already have it selected? */ + if(!gotit + && ((strm = this_is_the_stream) + || (strm = sp_stream_get(mailbox_name, SP_MATCH | SP_RO_OK)) + || (!IS_REMOTE(mailbox_name) + && (strm = already_open_stream(mailbox_name, AOS_NONE))))){ + gotit++; + + /* + * Unfortunately, we have to worry about excluded + * messages. The user doesn't want to have + * excluded messages count in the totals, especially + * recent excluded messages. + */ + + msgmap = sp_msgmap(strm); + excluded = any_lflagged(msgmap, MN_EXLD); + + tot = strm->nmsgs - excluded; + if(tot){ + if(new){ + if(sp_recent_since_visited(strm) == 0) + nw = 0; + else + nw = count_flagged(strm, F_RECENT | F_UNSEEN | F_UNDEL); + } + + if(unseen) + uns = count_flagged(strm, F_UNSEEN | F_UNDEL); + } + else{ + nw = 0; + uns = 0; + } + } + /* + * No, but how about another stream to same server which + * could be used for a STATUS command? + */ + else if(!gotit && (strm = sp_stream_get(mailbox_name, SP_SAME)) + && modern_imap_stream(strm)){ + + flags = 0L; + if(total) + flags |= SA_MESSAGES; + + if(new) + flags |= SA_RECENT; + + if(unseen) + flags |= SA_UNSEEN; + + mm_status_result.flags = 0L; + + pine_mail_status(strm, mailbox_name, flags); + if(total){ + if(mm_status_result.flags & SA_MESSAGES){ + tot = mm_status_result.messages; + gotit++; + } + } + + if(!(total && !gotit)){ + if(new){ + if(mm_status_result.flags & SA_RECENT){ + nw = mm_status_result.recent; + gotit++; + } + else + gotit = 0; + } + } + + if(!((total || new) && !gotit)){ + if(unseen){ + if(mm_status_result.flags & SA_UNSEEN){ + uns = mm_status_result.unseen; + gotit++; + } + else + gotit = 0; + } + } + } + + /* Let's just Select it. */ + if(!gotit){ + long saved_timeout; + long openflags; + + /* + * Traditional unix folders don't notice new mail if + * they are opened readonly. So maildrops with unix folder + * targets will snarf to the file but the stream that is + * opened won't see the new mail. So make all maildrop + * opens non-readonly here. + */ + openflags = SP_USEPOOL | SP_TEMPUSE | (maildrop ? 0 : OP_READONLY); + + saved_timeout = (long) mail_parameters(NULL, GET_OPENTIMEOUT, NULL); + mail_parameters(NULL, SET_OPENTIMEOUT, (void *) (long) ps_global->inc_check_timeout); + strm = pine_mail_open(NULL, mailbox_name, openflags, NULL); + mail_parameters(NULL, SET_OPENTIMEOUT, (void *) saved_timeout); + + if(strm){ + gotit++; + msgmap = sp_msgmap(strm); + excluded = any_lflagged(msgmap, MN_EXLD); + + tot = strm->nmsgs - excluded; + if(tot){ + if(new){ + if(sp_recent_since_visited(strm) == 0) + nw = 0; + else + nw = count_flagged(strm, F_RECENT | F_UNSEEN | F_UNDEL); + } + + if(unseen) + uns = count_flagged(strm, F_UNSEEN | F_UNDEL); + } + else{ + nw = 0; + uns = 0; + } + + pine_mail_close(strm); + } + } + + if(gotit){ + if(new) + *new = nw; + + if(unseen) + *unseen = uns; + + if(total) + *total = tot; + } + + return(gotit); +} + + +void +clear_incoming_valid_bits(void) +{ + CONTEXT_S *ctxt; + int ftotal, i; + FOLDER_S *f; + + if(F_ON(F_ENABLE_INCOMING_CHECKING, ps_global) + && (ctxt=ps_global->context_list) && ctxt->use & CNTXT_INCMNG + && (ftotal = folder_total(FOLDERS(ctxt)))) + for(i = 0; i < ftotal; i++){ + f = folder_entry(i, FOLDERS(ctxt)); + init_incoming_unseen_data(ps_global, f); + } +} + + +int +selected_folders(CONTEXT_S *context) +{ + int i, n, total; + + n = folder_total(FOLDERS(context)); + for(total = i = 0; i < n; i++) + if(folder_entry(i, FOLDERS(context))->selected) + total++; + + return(total); +} + + +SELECTED_S * +new_selected(void) +{ + SELECTED_S *selp; + + selp = (SELECTED_S *)fs_get(sizeof(SELECTED_S)); + selp->sub = NULL; + selp->reference = NULL; + selp->folders = NULL; + selp->zoomed = 0; + + return selp; +} + + +/* + * Free the current selected struct and all of the + * following structs in the list + */ +void +free_selected(SELECTED_S **selp) +{ + if(!selp || !(*selp)) + return; + if((*selp)->sub) + free_selected(&((*selp)->sub)); + + free_strlist(&(*selp)->folders); + if((*selp)->reference) + fs_give((void **) &(*selp)->reference); + + fs_give((void **) selp); +} + + +void +folder_select_preserve(CONTEXT_S *context) +{ + if(context + && !(context->use & CNTXT_PARTFIND)){ + FOLDER_S *fp; + STRLIST_S **slpp; + SELECTED_S *selp = &context->selected; + int i, folder_n; + + if(!context->dir->ref){ + if(!context->selected.folders) + slpp = &context->selected.folders; + else + return; + } + else{ + if(!selected_folders(context)) + return; + else{ + while(selp->sub){ + selp = selp->sub; + if(!strcmp(selp->reference, context->dir->ref)) + return; + } + selp->sub = new_selected(); + selp = selp->sub; + slpp = &(selp->folders); + } + } + folder_n = folder_total(FOLDERS(context)); + + for(i = 0; i < folder_n; i++) + if((fp = folder_entry(i, FOLDERS(context)))->selected){ + *slpp = new_strlist(fp->name); + slpp = &(*slpp)->next; + } + + /* Only remember "ref" if any folders were selected */ + if(selp->folders && context->dir->ref) + selp->reference = cpystr(context->dir->ref); + + selp->zoomed = (context->use & CNTXT_ZOOM) != 0; + } +} + + +int +folder_select_restore(CONTEXT_S *context) +{ + int rv = 0; + + if(context + && !(context->use & CNTXT_PARTFIND)){ + STRLIST_S *slp; + SELECTED_S *selp, *pselp = NULL; + int i, found = 0; + + selp = &(context->selected); + + if(context->dir->ref){ + pselp = selp; + selp = selp->sub; + while(selp && strcmp(selp->reference, context->dir->ref)){ + pselp = selp; + selp = selp->sub; + } + if (selp) + found = 1; + } + else + found = selp->folders != 0; + if(found){ + for(slp = selp->folders; slp; slp = slp->next) + if(slp->name + && (i = folder_index(slp->name, context, FI_FOLDER)) >= 0){ + folder_entry(i, FOLDERS(context))->selected = 1; + rv++; + } + + /* Used, always clean them up */ + free_strlist(&selp->folders); + if(selp->reference) + fs_give((void **) &selp->reference); + + if(selp->zoomed){ + context->use |= CNTXT_ZOOM; + selp->zoomed = 0; + } + if(!(selp == &context->selected)){ + if(pselp){ + pselp->sub = selp->sub; + fs_give((void **) &selp); + } + } + } + } + + return(rv); +} + |