* clear out some warnings by gcc 9.3.1.
[alpine.git] / pith / folder.c
blob4f38d24984980c67590842091bcfbe7f5fa70044
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: folder.c 1142 2008-08-13 17:22:21Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2013-2020 Eduardo Chappa
8 * Copyright 2006-2008 University of Washington
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 #include "../pith/headers.h"
20 #include "../c-client/utf8aux.h"
21 #include "../pith/folder.h"
22 #include "../pith/state.h"
23 #include "../pith/context.h"
24 #include "../pith/init.h"
25 #include "../pith/conf.h"
26 #include "../pith/stream.h"
27 #include "../pith/imap.h"
28 #include "../pith/util.h"
29 #include "../pith/flag.h"
30 #include "../pith/status.h"
31 #include "../pith/busy.h"
32 #include "../pith/mailindx.h"
35 typedef struct _build_folder_list_data {
36 long mask; /* bitmap of responses to ignore */
37 LISTARGS_S args;
38 LISTRES_S response;
39 int is_move_folder;
40 FLIST *list;
41 } BFL_DATA_S;
44 #define FCHUNK 64
48 * Internal prototypes
50 void mail_list_exists(MAILSTREAM *, char *, int, long, void *, unsigned);
51 void init_incoming_folder_list(struct pine *, CONTEXT_S *);
52 void mail_list_filter(MAILSTREAM *, char *, int, long, void *, unsigned);
53 void mail_lsub_filter(MAILSTREAM *, char *, int, long, void *, unsigned);
54 int mail_list_in_collection(char **, char *, char *, char *);
55 char *folder_last_cmpnt(char *, int);
56 void free_folder_entries(FLIST **);
57 int folder_insert_sorted(int, int, int, FOLDER_S *, FLIST *,
58 int (*)(FOLDER_S *, FOLDER_S *));
59 void folder_insert_index(FOLDER_S *, int, FLIST *);
60 void resort_folder_list(FLIST *flist);
61 int compare_folders_alpha_qsort(const qsort_t *a1, const qsort_t *a2);
62 int compare_folders_dir_alpha_qsort(const qsort_t *a1, const qsort_t *a2);
63 int compare_folders_alpha_dir_qsort(const qsort_t *a1, const qsort_t *a2);
64 int compare_names(const qsort_t *, const qsort_t *);
65 void init_incoming_unseen_data(struct pine *, FOLDER_S *f);
68 char *
69 folder_lister_desc(CONTEXT_S *cntxt, FDIR_S *fdp)
71 char *p, *q;
72 unsigned char *fname;
74 q = ((p = strstr(cntxt->context, "%s")) && !*(p+2)
75 && !strncmp(fdp->ref, cntxt->context, p - cntxt->context))
76 ? fdp->ref + (p - cntxt->context) : fdp->ref;
77 fname = folder_name_decoded((unsigned char *) q);
78 /* Provide context in new collection header */
79 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "Dir: %s", fname ? (char *) fname : q);
80 if(fname) fs_give((void **)&fname);
82 return(cpystr(tmp_20k_buf));
86 void
87 reset_context_folders(CONTEXT_S *cntxt)
89 CONTEXT_S *tc;
91 for(tc = cntxt; tc && tc->prev ; tc = tc->prev)
92 ; /* start at beginning */
94 for( ; tc ; tc = tc->next){
95 free_folder_list(tc);
97 while(tc->dir->prev){
98 FDIR_S *tp = tc->dir->prev;
99 free_fdir(&tc->dir, 0);
100 tc->dir = tp;
107 * next_folder_dir - return a directory structure with the folders it
108 * contains
110 FDIR_S *
111 next_folder_dir(CONTEXT_S *context, char *new_dir, int build_list, MAILSTREAM **streamp)
113 char tmp[MAILTMPLEN], dir[3];
114 FDIR_S *tmp_fp, *fp;
116 fp = (FDIR_S *) fs_get(sizeof(FDIR_S));
117 memset(fp, 0, sizeof(FDIR_S));
118 (void) context_apply(tmp, context, new_dir, MAILTMPLEN);
119 dir[0] = context->dir->delim;
120 dir[1] = '\0';
121 strncat(tmp, dir, sizeof(tmp)-1-strlen(tmp));
122 fp->ref = cpystr(tmp);
123 fp->delim = context->dir->delim;
124 fp->view.internal = cpystr(NEWS_TEST(context) ? "*" : "%");
125 fp->folders = init_folder_entries();
126 fp->status = CNTXT_NOFIND;
127 tmp_fp = context->dir; /* temporarily rebind */
128 context->dir = fp;
130 if(build_list)
131 build_folder_list(streamp, context, NULL, NULL,
132 NEWS_TEST(context) ? BFL_LSUB : BFL_NONE);
134 context->dir = tmp_fp;
135 return(fp);
140 * Return which pinerc incoming folder #index is in.
142 EditWhich
143 config_containing_inc_fldr(FOLDER_S *folder)
145 char **t;
146 int i, keep_going = 1, inheriting = 0;
147 struct variable *v = &ps_global->vars[V_INCOMING_FOLDERS];
149 if(v->is_fixed)
150 return(None);
152 /* is it in exceptions config? */
153 if(v->post_user_val.l){
154 for(i = 0, t=v->post_user_val.l; t[i]; i++){
155 if(expand_variables(tmp_20k_buf, SIZEOF_20KBUF, t[i], 0)){
156 keep_going = 0;
158 if(!strcmp(tmp_20k_buf, INHERIT))
159 inheriting = 1;
160 else if(folder->varhash == line_hash(tmp_20k_buf))
161 return(Post);
166 if(inheriting)
167 keep_going = 1;
169 /* is it in main config? */
170 if(keep_going && v->main_user_val.l){
171 for(i = 0, t=v->main_user_val.l; t[i]; i++){
172 if(expand_variables(tmp_20k_buf, SIZEOF_20KBUF, t[i], 0) &&
173 folder->varhash == line_hash(tmp_20k_buf))
174 return(Main);
178 return(None);
182 /*----------------------------------------------------------------------
183 Format the given folder name for display for the user
185 Args: folder -- The folder name to fix up
187 Not sure this always makes it prettier. It could do nice truncation if we
188 passed in a length. Right now it adds the path name of the mail
189 subdirectory if appropriate.
190 ----*/
192 char *
193 pretty_fn(char *folder)
195 if(!strucmp(folder, ps_global->inbox_name))
196 return(ps_global->inbox_name);
197 else
198 return(folder);
202 /*----------------------------------------------------------------------
203 Return the path delimiter for the given folder on the given server
205 Args: folder -- folder type for delimiter
207 ----*/
209 get_folder_delimiter(char *folder)
211 MM_LIST_S ldata;
212 LISTRES_S response;
213 int ourstream = 0;
215 memset(mm_list_info = &ldata, 0, sizeof(MM_LIST_S));
216 ldata.filter = mail_list_response;
217 memset(ldata.data = &response, 0, sizeof(LISTRES_S));
219 if(*folder == '{'
220 && !(ldata.stream = sp_stream_get(folder, SP_MATCH))
221 && !(ldata.stream = sp_stream_get(folder, SP_SAME))){
222 if((ldata.stream = pine_mail_open(NULL,folder,
223 OP_HALFOPEN|OP_SILENT|SP_USEPOOL|SP_TEMPUSE,
224 NULL)) != NULL){
225 ourstream++;
227 else{
228 return(FEX_ERROR);
232 pine_mail_list(ldata.stream, folder, "", NULL);
234 if(ourstream)
235 pine_mail_close(ldata.stream);
237 return(response.delim);
241 /*----------------------------------------------------------------------
242 Check to see if folder exists in given context
244 Args: cntxt -- context inwhich to interpret "file" arg
245 file -- name of folder to check
247 Result: returns FEX_ISFILE if the folder exists and is a folder
248 FEX_ISDIR if the folder exists and is a directory
249 FEX_NOENT if it doesn't exist
250 FEX_ERROR on error
252 The two existence return values above may be logically OR'd
254 Uses mail_list to sniff out the existence of the requested folder.
255 The context string is just here for convenience. Checking for
256 folder's existence within a given context is probably more efficiently
257 handled outside this function for now using build_folder_list().
259 ----*/
261 folder_exists(CONTEXT_S *cntxt, char *file)
263 return(folder_name_exists(cntxt, file, NULL));
267 /*----------------------------------------------------------------------
268 Check to see if folder exists in given context
270 Args: cntxt -- context in which to interpret "file" arg
271 file -- name of folder to check
272 name -- name of folder folder with context applied
274 Result: returns FEX_ISFILE if the folder exists and is a folder
275 FEX_ISDIR if the folder exists and is a directory
276 FEX_NOENT if it doesn't exist
277 FEX_ERROR on error
279 The two existence return values above may be logically OR'd
281 Uses mail_list to sniff out the existence of the requested folder.
282 The context string is just here for convenience. Checking for
283 folder's existence within a given context is probably more efficiently
284 handled outside this function for now using build_folder_list().
286 ----*/
288 folder_name_exists(CONTEXT_S *cntxt, char *file, char **fullpath)
290 MM_LIST_S ldata;
291 EXISTDATA_S parms;
292 int we_cancel = 0, res;
293 char *p, reference[MAILTMPLEN], tmp[MAILTMPLEN], *tfolder = NULL;
296 * No folder means "inbox".
298 if(*file == '{' && (p = strchr(file, '}')) && (!*(p+1))){
299 size_t l;
301 l = strlen(file)+strlen("inbox");
302 tfolder = (char *) fs_get((l+1) * sizeof(char));
303 snprintf(tfolder, l+1, "%s%s", file, "inbox");
304 file = tfolder;
305 cntxt = NULL;
308 mm_list_info = &ldata; /* tie down global reference */
309 memset(&ldata, 0, sizeof(ldata));
310 ldata.filter = mail_list_exists;
312 ldata.stream = sp_stream_get(context_apply(tmp, cntxt, file, sizeof(tmp)),
313 SP_SAME);
315 memset(ldata.data = &parms, 0, sizeof(EXISTDATA_S));
318 * If no preset reference string, must be at top of context
320 if(cntxt && context_isambig(file)){
321 /* inbox in first context is the real inbox */
322 if(ps_global->context_list == cntxt && !strucmp(file, ps_global->inbox_name)){
323 reference[0] = '\0';
324 parms.args.reference = reference;
326 else if(!(parms.args.reference = cntxt->dir->ref)){
327 char *p;
329 if((p = strstr(cntxt->context, "%s")) != NULL){
330 strncpy(parms.args.reference = reference,
331 cntxt->context,
332 MIN(p - cntxt->context, sizeof(reference)-1));
333 reference[MIN(p - cntxt->context, sizeof(reference)-1)] = '\0';
334 if(*(p += 2))
335 parms.args.tail = p;
337 else
338 parms.args.reference = cntxt->context;
341 parms.fullname = fullpath;
344 ps_global->mm_log_error = 0;
345 ps_global->noshow_error = 1;
347 we_cancel = busy_cue(NULL, NULL, 1);
349 parms.args.name = file;
351 res = pine_mail_list(ldata.stream, parms.args.reference, parms.args.name,
352 &ldata.options);
354 if(we_cancel)
355 cancel_busy_cue(-1);
357 ps_global->noshow_error = 0;
359 if(cntxt && cntxt->dir && parms.response.delim)
360 cntxt->dir->delim = parms.response.delim;
362 if(tfolder)
363 fs_give((void **)&tfolder);
364 return(((res == FALSE) || ps_global->mm_log_error)
365 ? FEX_ERROR
366 : (((parms.response.isfile)
367 ? FEX_ISFILE : 0 )
368 | ((parms.response.isdir)
369 ? FEX_ISDIR : 0)
370 | ((parms.response.ismarked)
371 ? FEX_ISMARKED : 0)
372 | ((parms.response.unmarked)
373 ? FEX_UNMARKED : 0)));
377 void
378 mail_list_exists(MAILSTREAM *stream, char *mailbox, int delim, long int attribs,
379 void *data, unsigned int options)
381 if(delim)
382 ((EXISTDATA_S *) data)->response.delim = delim;
384 if(mailbox && *mailbox){
385 if(!(attribs & LATT_NOSELECT)){
386 ((EXISTDATA_S *) data)->response.isfile = 1;
387 ((EXISTDATA_S *) data)->response.count += 1;
390 if(!(attribs & LATT_NOINFERIORS)){
391 ((EXISTDATA_S *) data)->response.isdir = 1;
392 ((EXISTDATA_S *) data)->response.count += 1;
395 if(attribs & LATT_MARKED)
396 ((EXISTDATA_S *) data)->response.ismarked = 1;
398 /* don't mark #move folders unmarked */
399 if(attribs & LATT_UNMARKED && !(options & PML_IS_MOVE_MBOX))
400 ((EXISTDATA_S *) data)->response.unmarked = 1;
402 if(attribs & LATT_HASCHILDREN)
403 ((EXISTDATA_S *) data)->response.haschildren = 1;
405 if(attribs & LATT_HASNOCHILDREN)
406 ((EXISTDATA_S *) data)->response.hasnochildren = 1;
408 if(((EXISTDATA_S *) data)->fullname
409 && ((((EXISTDATA_S *) data)->args.reference[0] != '\0')
410 ? struncmp(((EXISTDATA_S *) data)->args.reference, mailbox,
411 strlen(((EXISTDATA_S *)data)->args.reference))
412 : struncmp(((EXISTDATA_S *) data)->args.name, mailbox,
413 strlen(((EXISTDATA_S *) data)->args.name)))){
414 char *p;
415 size_t alloclen;
416 size_t len = (((stream && stream->mailbox)
417 ? strlen(stream->mailbox) : 0)
418 + strlen(((EXISTDATA_S *) data)->args.reference)
419 + strlen(((EXISTDATA_S *) data)->args.name)
420 + strlen(mailbox)) * sizeof(char);
423 * Fully qualify (in the c-client name structure sense)
424 * anything that's not in the context of the "reference"...
426 if(*((EXISTDATA_S *) data)->fullname)
427 fs_give((void **) ((EXISTDATA_S *) data)->fullname);
429 alloclen = len;
430 *((EXISTDATA_S *) data)->fullname = (char *) fs_get(alloclen);
431 if(*mailbox != '{'
432 && stream && stream->mailbox && *stream->mailbox == '{'
433 && (p = strindex(stream->mailbox, '}'))){
434 len = (p - stream->mailbox) + 1;
435 strncpy(*((EXISTDATA_S *) data)->fullname,
436 stream->mailbox, MIN(len,alloclen));
437 p = *((EXISTDATA_S *) data)->fullname + len;
439 else
440 p = *((EXISTDATA_S *) data)->fullname;
442 strncpy(p, mailbox, alloclen-(p-(*((EXISTDATA_S *) data)->fullname)));
443 (*((EXISTDATA_S *) data)->fullname)[alloclen-1] = '\0';;
449 void
450 mail_list_response(MAILSTREAM *stream, char *mailbox, int delim, long int attribs,
451 void *data, unsigned int options)
453 int counted = 0;
455 if(delim)
456 ((LISTRES_S *) data)->delim = delim;
458 if(mailbox && *mailbox){
459 if(!(attribs & LATT_NOSELECT)){
460 counted++;
461 ((LISTRES_S *) data)->isfile = 1;
462 ((LISTRES_S *) data)->count += 1;
465 if(!(attribs & LATT_NOINFERIORS)){
466 ((LISTRES_S *) data)->isdir = 1;
468 if(!counted)
469 ((LISTRES_S *) data)->count += 1;
472 if(attribs & LATT_HASCHILDREN)
473 ((LISTRES_S *) data)->haschildren = 1;
475 if(attribs & LATT_HASNOCHILDREN)
476 ((LISTRES_S *) data)->hasnochildren = 1;
481 char *
482 folder_as_breakout(CONTEXT_S *cntxt, char *name)
484 if(context_isambig(name)){ /* if simple check doesn't pan out */
485 char tmp[2*MAILTMPLEN], *p, *f; /* look harder */
487 if(!cntxt->dir->delim){
488 (void) context_apply(tmp, cntxt, "", sizeof(tmp)/2);
489 cntxt->dir->delim = get_folder_delimiter(tmp);
492 if((p = strindex(name, cntxt->dir->delim)) != NULL){
493 if(p == name){ /* assumption 6,321: delim is root */
494 if(cntxt->context[0] == '{'
495 && (p = strindex(cntxt->context, '}'))){
496 strncpy(tmp, cntxt->context,
497 MIN((p - cntxt->context) + 1, sizeof(tmp)/2));
498 tmp[MIN((p - cntxt->context) + 1, sizeof(tmp)/2)] = '\0';
499 strncpy(&tmp[MIN((p - cntxt->context) + 1, sizeof(tmp)/2)],
500 name, sizeof(tmp)/2-strlen(tmp));
501 tmp[sizeof(tmp)-1] = '\0';
502 return(cpystr(tmp));
504 else
505 return(cpystr(name));
507 else{ /* assumption 6,322: no create ~foo */
508 strncpy(tmp, name, MIN(p - name, MAILTMPLEN));
509 /* lop off trailingpath */
510 tmp[MIN(p - name, sizeof(tmp)/2)] = '\0';
511 f = NULL;
512 (void)folder_name_exists(cntxt, tmp, &f);
513 if(f){
514 snprintf(tmp, sizeof(tmp), "%s%s",f,p);
515 tmp[sizeof(tmp)-1] = '\0';
516 fs_give((void **) &f);
517 return(cpystr(tmp));
523 return(NULL);
527 /*----------------------------------------------------------------------
528 Initialize global list of contexts for folder collections.
530 Interprets collections defined in the pinerc and orders them for
531 pine's use. Parses user-provided context labels and sets appropriate
532 use flags and the default prototype for that collection.
533 (See find_folders for how the actual folder list is found).
535 ----*/
536 void
537 init_folders(struct pine *ps)
539 CONTEXT_S *tc, *top = NULL, **clist;
540 int i, prime = 0;
542 clist = ⊤
545 * If no incoming folders are config'd, but the user asked for
546 * them via feature, make sure at least "inbox" ends up there...
548 if(F_ON(F_ENABLE_INCOMING, ps) && !ps->VAR_INCOMING_FOLDERS){
549 ps->VAR_INCOMING_FOLDERS = (char **)fs_get(2 * sizeof(char *));
550 ps->VAR_INCOMING_FOLDERS[0] = cpystr(ps->inbox_name);
551 ps->VAR_INCOMING_FOLDERS[1] = NULL;
555 * Build context that's a list of folders the user's defined
556 * as receiving new messages. At some point, this should
557 * probably include adding a prefix with the new message count.
558 * fake new context...
560 if(ps->VAR_INCOMING_FOLDERS && ps->VAR_INCOMING_FOLDERS[0]
561 && (tc = new_context("Incoming-Folders []", NULL))){
562 tc->dir->status &= ~CNTXT_NOFIND;
563 tc->use |= CNTXT_INCMNG; /* mark this as incoming collection */
564 if(tc->label)
565 fs_give((void **) &tc->label);
567 /* TRANSLATORS: a label */
568 tc->label = cpystr(_("Incoming Message Folders"));
570 *clist = tc;
571 clist = &tc->next;
573 init_incoming_folder_list(ps, tc);
577 * Build list of folder collections. Because of the way init.c
578 * works, we're guaranteed at least a default. Also write any
579 * "bogus format" messages...
581 for(i = 0; ps->VAR_FOLDER_SPEC && ps->VAR_FOLDER_SPEC[i] ; i++)
582 if((tc = new_context(ps->VAR_FOLDER_SPEC[i], &prime)) != NULL){
583 *clist = tc; /* add it to list */
584 clist = &tc->next; /* prepare for next */
585 tc->var.v = &ps->vars[V_FOLDER_SPEC];
586 tc->var.i = i;
591 * Whoah cowboy!!! Guess we couldn't find a valid folder
592 * collection???
594 if(!prime)
595 alpine_panic(_("No folder collections defined"));
598 * At this point, insert the INBOX mapping as the leading
599 * folder entry of the first collection...
601 init_inbox_mapping(ps->VAR_INBOX_PATH, top);
603 set_news_spec_current_val(TRUE, TRUE);
606 * If news groups, loop thru list adding to collection list
608 for(i = 0; ps->VAR_NEWS_SPEC && ps->VAR_NEWS_SPEC[i] ; i++)
609 if(ps->VAR_NEWS_SPEC[i][0]
610 && (tc = new_context(ps->VAR_NEWS_SPEC[i], NULL))){
611 *clist = tc; /* add it to list */
612 clist = &tc->next; /* prepare for next */
613 tc->var.v = &ps->vars[V_NEWS_SPEC];
614 tc->var.i = i;
617 ps->context_list = top; /* init pointers */
618 ps->context_current = (top->use & CNTXT_INCMNG) ? top->next : top;
619 ps->context_last = NULL;
620 /* Tie up all the previous pointers */
621 for(; top; top = top->next)
622 if(top->next)
623 top->next->prev = top;
625 #ifdef DEBUG
626 dump_contexts();
627 #endif
632 * Add incoming list of folders to context.
634 void
635 init_incoming_folder_list(struct pine *ps, CONTEXT_S *cntxt)
637 int i;
638 char *folder_string, *nickname;
639 FOLDER_S *f;
641 for(i = 0; ps->VAR_INCOMING_FOLDERS[i] ; i++){
643 * Parse folder line for nickname and folder name.
644 * No nickname on line is OK.
646 get_pair(ps->VAR_INCOMING_FOLDERS[i], &nickname, &folder_string,0,0);
649 * Allow for inbox to be specified in the incoming list, but
650 * don't let it show up along side the one magically inserted
651 * above!
653 if(!folder_string || !strucmp(ps->inbox_name, folder_string)){
654 if(folder_string)
655 fs_give((void **)&folder_string);
657 if(nickname)
658 fs_give((void **)&nickname);
660 continue;
662 else if(update_bboard_spec(folder_string, tmp_20k_buf, SIZEOF_20KBUF)){
663 fs_give((void **) &folder_string);
664 folder_string = cpystr(tmp_20k_buf);
667 f = new_folder(folder_string,
668 line_hash(ps->VAR_INCOMING_FOLDERS[i]));
669 f->isfolder = 1;
670 fs_give((void **)&folder_string);
672 if(nickname){
673 if(strucmp(ps->inbox_name, nickname)){
674 f->nickname = nickname;
675 f->name_len = strlen(f->nickname);
677 else
678 fs_give((void **)&nickname);
681 init_incoming_unseen_data(ps, f);
683 folder_insert(f->nickname
684 && (strucmp(f->nickname, ps->inbox_name) == 0)
685 ? -1 : folder_total(FOLDERS(cntxt)),
686 f, FOLDERS(cntxt));
691 void
692 init_incoming_unseen_data(struct pine *ps, FOLDER_S *f)
694 int j, check_this = 0;
696 if(!f)
697 return;
699 /* see if this folder is in the monitoring list */
700 if(F_ON(F_ENABLE_INCOMING_CHECKING, ps)){
701 if(!ps->VAR_INCCHECKLIST)
702 check_this++; /* everything in by default */
703 else{
704 for(j = 0; !check_this && ps->VAR_INCCHECKLIST[j]; j++){
705 if((f->nickname && !strucmp(ps->VAR_INCCHECKLIST[j],f->nickname))
706 || (f->name && !strucmp(ps->VAR_INCCHECKLIST[j],f->name)))
707 check_this++;
712 if(check_this)
713 f->last_unseen_update = LUU_INIT;
714 else
715 f->last_unseen_update = LUU_NEVERCHK;
719 void
720 reinit_incoming_folder_list(struct pine *ps, CONTEXT_S *context)
722 free_folder_entries(&(FOLDERS(context)));
723 FOLDERS(context) = init_folder_entries();
724 init_incoming_folder_list(ps_global, context);
725 init_inbox_mapping(ps_global->VAR_INBOX_PATH, context);
732 void
733 init_inbox_mapping(char *path, CONTEXT_S *cntxt)
735 FOLDER_S *f;
736 int check_this, j;
739 * If mapping already exists, blast it and replace it below...
741 if((f = folder_entry(0, FOLDERS(cntxt)))
742 && f->nickname && !strcmp(f->nickname, ps_global->inbox_name))
743 folder_delete(0, FOLDERS(cntxt));
745 if(path){
746 f = new_folder(path, 0);
747 f->nickname = cpystr(ps_global->inbox_name);
748 f->name_len = strlen(f->nickname);
750 else
751 f = new_folder(ps_global->inbox_name, 0);
753 f->isfolder = 1;
755 /* see if this folder is in the monitoring list */
756 check_this = 0;
757 if(F_ON(F_ENABLE_INCOMING_CHECKING, ps_global) && ps_global->VAR_INCOMING_FOLDERS && ps_global->VAR_INCOMING_FOLDERS[0]){
758 if(!ps_global->VAR_INCCHECKLIST)
759 check_this++; /* everything in by default */
760 else{
761 for(j = 0; !check_this && ps_global->VAR_INCCHECKLIST[j]; j++){
762 if((f->nickname && !strucmp(ps_global->VAR_INCCHECKLIST[j],f->nickname))
763 || (f->name && !strucmp(ps_global->VAR_INCCHECKLIST[j],f->name)))
764 check_this++;
769 if(check_this)
770 f->last_unseen_update = LUU_INIT;
771 else
772 f->last_unseen_update = LUU_NEVERCHK;
774 folder_insert(0, f, FOLDERS(cntxt));
778 FDIR_S *
779 new_fdir(char *ref, char *view, int wildcard)
781 FDIR_S *rv = (FDIR_S *) fs_get(sizeof(FDIR_S));
783 memset((void *) rv, 0, sizeof(FDIR_S));
785 /* Monkey with the view to make sure it has wildcard? */
786 if(view && *view){
787 rv->view.user = cpystr(view);
790 * This is sorta hairy since, for simplicity we allow
791 * users to use '*' in the view, but for mail
792 * we really mean '%' as def'd in 2060...
794 if(wildcard == '*'){ /* must be #news. */
795 if(strindex(view, wildcard))
796 rv->view.internal = cpystr(view);
798 else{ /* must be mail */
799 char *p;
801 if((p = strpbrk(view, "*%")) != NULL){
802 rv->view.internal = p = cpystr(view);
803 while((p = strpbrk(p, "*%")) != NULL)
804 *p++ = '%'; /* convert everything to '%' */
808 if(!rv->view.internal){
809 size_t l;
811 l = strlen(view)+2;
812 rv->view.internal = (char *) fs_get((l+1) * sizeof(char));
813 snprintf(rv->view.internal, l+1, "%c%s%c", wildcard, view, wildcard);
816 else{
817 rv->view.internal = (char *) fs_get(2 * sizeof(char));
818 snprintf(rv->view.internal, 2, "%c", wildcard);
821 if(ref)
822 rv->ref = ref;
824 rv->folders = init_folder_entries();
825 rv->status = CNTXT_NOFIND;
826 return(rv);
830 void
831 free_fdir(FDIR_S **f, int recur)
833 if(f && *f){
834 if((*f)->prev && recur)
835 free_fdir(&(*f)->prev, 1);
837 if((*f)->ref)
838 fs_give((void **)&(*f)->ref);
840 if((*f)->view.user)
841 fs_give((void **)&(*f)->view.user);
843 if((*f)->view.internal)
844 fs_give((void **)&(*f)->view.internal);
846 if((*f)->desc)
847 fs_give((void **)&(*f)->desc);
849 free_folder_entries(&(*f)->folders);
850 fs_give((void **) f);
856 update_bboard_spec(char *bboard, char *buf, size_t buflen)
858 char *p = NULL, *origbuf;
859 int nntp = 0, bracket = 0;
861 origbuf = buf;
863 if(*bboard == '*'
864 || (*bboard == '{' && (p = strindex(bboard, '}')) && *(p+1) == '*')){
865 /* server name ? */
866 if(p || (*(bboard+1) == '{' && (p = strindex(++bboard, '}'))))
867 while(bboard <= p && (buf-origbuf < buflen)) /* copy it */
868 if((*buf++ = *bboard++) == '/' && !strncmp(bboard, "nntp", 4))
869 nntp++;
871 if(*bboard == '*')
872 bboard++;
874 if(!nntp){
876 * See if path portion looks newsgroup-ish while being aware
877 * of the "view" portion of the spec...
879 for(p = bboard; *p; p++)
880 if(*p == '['){
881 if(bracket) /* only one set allowed! */
882 break;
883 else
884 bracket++;
886 else if(*p == ']'){
887 if(bracket != 1) /* must be closing bracket */
888 break;
889 else
890 bracket++;
892 else if(!(isalnum((unsigned char) *p) || strindex(".-", *p)))
893 break;
896 snprintf(buf, buflen-(buf-origbuf), "%s%s%s",
897 (!nntp && *p) ? "#public" : "#news.",
898 (!nntp && *p && *bboard != '/') ? "/" : "",
899 bboard);
901 return(1);
904 return(0);
909 * build_folder_list - call mail_list to fetch us a list of folders
910 * from the given context.
912 void
913 build_folder_list(MAILSTREAM **stream, CONTEXT_S *context, char *pat, char *content, int flags)
915 MM_LIST_S ldata;
916 BFL_DATA_S response;
917 int local_open = 0, we_cancel = 0, resort = 0;
918 char reference[2*MAILTMPLEN], *p;
920 if(!(context->dir->status & CNTXT_NOFIND)
921 || (context->dir->status & CNTXT_PARTFIND))
922 return; /* find already done! */
924 dprint((7, "build_folder_list: %s %s\n",
925 context ? context->context : "NULL",
926 pat ? pat : "NULL"));
928 we_cancel = busy_cue(NULL, NULL, 1);
931 * Set up the pattern of folder name's to match within the
932 * given context.
934 if(!pat || ((*pat == '*' || *pat == '%') && *(pat+1) == '\0')){
935 context->dir->status &= ~CNTXT_NOFIND; /* let'em know we tried */
936 pat = context->dir->view.internal;
938 else
939 context->use |= CNTXT_PARTFIND; /* or are in a partial find */
941 memset(mm_list_info = &ldata, 0, sizeof(MM_LIST_S));
942 ldata.filter = (NEWS_TEST(context)) ? mail_lsub_filter : mail_list_filter;
943 memset(ldata.data = &response, 0, sizeof(BFL_DATA_S));
944 response.list = FOLDERS(context);
946 if(flags & BFL_FLDRONLY)
947 response.mask = LATT_NOSELECT;
950 * if context is associated with a server, prepare a stream for
951 * sending our request.
953 if(*context->context == '{'){
955 * Try using a stream we've already got open...
957 if(stream && *stream
958 && !(ldata.stream = same_stream(context->context, *stream))){
959 pine_mail_close(*stream);
960 *stream = NULL;
963 if(!ldata.stream)
964 ldata.stream = sp_stream_get(context->context, SP_MATCH);
966 if(!ldata.stream)
967 ldata.stream = sp_stream_get(context->context, SP_SAME);
969 /* gotta open a new one? */
970 if((F_OFF(F_CMBND_FOLDER_DISP, ps_global)
971 || context->update == LUU_INIT) && !ldata.stream){
972 ldata.stream = mail_cmd_stream(context, &local_open);
973 if(stream)
974 *stream = ldata.stream;
977 dprint((ldata.stream ? 7 : 1, "build_folder_list: mail_open(%s) %s.\n",
978 context->server ? context->server : "?",
979 ldata.stream ? "OK" : "FAILED"));
981 if(!ldata.stream){
982 context->use &= ~CNTXT_PARTFIND; /* unset partial find bit */
983 context->update = LUU_NOMORECHK;
984 if(we_cancel)
985 cancel_busy_cue(-1);
987 return;
990 else if(stream && *stream){ /* no server, simple case */
991 if(!sp_flagged(*stream, SP_LOCKED))
992 pine_mail_close(*stream);
994 *stream = NULL;
998 * If preset reference string, we're somewhere in the hierarchy.
999 * ELSE we must be at top of context...
1001 response.args.name = pat;
1002 if(!(response.args.reference = context->dir->ref)){
1003 if((p = strstr(context->context, "%s")) != NULL){
1004 strncpy(response.args.reference = reference,
1005 context->context,
1006 MIN(p - context->context, sizeof(reference)/2));
1007 reference[MIN(p - context->context, sizeof(reference)/2)] = '\0';
1008 if(*(p += 2))
1009 response.args.tail = p;
1011 else
1012 response.args.reference = context->context;
1015 if(flags & BFL_SCAN)
1016 mail_scan(ldata.stream, response.args.reference,
1017 response.args.name, content);
1018 else if(flags & BFL_LSUB)
1019 mail_lsub(ldata.stream, response.args.reference, response.args.name);
1020 else{
1021 set_read_predicted(1);
1022 pine_mail_list(ldata.stream, response.args.reference, response.args.name,
1023 &ldata.options);
1024 set_read_predicted(0);
1027 context->update = LUU_INIT;
1028 if(context->dir && response.response.delim)
1029 context->dir->delim = response.response.delim;
1031 if(!(flags & (BFL_LSUB|BFL_SCAN)) && F_ON(F_QUELL_EMPTY_DIRS, ps_global)){
1032 LISTRES_S listres;
1033 FOLDER_S *f;
1034 int i;
1036 for(i = 0; i < folder_total(response.list); i++)
1037 if((f = folder_entry(i, FOLDERS(context)))->isdir){
1039 * we don't need to do a list if we know already
1040 * whether there are children or not.
1042 if(!f->haschildren && !f->hasnochildren){
1043 memset(ldata.data = &listres, 0, sizeof(LISTRES_S));
1044 ldata.filter = mail_list_response;
1046 if(context->dir->ref)
1047 snprintf(reference, sizeof(reference), "%s%s", context->dir->ref, f->name);
1048 else
1049 context_apply(reference, context, f->name, sizeof(reference)/2);
1051 /* append the delimiter to the reference */
1052 for(p = reference; *p; p++)
1055 *p++ = context->dir->delim;
1056 *p = '\0';
1058 pine_mail_list(ldata.stream, reference, "%", NULL);
1060 /* anything interesting inside? */
1061 f->hasnochildren = (listres.count <= 1L);
1062 f->haschildren = !f->hasnochildren;;
1065 if(f->hasnochildren){
1066 if(f->isfolder){
1067 f->isdir = 0;
1068 if(ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_FIRST
1069 || ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_LAST)
1070 resort = 1;
1073 * We don't want to hide directories
1074 * that are only directories even if they are
1075 * empty. We only want to hide the directory
1076 * piece of a dual-use folder when there are
1077 * no children in the directory (and the user
1078 * most likely thinks of it as a folder instead
1079 * of a folder and a directory).
1081 else if(f->isdir && f->isdual){
1082 folder_delete(i, FOLDERS(context));
1083 i--;
1089 if(resort)
1090 resort_folder_list(response.list);
1092 if(local_open && !stream)
1093 pine_mail_close(ldata.stream);
1095 if(context->use & CNTXT_PRESRV)
1096 folder_select_restore(context);
1098 context->use &= ~CNTXT_PARTFIND; /* unset partial list bit */
1099 if(we_cancel)
1100 cancel_busy_cue(-1);
1105 * Validate LIST response to command issued from build_folder_list
1108 void
1109 mail_list_filter(MAILSTREAM *stream, char *mailbox, int delim, long int attribs, void *data, unsigned int options)
1111 BFL_DATA_S *ld = (BFL_DATA_S *) data;
1112 FOLDER_S *new_f = NULL, *dual_f = NULL;
1113 int suppress_folder_add = 0;
1115 if(!ld)
1116 return;
1118 if(delim)
1119 ld->response.delim = delim;
1121 /* test against mask of DIS-allowed attributes */
1122 if((ld->mask & attribs)){
1123 dprint((3, "mail_list_filter: failed attribute test"));
1124 return;
1128 * First, make sure response fits our "reference" arg
1129 * NOTE: build_folder_list can't supply breakout?
1131 if(!mail_list_in_collection(&mailbox, ld->args.reference,
1132 ld->args.name, ld->args.tail))
1133 return;
1135 /* ignore dotfolders unless told not to */
1136 if(F_OFF(F_ENABLE_DOT_FOLDERS, ps_global) && *mailbox == '.'){
1137 dprint((3, "mail_list_filter: dotfolder disallowed"));
1138 return;
1142 * If this response is INBOX we have to handle it specially.
1143 * The special cases are:
1145 * Incoming folders are enabled and this INBOX is in the Incoming
1146 * folders list already. We don't want to list it here, as well,
1147 * unless it is also a directory.
1149 * Incoming folders are not enabled, but we inserted INBOX into
1150 * this primary collection (with init_inbox_mapping()) so it is
1151 * already accounted for unless it is also a directory.
1153 * Incoming folders are not enabled, but we inserted INBOX into
1154 * the primary collection (which we are not looking at here) so
1155 * it is already accounted for there. We'll add it as a directory
1156 * as well here if that is what is called for.
1158 if(!strucmp(mailbox, ps_global->inbox_name)){
1159 int ftotal, i;
1160 FOLDER_S *f;
1161 char fullname[1000], tmp1[1000], tmp2[1000], *l1, *l2;
1163 /* fullname is the name of the mailbox in the list response */
1164 if(ld->args.reference){
1165 strncpy(fullname, ld->args.reference, sizeof(fullname)-1);
1166 fullname[sizeof(fullname)-1] = '\0';
1168 else
1169 fullname[0] = '\0';
1171 strncat(fullname, mailbox, sizeof(fullname)-strlen(fullname)-1);
1172 fullname[sizeof(fullname)-1] = '\0';
1174 /* check if Incoming Folders are enabled */
1175 if(ps_global->context_list && ps_global->context_list->use & CNTXT_INCMNG){
1176 int this_inbox_is_in_incoming = 0;
1179 * Figure out if this INBOX is already in the Incoming list.
1182 /* compare fullname to each incoming folder */
1183 ftotal = folder_total(FOLDERS(ps_global->context_list));
1184 for(i = 0; i < ftotal && !this_inbox_is_in_incoming; i++){
1185 f = folder_entry(i, FOLDERS(ps_global->context_list));
1186 if(f && f->name){
1187 if(same_remote_mailboxes(fullname, f->name)
1188 || ((!IS_REMOTE(fullname) && !IS_REMOTE(f->name))
1189 && (l1=mailboxfile(tmp1,fullname))
1190 && (l2=mailboxfile(tmp2,f->name))
1191 && !strcmp(l1,l2)))
1192 this_inbox_is_in_incoming++;
1196 if(this_inbox_is_in_incoming){
1198 * Don't add a folder for this, only a directory if called for.
1199 * If it isn't a directory, skip it.
1201 if(!(delim && !(attribs & LATT_NOINFERIORS)))
1202 return;
1204 suppress_folder_add++;
1207 else{
1208 int inbox_is_in_this_collection = 0;
1210 /* is INBOX already in this folder list? */
1211 ftotal = folder_total(ld->list);
1212 for(i = 0; i < ftotal && !inbox_is_in_this_collection; i++){
1213 f = folder_entry(i, ld->list);
1214 if(!strucmp(FLDR_NAME(f), ps_global->inbox_name))
1215 inbox_is_in_this_collection++;
1218 if(inbox_is_in_this_collection){
1221 * Inbox is already inserted in this collection. Unless
1222 * it is also a directory, we are done.
1224 if(!(delim && !(attribs & LATT_NOINFERIORS)))
1225 return;
1228 * Since it is also a directory, what we do depends on
1229 * the F_SEP feature. If that feature is not set we just
1230 * want to mark the existing entry dual-use. If F_SEP is
1231 * set we want to add a new directory entry.
1233 if(F_ON(F_SEPARATE_FLDR_AS_DIR, ps_global)){
1234 f->isdir = 0;
1235 f->haschildren = 0;
1236 f->hasnochildren = 0;
1237 /* fall through and add a new directory */
1239 else{
1240 /* mark existing entry dual-use and return */
1241 ld->response.count++;
1242 ld->response.isdir = 1;
1243 f->isdir = 1;
1244 if(attribs & LATT_HASCHILDREN)
1245 f->haschildren = 1;
1247 if(attribs & LATT_HASNOCHILDREN)
1248 f->hasnochildren = 1;
1250 return;
1253 suppress_folder_add++;
1255 else{
1256 int found_it = 0;
1257 int this_inbox_is_primary_inbox = 0;
1260 * See if this INBOX is the same as the INBOX we inserted
1261 * in the primary collection.
1264 /* first find the existing INBOX entry */
1265 ftotal = folder_total(FOLDERS(ps_global->context_list));
1266 for(i = 0; i < ftotal && !found_it; i++){
1267 f = folder_entry(i, FOLDERS(ps_global->context_list));
1268 if(!strucmp(FLDR_NAME(f), ps_global->inbox_name))
1269 found_it++;
1272 if(found_it && f && f->name){
1273 if(same_remote_mailboxes(fullname, f->name)
1274 || ((!IS_REMOTE(fullname) && !IS_REMOTE(f->name))
1275 && (l1=mailboxfile(tmp1,fullname))
1276 && (l2=mailboxfile(tmp2,f->name))
1277 && !strcmp(l1,l2)))
1278 this_inbox_is_primary_inbox++;
1281 if(this_inbox_is_primary_inbox){
1283 * Don't add a folder for this, only a directory if called for.
1284 * If it isn't a directory, skip it.
1286 if(!(delim && !(attribs & LATT_NOINFERIORS)))
1287 return;
1289 suppress_folder_add++;
1295 /* is it a mailbox? */
1296 if(!(attribs & LATT_NOSELECT) && !suppress_folder_add){
1297 ld->response.count++;
1298 ld->response.isfile = 1;
1299 new_f = new_folder(mailbox, 0);
1300 new_f->isfolder = 1;
1302 if(F_ON(F_SEPARATE_FLDR_AS_DIR, ps_global)){
1303 folder_insert(-1, new_f, ld->list);
1304 dual_f = new_f;
1305 new_f = NULL;
1309 /* directory? */
1310 if(delim && !(attribs & LATT_NOINFERIORS)){
1311 ld->response.count++;
1312 ld->response.isdir = 1;
1314 if(!new_f)
1315 new_f = new_folder(mailbox, 0);
1317 new_f->isdir = 1;
1318 if(attribs & LATT_HASCHILDREN)
1319 new_f->haschildren = 1;
1320 if(attribs & LATT_HASNOCHILDREN)
1321 new_f->hasnochildren = 1;
1324 * When we have F_SEPARATE_FLDR_AS_DIR we still want to know
1325 * whether the name really represents both so that we don't
1326 * inadvertently delete both when the user meant one or the
1327 * other.
1329 if(dual_f){
1330 if(attribs & LATT_HASCHILDREN)
1331 dual_f->haschildren = 1;
1333 if(attribs & LATT_HASNOCHILDREN)
1334 dual_f->hasnochildren = 1;
1336 dual_f->isdual = 1;
1337 new_f->isdual = 1;
1341 if(new_f)
1342 folder_insert(-1, new_f, ld->list);
1344 if(attribs & LATT_MARKED)
1345 ld->response.ismarked = 1;
1347 /* don't mark #move folders unmarked */
1348 if(attribs & LATT_UNMARKED && !(options & PML_IS_MOVE_MBOX))
1349 ld->response.unmarked = 1;
1351 if(attribs & LATT_HASCHILDREN)
1352 ld->response.haschildren = 1;
1354 if(attribs & LATT_HASNOCHILDREN)
1355 ld->response.hasnochildren = 1;
1360 * Validate LSUB response to command issued from build_folder_list
1363 void
1364 mail_lsub_filter(MAILSTREAM *stream, char *mailbox, int delim, long int attribs,
1365 void *data, unsigned int options)
1367 BFL_DATA_S *ld = (BFL_DATA_S *) data;
1368 FOLDER_S *new_f = NULL;
1369 char *ref;
1371 if(delim)
1372 ld->response.delim = delim;
1374 /* test against mask of DIS-allowed attributes */
1375 if((ld->mask & attribs)){
1376 dprint((3, "mail_lsub_filter: failed attribute test"));
1377 return;
1380 /* Normalize mailbox and reference strings re: namespace */
1381 if(!strncmp(mailbox, "#news.", 6))
1382 mailbox += 6;
1384 if(!strncmp(ref = ld->args.reference, "#news.", 6))
1385 ref += 6;
1387 if(!mail_list_in_collection(&mailbox, ref, ld->args.name, ld->args.tail))
1388 return;
1390 if(!(attribs & LATT_NOSELECT)){
1391 ld->response.count++;
1392 ld->response.isfile = 1;
1393 new_f = new_folder(mailbox, 0);
1394 new_f->isfolder = 1;
1395 folder_insert(F_ON(F_READ_IN_NEWSRC_ORDER, ps_global)
1396 ? folder_total(ld->list) : -1,
1397 new_f, ld->list);
1400 /* We don't support directories in #news */
1403 /* human readable name for a folder. memory freed by caller
1404 * This is a jacket to conversion from modified utf7 to utf8.
1406 unsigned char *folder_name_decoded(unsigned char *mailbox)
1408 unsigned char *s;
1409 s = (unsigned char *) utf8_from_mutf7((unsigned char *) mailbox);
1410 if (s == NULL) s = (unsigned char *) cpystr((char *)mailbox);
1411 return s;
1414 /* mutf7 encoded name of a folder, from its name in utf8.
1415 * memory freed by caller.
1417 unsigned char *folder_name_encoded(unsigned char *mailbox)
1419 unsigned char *s;
1420 if ((s = utf8_to_mutf7(mailbox)) == NULL)
1421 s = (unsigned char *) cpystr((char *) mailbox);
1422 return s;
1426 mail_list_in_collection(char **mailbox, char *ref, char *name, char *tail)
1428 int boxlen, reflen, taillen;
1429 char *p;
1431 boxlen = strlen(*mailbox);
1432 reflen = ref ? strlen(ref) : 0;
1433 taillen = tail ? strlen(tail) : 0;
1435 if(boxlen
1436 && (reflen ? !struncmp(*mailbox, ref, reflen)
1437 : (p = strpbrk(name, "%*"))
1438 ? !struncmp(*mailbox, name, p - name)
1439 : !strucmp(*mailbox,name))
1440 && (!taillen
1441 || (taillen < boxlen - reflen
1442 && !strucmp(&(*mailbox)[boxlen - taillen], tail)))){
1443 if(taillen)
1444 (*mailbox)[boxlen - taillen] = '\0';
1446 if(*(*mailbox += reflen))
1447 return(TRUE);
1450 * else don't worry about context "breakouts" since
1451 * build_folder_list doesn't let the user introduce
1452 * one...
1455 return(FALSE);
1460 * rebuild_folder_list -- free up old list and re-issue commands to build
1461 * a new list.
1463 void
1464 refresh_folder_list(CONTEXT_S *context, int nodirs, int startover, MAILSTREAM **streamp)
1466 if(startover)
1467 free_folder_list(context);
1469 build_folder_list(streamp, context, NULL, NULL,
1470 (NEWS_TEST(context) ? BFL_LSUB : BFL_NONE)
1471 | ((nodirs) ? BFL_FLDRONLY : BFL_NONE));
1476 * free_folder_list - loop thru the context's lists of folders
1477 * clearing all entries without nicknames
1478 * (as those were user provided) AND reset the
1479 * context's find flag.
1481 * NOTE: if fetched() information (e.g., like message counts come back
1482 * in bboard collections), we may want to have a check before
1483 * executing the loop and setting the FIND flag.
1485 void
1486 free_folder_list(CONTEXT_S *cntxt)
1488 int n, i;
1491 * In this case, don't blast the list as it was given to us by the
1492 * user and not the result of a mail_list call...
1494 if(cntxt->use & CNTXT_INCMNG)
1495 return;
1497 if(cntxt->use & CNTXT_PRESRV)
1498 folder_select_preserve(cntxt);
1500 for(n = folder_total(FOLDERS(cntxt)), i = 0; n > 0; n--)
1501 if(folder_entry(i, FOLDERS(cntxt))->nickname)
1502 i++; /* entry wasn't from LIST */
1503 else
1504 folder_delete(i, FOLDERS(cntxt));
1506 cntxt->dir->status |= CNTXT_NOFIND; /* do find next time... */
1507 /* or add the fake entry */
1508 cntxt->use &= ~(CNTXT_PSEUDO | CNTXT_PRESRV | CNTXT_ZOOM);
1513 * default_save_context - return the default context for saved messages
1515 CONTEXT_S *
1516 default_save_context(CONTEXT_S *cntxt)
1518 while(cntxt)
1519 if((cntxt->use) & CNTXT_SAVEDFLT)
1520 return(cntxt);
1521 else
1522 cntxt = cntxt->next;
1524 return(NULL);
1530 * folder_complete - foldername completion routine
1532 * Result: returns 0 if the folder doesn't have a any completion
1533 * 1 if the folder has a completion (*AND* "name" is
1534 * replaced with the completion)
1538 folder_complete(CONTEXT_S *context, char *name, size_t namelen, int *completions)
1540 return(folder_complete_internal(context, name, namelen, completions, FC_NONE));
1548 folder_complete_internal(CONTEXT_S *context, char *name, size_t namelen,
1549 int *completions, int flags)
1551 int i, match = -1, ftotal;
1552 char tmp[MAXFOLDER+2], *a, *b, *fn, *pat;
1553 FOLDER_S *f;
1555 if(completions)
1556 *completions = 0;
1558 if(*name == '\0' || !context_isambig(name))
1559 return(0);
1561 if(!((context->use & CNTXT_INCMNG) || ALL_FOUND(context))){
1563 * Build the folder list from scratch since we may need to
1564 * traverse hierarchy...
1567 free_folder_list(context);
1568 snprintf(tmp, sizeof(tmp), "%s%c", name, NEWS_TEST(context) ? '*' : '%');
1569 build_folder_list(NULL, context, tmp, NULL,
1570 (NEWS_TEST(context) & !(flags & FC_FORCE_LIST))
1571 ? BFL_LSUB : BFL_NONE);
1574 *tmp = '\0'; /* find uniq substring */
1575 ftotal = folder_total(FOLDERS(context));
1576 for(i = 0; i < ftotal; i++){
1577 f = folder_entry(i, FOLDERS(context));
1578 fn = FLDR_NAME(f);
1579 pat = name;
1580 if(!(NEWS_TEST(context) || (context->use & CNTXT_INCMNG))){
1581 fn = folder_last_cmpnt(fn, context->dir->delim);
1582 pat = folder_last_cmpnt(pat, context->dir->delim);
1585 if(!strncmp(fn, pat, strlen(pat))){
1586 if(match != -1){ /* oh well, do best we can... */
1587 a = fn;
1588 if(match >= 0){
1589 f = folder_entry(match, FOLDERS(context));
1590 fn = FLDR_NAME(f);
1591 if(!NEWS_TEST(context))
1592 fn = folder_last_cmpnt(fn, context->dir->delim);
1594 strncpy(tmp, fn, sizeof(tmp)-1);
1595 tmp[sizeof(tmp)-1] = '\0';
1598 match = -2;
1599 b = tmp; /* remember largest common text */
1600 while(*a && *b && *a == *b)
1601 *b++ = *a++;
1603 *b = '\0';
1605 else
1606 match = i; /* bingo?? */
1610 if(match >= 0){ /* found! */
1611 f = folder_entry(match, FOLDERS(context));
1612 fn = FLDR_NAME(f);
1613 if(!(NEWS_TEST(context) || (context->use & CNTXT_INCMNG)))
1614 fn = folder_last_cmpnt(fn, context->dir->delim);
1616 strncpy(pat, fn, namelen-(pat-name));
1617 name[namelen-1] = '\0';
1618 if(f->isdir && !f->isfolder){
1619 name[i = strlen(name)] = context->dir->delim;
1620 name[i+1] = '\0';
1623 else if(match == -2){ /* closest we could find */
1624 strncpy(pat, tmp, namelen-(pat-name));
1625 name[namelen-1] = '\0';
1628 if(completions)
1629 *completions = ftotal;
1631 if(!((context->use & CNTXT_INCMNG) || ALL_FOUND(context)))
1632 free_folder_list(context);
1634 return((match >= 0) ? ftotal : 0);
1638 char *
1639 folder_last_cmpnt(char *s, int d)
1641 register char *p;
1643 if(d)
1644 for(p = s; (p = strindex(p, d)); s = ++p)
1647 return(s);
1652 * init_folder_entries - return a piece of memory suitable for attaching
1653 * a list of folders...
1656 FLIST *
1657 init_folder_entries(void)
1659 FLIST *flist = (FLIST *) fs_get(sizeof(FLIST));
1660 flist->folders = (FOLDER_S **) fs_get(FCHUNK * sizeof(FOLDER_S *));
1661 memset((void *)flist->folders, 0, (FCHUNK * sizeof(FOLDER_S *)));
1662 flist->allocated = FCHUNK;
1663 flist->used = 0;
1664 return(flist);
1669 * new_folder - return a brand new folder entry, with the given name
1670 * filled in.
1672 * NOTE: THIS IS THE ONLY WAY TO PUT A NAME INTO A FOLDER ENTRY!!!
1673 * STRCPY WILL NOT WORK!!!
1675 FOLDER_S *
1676 new_folder(char *name, long unsigned int hash)
1678 FOLDER_S *tmp;
1679 size_t l = strlen(name);
1681 tmp = (FOLDER_S *)fs_get(sizeof(FOLDER_S) + (l * sizeof(char)));
1682 memset((void *)tmp, 0, sizeof(FOLDER_S));
1683 strncpy(tmp->name, name, l);
1684 tmp->name[l] = '\0';
1685 tmp->name_len = (unsigned char) l;
1686 tmp->varhash = hash;
1687 return(tmp);
1692 * folder_entry - folder struct access routine. Permit reference to
1693 * folder structs via index number. Serves two purposes:
1694 * 1) easy way for callers to access folder data
1695 * conveniently
1696 * 2) allows for a custom manager to limit memory use
1697 * under certain rather limited "operating systems"
1698 * who shall renameless, but whose initials are DOS
1702 FOLDER_S *
1703 folder_entry(int i, FLIST *flist)
1705 return((i >= flist->used) ? NULL:flist->folders[i]);
1710 * free_folder_entries - release all resources associated with the given
1711 * list of folder entries
1713 void
1714 free_folder_entries(FLIST **flist)
1716 register int i;
1718 if(!(flist && *flist))
1719 return;
1721 i = (*flist)->used;
1722 while(i--){
1723 if((*flist)->folders[i]->nickname)
1724 fs_give((void **) &(*flist)->folders[i]->nickname);
1726 fs_give((void **) &((*flist)->folders[i]));
1729 fs_give((void **) &((*flist)->folders));
1730 fs_give((void **) flist);
1735 * return the number of folders associated with the given folder list
1738 folder_total(FLIST *flist)
1740 return((int) flist->used);
1745 * return the index number of the given name in the given folder list
1748 folder_index(char *name, CONTEXT_S *cntxt, int flags)
1750 register int i = 0;
1751 FOLDER_S *f;
1752 char *fname;
1754 for(i = 0; (f = folder_entry(i, FOLDERS(cntxt))); i++)
1755 if(((flags & FI_FOLDER) && (f->isfolder || (cntxt->use & CNTXT_INCMNG)))
1756 || ((flags & FI_DIR) && f->isdir)){
1757 fname = FLDR_NAME(f);
1758 #if defined(DOS) || defined(OS2)
1759 if(flags & FI_RENAME){ /* case-dependent for rename */
1760 if(*name == *fname && strcmp(name, fname) == 0)
1761 return(i);
1763 else{
1764 if(toupper((unsigned char)(*name))
1765 == toupper((unsigned char)(*fname)) && strucmp(name, fname) == 0)
1766 return(i);
1768 #else
1769 if(*name == *fname && strcmp(name, fname) == 0)
1770 return(i);
1771 #endif
1774 return(-1);
1779 * folder_is_nick - check to see if the given name is a nickname
1780 * for some folder in the given context...
1782 * NOTE: no need to check if mm_list_names has been done as
1783 * nicknames can only be set by configuration...
1785 char *
1786 folder_is_nick(char *nickname, FLIST *flist, int flags)
1788 register int i = 0;
1789 FOLDER_S *f;
1791 if(!(nickname && *nickname && flist))
1792 return(NULL);
1794 while((f = folder_entry(i, flist)) != NULL){
1796 * The second part of the OR is checking in a case-indep
1797 * way for INBOX. It should be restricted to the context
1798 * to which we add the INBOX folder, which would be either
1799 * the Incoming Folders collection or the first collection
1800 * if there is no Incoming collection. We don't need to check
1801 * the collection because nickname assignment has already
1802 * done that for us. Most folders don't have nicknames, only
1803 * incoming folders and folders like inbox if not in incoming.
1805 if(f->nickname
1806 && (!strcmp(nickname, f->nickname)
1807 || (!strucmp(nickname, f->nickname)
1808 && !strucmp(nickname, ps_global->inbox_name)))){
1809 char source[MAILTMPLEN], *target = NULL;
1812 * If f is a maildrop, then we want to return the
1813 * destination folder, not the whole #move thing.
1815 if(!(flags & FN_WHOLE_NAME)
1816 && check_for_move_mbox(f->name, source, sizeof(source), &target))
1817 return(target);
1818 else
1819 return(f->name);
1821 else
1822 i++;
1825 return(NULL);
1829 char *
1830 folder_is_target_of_nick(char *longname, CONTEXT_S *cntxt)
1832 register int i = 0;
1833 FOLDER_S *f;
1834 FLIST *flist = NULL;
1836 if(cntxt && cntxt == ps_global->context_list)
1837 flist = FOLDERS(cntxt);
1839 if(!(longname && *longname && flist))
1840 return(NULL);
1842 while((f = folder_entry(i, flist)) != NULL){
1843 if(f->nickname && f->name && !strcmp(longname, f->name))
1844 return(f->nickname);
1845 else
1846 i++;
1849 return(NULL);
1853 /*----------------------------------------------------------------------
1854 Insert the given folder name into the sorted folder list
1855 associated with the given context. Only allow ambiguous folder
1856 names IF associated with a nickname.
1858 Args: index -- Index to insert at, OR insert in sorted order if -1
1859 folder -- folder structure to insert into list
1860 flist -- folder list to insert folder into
1862 **** WARNING ****
1863 DON'T count on the folder pointer being valid after this returns
1864 *** ALL FOLDER ELEMENT READS SHOULD BE THROUGH folder_entry() ***
1866 ----*/
1868 folder_insert(int index, FOLDER_S *folder, FLIST *flist)
1870 /* requested index < 0 means add to sorted list */
1871 if(index < 0 && (index = folder_total(flist)) > 0)
1872 index = folder_insert_sorted(index / 2, 0, index, folder, flist,
1873 (ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_FIRST)
1874 ? compare_folders_dir_alpha
1875 : (ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_LAST)
1876 ? compare_folders_alpha_dir
1877 : compare_folders_alpha);
1879 folder_insert_index(folder, index, flist);
1880 return(index);
1885 * folder_insert_sorted - Insert given folder struct into given list
1886 * observing sorting specified by given
1887 * comparison function
1890 folder_insert_sorted(int index, int min_index, int max_index, FOLDER_S *folder,
1891 FLIST *flist, int (*compf)(FOLDER_S *, FOLDER_S *))
1893 int i;
1895 return(((i = (*compf)(folder_entry(index, flist), folder)) == 0)
1896 ? index
1897 : (i < 0)
1898 ? ((++index >= max_index)
1899 ? max_index
1900 : ((*compf)(folder_entry(index, flist), folder) > 0)
1901 ? index
1902 : folder_insert_sorted((index + max_index) / 2, index,
1903 max_index, folder, flist, compf))
1904 : ((index <= min_index)
1905 ? min_index
1906 : folder_insert_sorted((min_index + index) / 2, min_index, index,
1907 folder, flist, compf)));
1912 * folder_insert_index - Insert the given folder struct into the global list
1913 * at the given index.
1915 void
1916 folder_insert_index(FOLDER_S *folder, int index, FLIST *flist)
1918 register FOLDER_S **flp, **iflp;
1920 /* if index is beyond size, place at end of list */
1921 index = MIN(index, flist->used);
1923 /* grow array ? */
1924 if(flist->used + 1 > flist->allocated){
1925 flist->allocated += FCHUNK;
1926 fs_resize((void **)&(flist->folders),
1927 flist->allocated * sizeof(FOLDER_S *));
1930 /* shift array left */
1931 iflp = &((flist->folders)[index]);
1932 for(flp = &((flist->folders)[flist->used]);
1933 flp > iflp; flp--)
1934 flp[0] = flp[-1];
1936 flist->folders[index] = folder;
1937 flist->used += 1;
1941 void
1942 resort_folder_list(FLIST *flist)
1944 if(flist && folder_total(flist) > 1 && flist->folders)
1945 qsort(flist->folders, folder_total(flist), sizeof(flist->folders[0]),
1946 (ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_FIRST)
1947 ? compare_folders_dir_alpha_qsort
1948 : (ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_LAST)
1949 ? compare_folders_alpha_dir_qsort
1950 : compare_folders_alpha_qsort);
1954 /*----------------------------------------------------------------------
1955 Removes a folder at the given index in the given context's
1956 list.
1958 Args: index -- Index in folder list of folder to be removed
1959 flist -- folder list
1960 ----*/
1961 void
1962 folder_delete(int index, FLIST *flist)
1964 register int i;
1965 FOLDER_S *f;
1967 if(flist->used
1968 && (index < 0 || index >= flist->used))
1969 return; /* bogus call! */
1971 if((f = folder_entry(index, flist))->nickname)
1972 fs_give((void **)&(f->nickname));
1974 fs_give((void **) &(flist->folders[index]));
1975 for(i = index; i < flist->used - 1; i++)
1976 flist->folders[i] = flist->folders[i+1];
1979 flist->used -= 1;
1983 /*----------------------------------------------------------------------
1984 compare two names for qsort, case independent
1986 Args: pointers to strings to compare
1988 Result: integer result of strcmp of the names. Uses simple
1989 efficiency hack to speed the string comparisons up a bit.
1991 ----------------------------------------------------------------------*/
1993 compare_names(const qsort_t *x, const qsort_t *y)
1995 char *a = *(char **)x, *b = *(char **)y;
1996 int r;
1997 #define CMPI(X,Y) ((X)[0] - (Y)[0])
1998 #define UCMPI(X,Y) ((isupper((unsigned char)((X)[0])) \
1999 ? (X)[0] - 'A' + 'a' : (X)[0]) \
2000 - (isupper((unsigned char)((Y)[0])) \
2001 ? (Y)[0] - 'A' + 'a' : (Y)[0]))
2003 /*---- Inbox always sorts to the top ----*/
2004 if(UCMPI(a, ps_global->inbox_name) == 0
2005 && strucmp(a, ps_global->inbox_name) == 0)
2006 return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : -1);
2007 else if((UCMPI(b, ps_global->inbox_name)) == 0
2008 && strucmp(b, ps_global->inbox_name) == 0)
2009 return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : 1);
2011 /*----- The sent-mail folder, is always next unless... ---*/
2012 else if(F_OFF(F_SORT_DEFAULT_FCC_ALPHA, ps_global)
2013 && CMPI(a, ps_global->VAR_DEFAULT_FCC) == 0
2014 && strcmp(a, ps_global->VAR_DEFAULT_FCC) == 0)
2015 return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : -1);
2016 else if(F_OFF(F_SORT_DEFAULT_FCC_ALPHA, ps_global)
2017 && CMPI(b, ps_global->VAR_DEFAULT_FCC) == 0
2018 && strcmp(b, ps_global->VAR_DEFAULT_FCC) == 0)
2019 return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : 1);
2021 /*----- The saved-messages folder, is always next unless... ---*/
2022 else if(F_OFF(F_SORT_DEFAULT_SAVE_ALPHA, ps_global)
2023 && CMPI(a, ps_global->VAR_DEFAULT_SAVE_FOLDER) == 0
2024 && strcmp(a, ps_global->VAR_DEFAULT_SAVE_FOLDER) == 0)
2025 return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : -1);
2026 else if(F_OFF(F_SORT_DEFAULT_SAVE_ALPHA, ps_global)
2027 && CMPI(b, ps_global->VAR_DEFAULT_SAVE_FOLDER) == 0
2028 && strcmp(b, ps_global->VAR_DEFAULT_SAVE_FOLDER) == 0)
2029 return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : 1);
2031 else
2032 return((r = CMPI(a, b)) ? r : strcmp(a, b));
2036 /*----------------------------------------------------------------------
2037 compare two folder structs for ordering alphabetically
2039 Args: pointers to folder structs to compare
2041 Result: integer result of dir-bit and strcmp of the folders.
2042 ----------------------------------------------------------------------*/
2044 compare_folders_alpha(FOLDER_S *f1, FOLDER_S *f2)
2046 int i;
2047 char *f1name = FLDR_NAME(f1),
2048 *f2name = FLDR_NAME(f2);
2050 return(((i = compare_names(&f1name, &f2name)) != 0)
2051 ? i : (f2->isdir - f1->isdir));
2056 compare_folders_alpha_qsort(const qsort_t *a1, const qsort_t *a2)
2058 FOLDER_S *f1 = *((FOLDER_S **) a1);
2059 FOLDER_S *f2 = *((FOLDER_S **) a2);
2061 return(compare_folders_alpha(f1, f2));
2065 /*----------------------------------------------------------------------
2066 compare two folder structs alphabetically with dirs first
2068 Args: pointers to folder structs to compare
2070 Result: integer result of dir-bit and strcmp of the folders.
2071 ----------------------------------------------------------------------*/
2073 compare_folders_dir_alpha(FOLDER_S *f1, FOLDER_S *f2)
2075 int i;
2077 if((i = (f2->isdir - f1->isdir)) == 0){
2078 char *f1name = FLDR_NAME(f1),
2079 *f2name = FLDR_NAME(f2);
2081 return(compare_names(&f1name, &f2name));
2084 return(i);
2089 compare_folders_dir_alpha_qsort(const qsort_t *a1, const qsort_t *a2)
2091 FOLDER_S *f1 = *((FOLDER_S **) a1);
2092 FOLDER_S *f2 = *((FOLDER_S **) a2);
2094 return(compare_folders_dir_alpha(f1, f2));
2098 /*----------------------------------------------------------------------
2099 compare two folder structs alphabetically with dirs last
2101 Args: pointers to folder structs to compare
2103 Result: integer result of dir-bit and strcmp of the folders.
2104 ----------------------------------------------------------------------*/
2106 compare_folders_alpha_dir(FOLDER_S *f1, FOLDER_S *f2)
2108 int i;
2110 if((i = (f1->isdir - f2->isdir)) == 0){
2111 char *f1name = FLDR_NAME(f1),
2112 *f2name = FLDR_NAME(f2);
2114 return(compare_names(&f1name, &f2name));
2117 return(i);
2122 compare_folders_alpha_dir_qsort(const qsort_t *a1, const qsort_t *a2)
2124 FOLDER_S *f1 = *((FOLDER_S **) a1);
2125 FOLDER_S *f2 = *((FOLDER_S **) a2);
2127 return(compare_folders_alpha_dir(f1, f2));
2132 * Find incoming folders and update the unseen counts
2133 * if necessary.
2135 void
2136 folder_unseen_count_updater(unsigned long flags)
2138 CONTEXT_S *ctxt;
2139 time_t oldest, started_checking;
2140 int ftotal, i, first = -1;
2141 FOLDER_S *f;
2144 * We would only do this if there is an incoming collection, the
2145 * user wants us to monitor, and we're in the folder screen.
2147 if(ps_global->in_folder_screen && F_ON(F_ENABLE_INCOMING_CHECKING, ps_global)
2148 && (ctxt=ps_global->context_list) && ctxt->use & CNTXT_INCMNG
2149 && (ftotal = folder_total(FOLDERS(ctxt)))){
2151 * Search through the last_unseen_update times to find
2152 * the one that was updated longest ago, and start with
2153 * that one. We don't want to delay long doing these
2154 * checks so may only check some of them each time we
2155 * get called. An update time equal to 1 means don't check
2156 * this folder at all.
2158 for(i = 0; i < ftotal; i++){
2159 f = folder_entry(i, FOLDERS(ctxt));
2160 if(f && LUU_YES(f->last_unseen_update)){
2161 first = i;
2162 oldest = f->last_unseen_update;
2163 break;
2168 * Now first is the first in the list that
2169 * should ever be checked. Next find the
2170 * one that should be checked next, the one
2171 * that was checked longest ago.
2173 if(first >= 0){
2174 for(i = 1; i < ftotal; i++){
2175 f = folder_entry(i, FOLDERS(ctxt));
2176 if(f && LUU_YES(f->last_unseen_update) && f->last_unseen_update < oldest){
2177 first = i;
2178 oldest = f->last_unseen_update;
2183 /* now first is the next one to be checked */
2185 started_checking = time(0);
2187 for(i = first; i < ftotal; i++){
2188 /* update the next one */
2189 f = folder_entry(i, FOLDERS(ctxt));
2190 if(f && LUU_YES(f->last_unseen_update)
2191 && (flags & UFU_FORCE
2192 /* or it's been long enough and we've not been in this function too long */
2193 || (((time(0) - f->last_unseen_update) >= ps_global->inc_check_interval)
2194 && ((time(0) - started_checking) < MIN(4,ps_global->inc_check_timeout)))))
2195 update_folder_unseen(f, ctxt, flags, NULL);
2198 for(i = 0; i < first; i++){
2199 f = folder_entry(i, FOLDERS(ctxt));
2200 if(f && LUU_YES(f->last_unseen_update)
2201 && (flags & UFU_FORCE
2202 || (((time(0) - f->last_unseen_update) >= ps_global->inc_check_interval)
2203 && ((time(0) - started_checking) < MIN(4,ps_global->inc_check_timeout)))))
2204 update_folder_unseen(f, ctxt, flags, NULL);
2211 * Update the count of unseen in the FOLDER_S struct
2212 * for this folder. This will update if the time
2213 * interval has passed or if the FORCE flag is set.
2215 void
2216 update_folder_unseen(FOLDER_S *f, CONTEXT_S *ctxt, unsigned long flags,
2217 MAILSTREAM *this_is_the_stream)
2219 time_t now;
2220 int orig_valid;
2221 int use_imap_interval = 0;
2222 int stream_is_open = 0;
2223 unsigned long orig_unseen, orig_new, orig_tot;
2224 char mailbox_name[MAILTMPLEN];
2225 char *target = NULL;
2226 DRIVER *d;
2228 if(!f || !LUU_YES(f->last_unseen_update))
2229 return;
2231 now = time(0);
2232 context_apply(mailbox_name, ctxt, f->name, MAILTMPLEN);
2234 if(!mailbox_name[0])
2235 return;
2237 if(check_for_move_mbox(mailbox_name, NULL, 0, &target)){
2238 MAILSTREAM *strm;
2241 * If this maildrop is the currently open stream use that.
2242 * I'm not altogether sure that this is a good way to
2243 * check this.
2245 if(target
2246 && ((strm=ps_global->mail_stream)
2247 && strm->snarf.name
2248 && (!strcmp(target,strm->mailbox)
2249 || !strcmp(target,strm->original_mailbox)))){
2250 stream_is_open++;
2253 else{
2254 MAILSTREAM *m = NULL;
2256 stream_is_open = (this_is_the_stream
2257 || (m=sp_stream_get(mailbox_name, SP_MATCH | SP_RO_OK))
2258 || ((m=ps_global->mail_stream) && !sp_dead_stream(m)
2259 && same_stream_and_mailbox(mailbox_name,m))
2260 || (!IS_REMOTE(mailbox_name)
2261 && (m=already_open_stream(mailbox_name, AOS_NONE)))) ? 1 : 0;
2263 if(stream_is_open){
2264 if(!this_is_the_stream)
2265 this_is_the_stream = m;
2267 else{
2269 * If it's IMAP or local we use a shorter interval.
2271 d = mail_valid(NIL, mailbox_name, (char *) NIL);
2272 if((d && !strcmp(d->name, "imap")) || !IS_REMOTE(mailbox_name))
2273 use_imap_interval++;
2278 * Update if forced, or if it's been a while, or if we have a
2279 * stream open to this mailbox already.
2281 if(flags & UFU_FORCE
2282 || stream_is_open
2283 || ((use_imap_interval
2284 && (now - f->last_unseen_update) >= ps_global->inc_check_interval)
2285 || ((now - f->last_unseen_update) >= ps_global->inc_second_check_interval))){
2286 unsigned long tot, uns, new;
2287 unsigned long *totp = NULL, *unsp = NULL, *newp = NULL;
2289 orig_valid = f->unseen_valid;
2290 orig_unseen = f->unseen;
2291 orig_new = f->new;
2292 orig_tot = f->total;
2294 if(F_ON(F_INCOMING_CHECKING_RECENT, ps_global))
2295 newp = &new;
2296 else
2297 unsp = &uns;
2299 if(F_ON(F_INCOMING_CHECKING_TOTAL, ps_global))
2300 totp = &tot;
2302 f->unseen_valid = 0;
2304 dprint((9, "update_folder_unseen(%s)", FLDR_NAME(f)));
2305 if(get_recent_in_folder(mailbox_name, newp, unsp, totp, this_is_the_stream)){
2306 f->last_unseen_update = time(0);
2307 f->unseen_valid = 1;
2308 if(unsp)
2309 f->unseen = uns;
2311 if(newp)
2312 f->new = new;
2314 if(totp)
2315 f->total = tot;
2317 if(!orig_valid){
2318 dprint((9, "update_folder_unseen(%s): original: %s%s%s%s",
2319 FLDR_NAME(f),
2320 F_ON(F_INCOMING_CHECKING_RECENT,ps_global) ? "new=" : "unseen=",
2321 F_ON(F_INCOMING_CHECKING_RECENT,ps_global) ? comatose(f->new) : comatose(f->unseen),
2322 F_ON(F_INCOMING_CHECKING_TOTAL,ps_global) ? " tot=" : "",
2323 F_ON(F_INCOMING_CHECKING_TOTAL,ps_global) ? comatose(f->total) : ""));
2326 if(orig_valid
2327 && ((F_ON(F_INCOMING_CHECKING_RECENT, ps_global)
2328 && orig_new != f->new)
2330 (F_OFF(F_INCOMING_CHECKING_RECENT, ps_global)
2331 && orig_unseen != f->unseen)
2333 (F_ON(F_INCOMING_CHECKING_TOTAL, ps_global)
2334 && orig_tot != f->total))){
2336 if(ps_global->in_folder_screen)
2337 ps_global->noticed_change_in_unseen = 1;
2339 dprint((9, "update_folder_unseen(%s): changed: %s%s%s%s",
2340 FLDR_NAME(f),
2341 F_ON(F_INCOMING_CHECKING_RECENT,ps_global) ? "new=" : "unseen=",
2342 F_ON(F_INCOMING_CHECKING_RECENT,ps_global) ? comatose(f->new) : comatose(f->unseen),
2343 F_ON(F_INCOMING_CHECKING_TOTAL,ps_global) ? " tot=" : "",
2344 F_ON(F_INCOMING_CHECKING_TOTAL,ps_global) ? comatose(f->total) : ""));
2346 if(flags & UFU_ANNOUNCE
2347 && ((F_ON(F_INCOMING_CHECKING_RECENT, ps_global)
2348 && orig_new < f->new)
2350 (F_OFF(F_INCOMING_CHECKING_RECENT, ps_global)
2351 && orig_unseen < f->unseen))){
2352 if(F_ON(F_INCOMING_CHECKING_RECENT, ps_global))
2353 q_status_message3(SM_ASYNC, 1, 3, "%s: %s %s",
2354 FLDR_NAME(f), comatose(f->new),
2355 _("new"));
2356 else
2357 q_status_message3(SM_ASYNC, 1, 3, "%s: %s %s",
2358 FLDR_NAME(f), comatose(f->unseen),
2359 _("unseen"));
2363 else
2364 f->last_unseen_update = LUU_NOMORECHK; /* no further checking */
2369 void
2370 update_folder_unseen_by_stream(MAILSTREAM *strm, unsigned long flags)
2372 CONTEXT_S *ctxt;
2373 int ftotal, i;
2374 char mailbox_name[MAILTMPLEN], *target;
2375 char *cn, tmp[MAILTMPLEN];
2376 FOLDER_S *f;
2379 * Attempt to figure out which incoming folder this stream
2380 * is open to, if any, so we can update the unseen counters.
2382 if(strm
2383 && F_ON(F_ENABLE_INCOMING_CHECKING, ps_global)
2384 && (ctxt=ps_global->context_list) && ctxt->use & CNTXT_INCMNG
2385 && (ftotal = folder_total(FOLDERS(ctxt)))){
2386 for(i = 0; i < ftotal; i++){
2387 f = folder_entry(i, FOLDERS(ctxt));
2388 context_apply(mailbox_name, ctxt, f->name, MAILTMPLEN);
2390 if((check_for_move_mbox(mailbox_name, NULL, 0, &target)
2391 && strm->snarf.name
2392 && (!strcmp(target,strm->mailbox)
2393 || !strcmp(target,strm->original_mailbox)))
2394 || same_stream_and_mailbox(mailbox_name, strm)
2395 || (!IS_REMOTE(mailbox_name) && (cn=mailboxfile(tmp,mailbox_name)) && (*cn) && (!strcmp(cn, strm->mailbox) || !strcmp(cn, strm->original_mailbox)))){
2396 /* if we failed earlier on this one, give it another go */
2397 if(f->last_unseen_update == LUU_NOMORECHK)
2398 init_incoming_unseen_data(ps_global, f);
2400 update_folder_unseen(f, ctxt, flags, strm);
2401 return;
2409 * Find the number of new, unseen, and the total number of
2410 * messages in mailbox_name.
2411 * If the corresponding arg is NULL it will skip the work
2412 * necessary for that flag.
2414 * Returns 1 if successful, 0 if not.
2417 get_recent_in_folder(char *mailbox_name, long unsigned int *new,
2418 long unsigned int *unseen, long unsigned int *total,
2419 MAILSTREAM *this_is_the_stream)
2421 MAILSTREAM *strm = NIL;
2422 unsigned long tot, nw, uns;
2423 int gotit = 0;
2424 int maildrop = 0;
2425 char *target = NULL;
2426 MSGNO_S *msgmap;
2427 long excluded, flags;
2428 extern MAILSTATUS mm_status_result;
2430 dprint((9, "get_recent_in_folder(%s)", mailbox_name ? mailbox_name : "?"));
2432 if(check_for_move_mbox(mailbox_name, NULL, 0, &target)){
2434 maildrop++;
2437 * If this maildrop is the currently open stream use that.
2439 if(target
2440 && ((strm=ps_global->mail_stream)
2441 && strm->snarf.name
2442 && (!strcmp(target,strm->mailbox)
2443 || !strcmp(target,strm->original_mailbox)))){
2444 gotit++;
2445 msgmap = sp_msgmap(strm);
2446 excluded = any_lflagged(msgmap, MN_EXLD);
2448 tot = strm->nmsgs - excluded;
2449 if(tot){
2450 if(new){
2451 if(sp_recent_since_visited(strm) == 0)
2452 nw = 0;
2453 else
2454 nw = count_flagged(strm, F_RECENT | F_UNSEEN | F_UNDEL);
2457 if(unseen)
2458 uns = count_flagged(strm, F_UNSEEN | F_UNDEL);
2460 else{
2461 nw = 0;
2462 uns = 0;
2465 /* else fall through to just open it case */
2468 /* do we already have it selected? */
2469 if(!gotit
2470 && ((strm = this_is_the_stream)
2471 || (strm = sp_stream_get(mailbox_name, SP_MATCH | SP_RO_OK))
2472 || (!IS_REMOTE(mailbox_name)
2473 && (strm = already_open_stream(mailbox_name, AOS_NONE))))){
2474 gotit++;
2477 * Unfortunately, we have to worry about excluded
2478 * messages. The user doesn't want to have
2479 * excluded messages count in the totals, especially
2480 * recent excluded messages.
2483 msgmap = sp_msgmap(strm);
2484 excluded = any_lflagged(msgmap, MN_EXLD);
2486 tot = strm->nmsgs - excluded;
2487 if(tot){
2488 if(new){
2489 if(sp_recent_since_visited(strm) == 0)
2490 nw = 0;
2491 else
2492 nw = count_flagged(strm, F_RECENT | F_UNSEEN | F_UNDEL);
2495 if(unseen)
2496 uns = count_flagged(strm, F_UNSEEN | F_UNDEL);
2498 else{
2499 nw = 0;
2500 uns = 0;
2504 * No, but how about another stream to same server which
2505 * could be used for a STATUS command?
2507 else if(!gotit && (strm = sp_stream_get(mailbox_name, SP_SAME))
2508 && modern_imap_stream(strm)){
2510 flags = 0L;
2511 if(total)
2512 flags |= SA_MESSAGES;
2514 if(new)
2515 flags |= SA_RECENT;
2517 if(unseen)
2518 flags |= SA_UNSEEN;
2520 mm_status_result.flags = 0L;
2522 pine_mail_status(strm, mailbox_name, flags);
2523 if(total){
2524 if(mm_status_result.flags & SA_MESSAGES){
2525 tot = mm_status_result.messages;
2526 gotit++;
2530 if(!(total && !gotit)){
2531 if(new){
2532 if(mm_status_result.flags & SA_RECENT){
2533 nw = mm_status_result.recent;
2534 gotit++;
2536 else
2537 gotit = 0;
2541 if(!((total || new) && !gotit)){
2542 if(unseen){
2543 if(mm_status_result.flags & SA_UNSEEN){
2544 uns = mm_status_result.unseen;
2545 gotit++;
2547 else
2548 gotit = 0;
2553 /* Let's just Select it. */
2554 if(!gotit){
2555 long saved_timeout;
2556 long openflags;
2559 * Traditional unix folders don't notice new mail if
2560 * they are opened readonly. So maildrops with unix folder
2561 * targets will snarf to the file but the stream that is
2562 * opened won't see the new mail. So make all maildrop
2563 * opens non-readonly here.
2565 openflags = SP_USEPOOL | SP_TEMPUSE | (maildrop ? 0 : OP_READONLY);
2567 saved_timeout = (long) mail_parameters(NULL, GET_OPENTIMEOUT, NULL);
2568 mail_parameters(NULL, SET_OPENTIMEOUT, (void *) (long) ps_global->inc_check_timeout);
2569 strm = pine_mail_open(NULL, mailbox_name, openflags, NULL);
2570 mail_parameters(NULL, SET_OPENTIMEOUT, (void *) saved_timeout);
2572 if(strm){
2573 gotit++;
2574 msgmap = sp_msgmap(strm);
2575 excluded = any_lflagged(msgmap, MN_EXLD);
2577 tot = strm->nmsgs - excluded;
2578 if(tot){
2579 if(new){
2580 if(sp_recent_since_visited(strm) == 0)
2581 nw = 0;
2582 else
2583 nw = count_flagged(strm, F_RECENT | F_UNSEEN | F_UNDEL);
2586 if(unseen)
2587 uns = count_flagged(strm, F_UNSEEN | F_UNDEL);
2589 else{
2590 nw = 0;
2591 uns = 0;
2594 pine_mail_close(strm);
2598 if(gotit){
2599 if(new)
2600 *new = nw;
2602 if(unseen)
2603 *unseen = uns;
2605 if(total)
2606 *total = tot;
2609 return(gotit);
2613 void
2614 clear_incoming_valid_bits(void)
2616 CONTEXT_S *ctxt;
2617 int ftotal, i;
2618 FOLDER_S *f;
2620 if(F_ON(F_ENABLE_INCOMING_CHECKING, ps_global)
2621 && (ctxt=ps_global->context_list) && ctxt->use & CNTXT_INCMNG
2622 && (ftotal = folder_total(FOLDERS(ctxt))))
2623 for(i = 0; i < ftotal; i++){
2624 f = folder_entry(i, FOLDERS(ctxt));
2625 init_incoming_unseen_data(ps_global, f);
2631 selected_folders(CONTEXT_S *context)
2633 int i, n, total;
2635 n = folder_total(FOLDERS(context));
2636 for(total = i = 0; i < n; i++)
2637 if(folder_entry(i, FOLDERS(context))->selected)
2638 total++;
2640 return(total);
2644 SELECTED_S *
2645 new_selected(void)
2647 SELECTED_S *selp;
2649 selp = (SELECTED_S *)fs_get(sizeof(SELECTED_S));
2650 selp->sub = NULL;
2651 selp->reference = NULL;
2652 selp->folders = NULL;
2653 selp->zoomed = 0;
2655 return selp;
2660 * Free the current selected struct and all of the
2661 * following structs in the list
2663 void
2664 free_selected(SELECTED_S **selp)
2666 if(!selp || !(*selp))
2667 return;
2668 if((*selp)->sub)
2669 free_selected(&((*selp)->sub));
2671 free_strlist(&(*selp)->folders);
2672 if((*selp)->reference)
2673 fs_give((void **) &(*selp)->reference);
2675 fs_give((void **) selp);
2679 void
2680 folder_select_preserve(CONTEXT_S *context)
2682 if(context
2683 && !(context->use & CNTXT_PARTFIND)){
2684 FOLDER_S *fp;
2685 STRLIST_S **slpp;
2686 SELECTED_S *selp = &context->selected;
2687 int i, folder_n;
2689 if(!context->dir->ref){
2690 if(!context->selected.folders)
2691 slpp = &context->selected.folders;
2692 else
2693 return;
2695 else{
2696 if(!selected_folders(context))
2697 return;
2698 else{
2699 while(selp->sub){
2700 selp = selp->sub;
2701 if(!strcmp(selp->reference, context->dir->ref))
2702 return;
2704 selp->sub = new_selected();
2705 selp = selp->sub;
2706 slpp = &(selp->folders);
2709 folder_n = folder_total(FOLDERS(context));
2711 for(i = 0; i < folder_n; i++)
2712 if((fp = folder_entry(i, FOLDERS(context)))->selected){
2713 *slpp = new_strlist(fp->name);
2714 slpp = &(*slpp)->next;
2717 /* Only remember "ref" if any folders were selected */
2718 if(selp->folders && context->dir->ref)
2719 selp->reference = cpystr(context->dir->ref);
2721 selp->zoomed = (context->use & CNTXT_ZOOM) != 0;
2727 folder_select_restore(CONTEXT_S *context)
2729 int rv = 0;
2731 if(context
2732 && !(context->use & CNTXT_PARTFIND)){
2733 STRLIST_S *slp;
2734 SELECTED_S *selp, *pselp = NULL;
2735 int i, found = 0;
2737 selp = &(context->selected);
2739 if(context->dir->ref){
2740 pselp = selp;
2741 selp = selp->sub;
2742 while(selp && strcmp(selp->reference, context->dir->ref)){
2743 pselp = selp;
2744 selp = selp->sub;
2746 if (selp)
2747 found = 1;
2749 else
2750 found = selp->folders != 0;
2751 if(found){
2752 for(slp = selp->folders; slp; slp = slp->next)
2753 if(slp->name
2754 && (i = folder_index(slp->name, context, FI_FOLDER)) >= 0){
2755 folder_entry(i, FOLDERS(context))->selected = 1;
2756 rv++;
2759 /* Used, always clean them up */
2760 free_strlist(&selp->folders);
2761 if(selp->reference)
2762 fs_give((void **) &selp->reference);
2764 if(selp->zoomed){
2765 context->use |= CNTXT_ZOOM;
2766 selp->zoomed = 0;
2768 if(!(selp == &context->selected)){
2769 if(pselp){
2770 pselp->sub = selp->sub;
2771 fs_give((void **) &selp);
2777 return(rv);