* Create help for explaining how encrypted password file support
[alpine.git] / pith / folder.c
blobda0ea6d503d6b4cd6777aeaa4d2047a062cb5fc2
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 2006-2008 University of Washington
8 * Copyright 2013-2014 Eduardo Chappa
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 receiveing 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 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;
895 snprintf(buf, buflen-(buf-origbuf), "%s%s%s",
896 (!nntp && *p) ? "#public" : "#news.",
897 (!nntp && *p && *bboard != '/') ? "/" : "",
898 bboard);
900 return(1);
903 return(0);
908 * build_folder_list - call mail_list to fetch us a list of folders
909 * from the given context.
911 void
912 build_folder_list(MAILSTREAM **stream, CONTEXT_S *context, char *pat, char *content, int flags)
914 MM_LIST_S ldata;
915 BFL_DATA_S response;
916 int local_open = 0, we_cancel = 0, resort = 0;
917 char reference[2*MAILTMPLEN], *p;
919 if(!(context->dir->status & CNTXT_NOFIND)
920 || (context->dir->status & CNTXT_PARTFIND))
921 return; /* find already done! */
923 dprint((7, "build_folder_list: %s %s\n",
924 context ? context->context : "NULL",
925 pat ? pat : "NULL"));
927 we_cancel = busy_cue(NULL, NULL, 1);
930 * Set up the pattern of folder name's to match within the
931 * given context.
933 if(!pat || ((*pat == '*' || *pat == '%') && *(pat+1) == '\0')){
934 context->dir->status &= ~CNTXT_NOFIND; /* let'em know we tried */
935 pat = context->dir->view.internal;
937 else
938 context->use |= CNTXT_PARTFIND; /* or are in a partial find */
940 memset(mm_list_info = &ldata, 0, sizeof(MM_LIST_S));
941 ldata.filter = (NEWS_TEST(context)) ? mail_lsub_filter : mail_list_filter;
942 memset(ldata.data = &response, 0, sizeof(BFL_DATA_S));
943 response.list = FOLDERS(context);
945 if(flags & BFL_FLDRONLY)
946 response.mask = LATT_NOSELECT;
949 * if context is associated with a server, prepare a stream for
950 * sending our request.
952 if(*context->context == '{'){
954 * Try using a stream we've already got open...
956 if(stream && *stream
957 && !(ldata.stream = same_stream(context->context, *stream))){
958 pine_mail_close(*stream);
959 *stream = NULL;
962 if(!ldata.stream)
963 ldata.stream = sp_stream_get(context->context, SP_MATCH);
965 if(!ldata.stream)
966 ldata.stream = sp_stream_get(context->context, SP_SAME);
968 /* gotta open a new one? */
969 if((F_OFF(F_CMBND_FOLDER_DISP, ps_global)
970 || context->update == LUU_INIT) && !ldata.stream){
971 ldata.stream = mail_cmd_stream(context, &local_open);
972 if(stream)
973 *stream = ldata.stream;
976 dprint((ldata.stream ? 7 : 1, "build_folder_list: mail_open(%s) %s.\n",
977 context->server ? context->server : "?",
978 ldata.stream ? "OK" : "FAILED"));
980 if(!ldata.stream){
981 context->use &= ~CNTXT_PARTFIND; /* unset partial find bit */
982 context->update = LUU_NOMORECHK;
983 if(we_cancel)
984 cancel_busy_cue(-1);
986 return;
989 else if(stream && *stream){ /* no server, simple case */
990 if(!sp_flagged(*stream, SP_LOCKED))
991 pine_mail_close(*stream);
993 *stream = NULL;
997 * If preset reference string, we're somewhere in the hierarchy.
998 * ELSE we must be at top of context...
1000 response.args.name = pat;
1001 if(!(response.args.reference = context->dir->ref)){
1002 if((p = strstr(context->context, "%s")) != NULL){
1003 strncpy(response.args.reference = reference,
1004 context->context,
1005 MIN(p - context->context, sizeof(reference)/2));
1006 reference[MIN(p - context->context, sizeof(reference)/2)] = '\0';
1007 if(*(p += 2))
1008 response.args.tail = p;
1010 else
1011 response.args.reference = context->context;
1014 if(flags & BFL_SCAN)
1015 mail_scan(ldata.stream, response.args.reference,
1016 response.args.name, content);
1017 else if(flags & BFL_LSUB)
1018 mail_lsub(ldata.stream, response.args.reference, response.args.name);
1019 else{
1020 set_read_predicted(1);
1021 pine_mail_list(ldata.stream, response.args.reference, response.args.name,
1022 &ldata.options);
1023 set_read_predicted(0);
1026 context->update = LUU_INIT;
1027 if(context->dir && response.response.delim)
1028 context->dir->delim = response.response.delim;
1030 if(!(flags & (BFL_LSUB|BFL_SCAN)) && F_ON(F_QUELL_EMPTY_DIRS, ps_global)){
1031 LISTRES_S listres;
1032 FOLDER_S *f;
1033 int i;
1035 for(i = 0; i < folder_total(response.list); i++)
1036 if((f = folder_entry(i, FOLDERS(context)))->isdir){
1038 * we don't need to do a list if we know already
1039 * whether there are children or not.
1041 if(!f->haschildren && !f->hasnochildren){
1042 memset(ldata.data = &listres, 0, sizeof(LISTRES_S));
1043 ldata.filter = mail_list_response;
1045 if(context->dir->ref)
1046 snprintf(reference, sizeof(reference), "%s%s", context->dir->ref, f->name);
1047 else
1048 context_apply(reference, context, f->name, sizeof(reference)/2);
1050 /* append the delimiter to the reference */
1051 for(p = reference; *p; p++)
1054 *p++ = context->dir->delim;
1055 *p = '\0';
1057 pine_mail_list(ldata.stream, reference, "%", NULL);
1059 /* anything interesting inside? */
1060 f->hasnochildren = (listres.count <= 1L);
1061 f->haschildren = !f->hasnochildren;;
1064 if(f->hasnochildren){
1065 if(f->isfolder){
1066 f->isdir = 0;
1067 if(ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_FIRST
1068 || ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_LAST)
1069 resort = 1;
1072 * We don't want to hide directories
1073 * that are only directories even if they are
1074 * empty. We only want to hide the directory
1075 * piece of a dual-use folder when there are
1076 * no children in the directory (and the user
1077 * most likely thinks of it as a folder instead
1078 * of a folder and a directory).
1080 else if(f->isdir && f->isdual){
1081 folder_delete(i, FOLDERS(context));
1082 i--;
1088 if(resort)
1089 resort_folder_list(response.list);
1091 if(local_open && !stream)
1092 pine_mail_close(ldata.stream);
1094 if(context->use & CNTXT_PRESRV)
1095 folder_select_restore(context);
1097 context->use &= ~CNTXT_PARTFIND; /* unset partial list bit */
1098 if(we_cancel)
1099 cancel_busy_cue(-1);
1104 * Validate LIST response to command issued from build_folder_list
1107 void
1108 mail_list_filter(MAILSTREAM *stream, char *mailbox, int delim, long int attribs, void *data, unsigned int options)
1110 BFL_DATA_S *ld = (BFL_DATA_S *) data;
1111 FOLDER_S *new_f = NULL, *dual_f = NULL;
1112 int suppress_folder_add = 0;
1114 if(!ld)
1115 return;
1117 if(delim)
1118 ld->response.delim = delim;
1120 /* test against mask of DIS-allowed attributes */
1121 if((ld->mask & attribs)){
1122 dprint((3, "mail_list_filter: failed attribute test"));
1123 return;
1127 * First, make sure response fits our "reference" arg
1128 * NOTE: build_folder_list can't supply breakout?
1130 if(!mail_list_in_collection(&mailbox, ld->args.reference,
1131 ld->args.name, ld->args.tail))
1132 return;
1134 /* ignore dotfolders unless told not to */
1135 if(F_OFF(F_ENABLE_DOT_FOLDERS, ps_global) && *mailbox == '.'){
1136 dprint((3, "mail_list_filter: dotfolder disallowed"));
1137 return;
1141 * If this response is INBOX we have to handle it specially.
1142 * The special cases are:
1144 * Incoming folders are enabled and this INBOX is in the Incoming
1145 * folders list already. We don't want to list it here, as well,
1146 * unless it is also a directory.
1148 * Incoming folders are not enabled, but we inserted INBOX into
1149 * this primary collection (with init_inbox_mapping()) so it is
1150 * already accounted for unless it is also a directory.
1152 * Incoming folders are not enabled, but we inserted INBOX into
1153 * the primary collection (which we are not looking at here) so
1154 * it is already accounted for there. We'll add it as a directory
1155 * as well here if that is what is called for.
1157 if(!strucmp(mailbox, ps_global->inbox_name)){
1158 int ftotal, i;
1159 FOLDER_S *f;
1160 char fullname[1000], tmp1[1000], tmp2[1000], *l1, *l2;
1162 /* fullname is the name of the mailbox in the list response */
1163 if(ld->args.reference){
1164 strncpy(fullname, ld->args.reference, sizeof(fullname)-1);
1165 fullname[sizeof(fullname)-1] = '\0';
1167 else
1168 fullname[0] = '\0';
1170 strncat(fullname, mailbox, sizeof(fullname)-strlen(fullname)-1);
1171 fullname[sizeof(fullname)-1] = '\0';
1173 /* check if Incoming Folders are enabled */
1174 if(ps_global->context_list && ps_global->context_list->use & CNTXT_INCMNG){
1175 int this_inbox_is_in_incoming = 0;
1178 * Figure out if this INBOX is already in the Incoming list.
1181 /* compare fullname to each incoming folder */
1182 ftotal = folder_total(FOLDERS(ps_global->context_list));
1183 for(i = 0; i < ftotal && !this_inbox_is_in_incoming; i++){
1184 f = folder_entry(i, FOLDERS(ps_global->context_list));
1185 if(f && f->name){
1186 if(same_remote_mailboxes(fullname, f->name)
1187 || ((!IS_REMOTE(fullname) && !IS_REMOTE(f->name))
1188 && (l1=mailboxfile(tmp1,fullname))
1189 && (l2=mailboxfile(tmp2,f->name))
1190 && !strcmp(l1,l2)))
1191 this_inbox_is_in_incoming++;
1195 if(this_inbox_is_in_incoming){
1197 * Don't add a folder for this, only a directory if called for.
1198 * If it isn't a directory, skip it.
1200 if(!(delim && !(attribs & LATT_NOINFERIORS)))
1201 return;
1203 suppress_folder_add++;
1206 else{
1207 int inbox_is_in_this_collection = 0;
1209 /* is INBOX already in this folder list? */
1210 ftotal = folder_total(ld->list);
1211 for(i = 0; i < ftotal && !inbox_is_in_this_collection; i++){
1212 f = folder_entry(i, ld->list);
1213 if(!strucmp(FLDR_NAME(f), ps_global->inbox_name))
1214 inbox_is_in_this_collection++;
1217 if(inbox_is_in_this_collection){
1220 * Inbox is already inserted in this collection. Unless
1221 * it is also a directory, we are done.
1223 if(!(delim && !(attribs & LATT_NOINFERIORS)))
1224 return;
1227 * Since it is also a directory, what we do depends on
1228 * the F_SEP feature. If that feature is not set we just
1229 * want to mark the existing entry dual-use. If F_SEP is
1230 * set we want to add a new directory entry.
1232 if(F_ON(F_SEPARATE_FLDR_AS_DIR, ps_global)){
1233 f->isdir = 0;
1234 f->haschildren = 0;
1235 f->hasnochildren = 0;
1236 /* fall through and add a new directory */
1238 else{
1239 /* mark existing entry dual-use and return */
1240 ld->response.count++;
1241 ld->response.isdir = 1;
1242 f->isdir = 1;
1243 if(attribs & LATT_HASCHILDREN)
1244 f->haschildren = 1;
1246 if(attribs & LATT_HASNOCHILDREN)
1247 f->hasnochildren = 1;
1249 return;
1252 suppress_folder_add++;
1254 else{
1255 int found_it = 0;
1256 int this_inbox_is_primary_inbox = 0;
1259 * See if this INBOX is the same as the INBOX we inserted
1260 * in the primary collection.
1263 /* first find the existing INBOX entry */
1264 ftotal = folder_total(FOLDERS(ps_global->context_list));
1265 for(i = 0; i < ftotal && !found_it; i++){
1266 f = folder_entry(i, FOLDERS(ps_global->context_list));
1267 if(!strucmp(FLDR_NAME(f), ps_global->inbox_name))
1268 found_it++;
1271 if(found_it && f && f->name){
1272 if(same_remote_mailboxes(fullname, f->name)
1273 || ((!IS_REMOTE(fullname) && !IS_REMOTE(f->name))
1274 && (l1=mailboxfile(tmp1,fullname))
1275 && (l2=mailboxfile(tmp2,f->name))
1276 && !strcmp(l1,l2)))
1277 this_inbox_is_primary_inbox++;
1280 if(this_inbox_is_primary_inbox){
1282 * Don't add a folder for this, only a directory if called for.
1283 * If it isn't a directory, skip it.
1285 if(!(delim && !(attribs & LATT_NOINFERIORS)))
1286 return;
1288 suppress_folder_add++;
1294 /* is it a mailbox? */
1295 if(!(attribs & LATT_NOSELECT) && !suppress_folder_add){
1296 ld->response.count++;
1297 ld->response.isfile = 1;
1298 new_f = new_folder(mailbox, 0);
1299 new_f->isfolder = 1;
1301 if(F_ON(F_SEPARATE_FLDR_AS_DIR, ps_global)){
1302 folder_insert(-1, new_f, ld->list);
1303 dual_f = new_f;
1304 new_f = NULL;
1308 /* directory? */
1309 if(delim && !(attribs & LATT_NOINFERIORS)){
1310 ld->response.count++;
1311 ld->response.isdir = 1;
1313 if(!new_f)
1314 new_f = new_folder(mailbox, 0);
1316 new_f->isdir = 1;
1317 if(attribs & LATT_HASCHILDREN)
1318 new_f->haschildren = 1;
1319 if(attribs & LATT_HASNOCHILDREN)
1320 new_f->hasnochildren = 1;
1323 * When we have F_SEPARATE_FLDR_AS_DIR we still want to know
1324 * whether the name really represents both so that we don't
1325 * inadvertently delete both when the user meant one or the
1326 * other.
1328 if(dual_f){
1329 if(attribs & LATT_HASCHILDREN)
1330 dual_f->haschildren = 1;
1332 if(attribs & LATT_HASNOCHILDREN)
1333 dual_f->hasnochildren = 1;
1335 dual_f->isdual = 1;
1336 new_f->isdual = 1;
1340 if(new_f)
1341 folder_insert(-1, new_f, ld->list);
1343 if(attribs & LATT_MARKED)
1344 ld->response.ismarked = 1;
1346 /* don't mark #move folders unmarked */
1347 if(attribs & LATT_UNMARKED && !(options & PML_IS_MOVE_MBOX))
1348 ld->response.unmarked = 1;
1350 if(attribs & LATT_HASCHILDREN)
1351 ld->response.haschildren = 1;
1353 if(attribs & LATT_HASNOCHILDREN)
1354 ld->response.hasnochildren = 1;
1359 * Validate LSUB response to command issued from build_folder_list
1362 void
1363 mail_lsub_filter(MAILSTREAM *stream, char *mailbox, int delim, long int attribs,
1364 void *data, unsigned int options)
1366 BFL_DATA_S *ld = (BFL_DATA_S *) data;
1367 FOLDER_S *new_f = NULL;
1368 char *ref;
1370 if(delim)
1371 ld->response.delim = delim;
1373 /* test against mask of DIS-allowed attributes */
1374 if((ld->mask & attribs)){
1375 dprint((3, "mail_lsub_filter: failed attribute test"));
1376 return;
1379 /* Normalize mailbox and reference strings re: namespace */
1380 if(!strncmp(mailbox, "#news.", 6))
1381 mailbox += 6;
1383 if(!strncmp(ref = ld->args.reference, "#news.", 6))
1384 ref += 6;
1386 if(!mail_list_in_collection(&mailbox, ref, ld->args.name, ld->args.tail))
1387 return;
1389 if(!(attribs & LATT_NOSELECT)){
1390 ld->response.count++;
1391 ld->response.isfile = 1;
1392 new_f = new_folder(mailbox, 0);
1393 new_f->isfolder = 1;
1394 folder_insert(F_ON(F_READ_IN_NEWSRC_ORDER, ps_global)
1395 ? folder_total(ld->list) : -1,
1396 new_f, ld->list);
1399 /* We don't support directories in #news */
1402 /* human readable name for a folder. memory freed by caller
1403 * This is a jacket to conversion from modified utf7 to utf8.
1405 unsigned char *folder_name_decoded(unsigned char *mailbox)
1407 unsigned char *s;
1408 s = (unsigned char *) utf8_from_mutf7((unsigned char *) mailbox);
1409 if (s == NULL) s = (unsigned char *) cpystr(mailbox);
1410 return s;
1413 /* mutf7 encoded name of a folder, from its name in utf8.
1414 * memory freed by caller.
1416 unsigned char *folder_name_encoded(unsigned char *mailbox)
1418 unsigned char *s;
1419 s = (char *) utf8_to_mutf7(mailbox);
1420 if (s == NULL) s = cpystr(mailbox);
1421 return s;
1425 mail_list_in_collection(char **mailbox, char *ref, char *name, char *tail)
1427 int boxlen, reflen, taillen;
1428 char *p;
1430 boxlen = strlen(*mailbox);
1431 reflen = ref ? strlen(ref) : 0;
1432 taillen = tail ? strlen(tail) : 0;
1434 if(boxlen
1435 && (reflen ? !struncmp(*mailbox, ref, reflen)
1436 : (p = strpbrk(name, "%*"))
1437 ? !struncmp(*mailbox, name, p - name)
1438 : !strucmp(*mailbox,name))
1439 && (!taillen
1440 || (taillen < boxlen - reflen
1441 && !strucmp(&(*mailbox)[boxlen - taillen], tail)))){
1442 if(taillen)
1443 (*mailbox)[boxlen - taillen] = '\0';
1445 if(*(*mailbox += reflen))
1446 return(TRUE);
1449 * else don't worry about context "breakouts" since
1450 * build_folder_list doesn't let the user introduce
1451 * one...
1454 return(FALSE);
1459 * rebuild_folder_list -- free up old list and re-issue commands to build
1460 * a new list.
1462 void
1463 refresh_folder_list(CONTEXT_S *context, int nodirs, int startover, MAILSTREAM **streamp)
1465 if(startover)
1466 free_folder_list(context);
1468 build_folder_list(streamp, context, NULL, NULL,
1469 (NEWS_TEST(context) ? BFL_LSUB : BFL_NONE)
1470 | ((nodirs) ? BFL_FLDRONLY : BFL_NONE));
1475 * free_folder_list - loop thru the context's lists of folders
1476 * clearing all entries without nicknames
1477 * (as those were user provided) AND reset the
1478 * context's find flag.
1480 * NOTE: if fetched() information (e.g., like message counts come back
1481 * in bboard collections), we may want to have a check before
1482 * executing the loop and setting the FIND flag.
1484 void
1485 free_folder_list(CONTEXT_S *cntxt)
1487 int n, i;
1490 * In this case, don't blast the list as it was given to us by the
1491 * user and not the result of a mail_list call...
1493 if(cntxt->use & CNTXT_INCMNG)
1494 return;
1496 if(cntxt->use & CNTXT_PRESRV)
1497 folder_select_preserve(cntxt);
1499 for(n = folder_total(FOLDERS(cntxt)), i = 0; n > 0; n--)
1500 if(folder_entry(i, FOLDERS(cntxt))->nickname)
1501 i++; /* entry wasn't from LIST */
1502 else
1503 folder_delete(i, FOLDERS(cntxt));
1505 cntxt->dir->status |= CNTXT_NOFIND; /* do find next time... */
1506 /* or add the fake entry */
1507 cntxt->use &= ~(CNTXT_PSEUDO | CNTXT_PRESRV | CNTXT_ZOOM);
1512 * default_save_context - return the default context for saved messages
1514 CONTEXT_S *
1515 default_save_context(CONTEXT_S *cntxt)
1517 while(cntxt)
1518 if((cntxt->use) & CNTXT_SAVEDFLT)
1519 return(cntxt);
1520 else
1521 cntxt = cntxt->next;
1523 return(NULL);
1529 * folder_complete - foldername completion routine
1531 * Result: returns 0 if the folder doesn't have a any completetion
1532 * 1 if the folder has a completion (*AND* "name" is
1533 * replaced with the completion)
1537 folder_complete(CONTEXT_S *context, char *name, size_t namelen, int *completions)
1539 return(folder_complete_internal(context, name, namelen, completions, FC_NONE));
1547 folder_complete_internal(CONTEXT_S *context, char *name, size_t namelen,
1548 int *completions, int flags)
1550 int i, match = -1, ftotal;
1551 char tmp[MAXFOLDER+2], *a, *b, *fn, *pat;
1552 FOLDER_S *f;
1554 if(completions)
1555 *completions = 0;
1557 if(*name == '\0' || !context_isambig(name))
1558 return(0);
1560 if(!((context->use & CNTXT_INCMNG) || ALL_FOUND(context))){
1562 * Build the folder list from scratch since we may need to
1563 * traverse hierarchy...
1566 free_folder_list(context);
1567 snprintf(tmp, sizeof(tmp), "%s%c", name, NEWS_TEST(context) ? '*' : '%');
1568 build_folder_list(NULL, context, tmp, NULL,
1569 (NEWS_TEST(context) & !(flags & FC_FORCE_LIST))
1570 ? BFL_LSUB : BFL_NONE);
1573 *tmp = '\0'; /* find uniq substring */
1574 ftotal = folder_total(FOLDERS(context));
1575 for(i = 0; i < ftotal; i++){
1576 f = folder_entry(i, FOLDERS(context));
1577 fn = FLDR_NAME(f);
1578 pat = name;
1579 if(!(NEWS_TEST(context) || (context->use & CNTXT_INCMNG))){
1580 fn = folder_last_cmpnt(fn, context->dir->delim);
1581 pat = folder_last_cmpnt(pat, context->dir->delim);
1584 if(!strncmp(fn, pat, strlen(pat))){
1585 if(match != -1){ /* oh well, do best we can... */
1586 a = fn;
1587 if(match >= 0){
1588 f = folder_entry(match, FOLDERS(context));
1589 fn = FLDR_NAME(f);
1590 if(!NEWS_TEST(context))
1591 fn = folder_last_cmpnt(fn, context->dir->delim);
1593 strncpy(tmp, fn, sizeof(tmp)-1);
1594 tmp[sizeof(tmp)-1] = '\0';
1597 match = -2;
1598 b = tmp; /* remember largest common text */
1599 while(*a && *b && *a == *b)
1600 *b++ = *a++;
1602 *b = '\0';
1604 else
1605 match = i; /* bingo?? */
1609 if(match >= 0){ /* found! */
1610 f = folder_entry(match, FOLDERS(context));
1611 fn = FLDR_NAME(f);
1612 if(!(NEWS_TEST(context) || (context->use & CNTXT_INCMNG)))
1613 fn = folder_last_cmpnt(fn, context->dir->delim);
1615 strncpy(pat, fn, namelen-(pat-name));
1616 name[namelen-1] = '\0';
1617 if(f->isdir && !f->isfolder){
1618 name[i = strlen(name)] = context->dir->delim;
1619 name[i+1] = '\0';
1622 else if(match == -2){ /* closest we could find */
1623 strncpy(pat, tmp, namelen-(pat-name));
1624 name[namelen-1] = '\0';
1627 if(completions)
1628 *completions = ftotal;
1630 if(!((context->use & CNTXT_INCMNG) || ALL_FOUND(context)))
1631 free_folder_list(context);
1633 return((match >= 0) ? ftotal : 0);
1637 char *
1638 folder_last_cmpnt(char *s, int d)
1640 register char *p;
1642 if(d)
1643 for(p = s; (p = strindex(p, d)); s = ++p)
1646 return(s);
1651 * init_folder_entries - return a piece of memory suitable for attaching
1652 * a list of folders...
1655 FLIST *
1656 init_folder_entries(void)
1658 FLIST *flist = (FLIST *) fs_get(sizeof(FLIST));
1659 flist->folders = (FOLDER_S **) fs_get(FCHUNK * sizeof(FOLDER_S *));
1660 memset((void *)flist->folders, 0, (FCHUNK * sizeof(FOLDER_S *)));
1661 flist->allocated = FCHUNK;
1662 flist->used = 0;
1663 return(flist);
1668 * new_folder - return a brand new folder entry, with the given name
1669 * filled in.
1671 * NOTE: THIS IS THE ONLY WAY TO PUT A NAME INTO A FOLDER ENTRY!!!
1672 * STRCPY WILL NOT WORK!!!
1674 FOLDER_S *
1675 new_folder(char *name, long unsigned int hash)
1677 FOLDER_S *tmp;
1678 size_t l = strlen(name);
1680 tmp = (FOLDER_S *)fs_get(sizeof(FOLDER_S) + (l * sizeof(char)));
1681 memset((void *)tmp, 0, sizeof(FOLDER_S));
1682 strncpy(tmp->name, name, l);
1683 tmp->name[l] = '\0';
1684 tmp->name_len = (unsigned char) l;
1685 tmp->varhash = hash;
1686 return(tmp);
1691 * folder_entry - folder struct access routine. Permit reference to
1692 * folder structs via index number. Serves two purposes:
1693 * 1) easy way for callers to access folder data
1694 * conveniently
1695 * 2) allows for a custom manager to limit memory use
1696 * under certain rather limited "operating systems"
1697 * who shall renameless, but whose initials are DOS
1701 FOLDER_S *
1702 folder_entry(int i, FLIST *flist)
1704 return((i >= flist->used) ? NULL:flist->folders[i]);
1709 * free_folder_entries - release all resources associated with the given
1710 * list of folder entries
1712 void
1713 free_folder_entries(FLIST **flist)
1715 register int i;
1717 if(!(flist && *flist))
1718 return;
1720 i = (*flist)->used;
1721 while(i--){
1722 if((*flist)->folders[i]->nickname)
1723 fs_give((void **) &(*flist)->folders[i]->nickname);
1725 fs_give((void **) &((*flist)->folders[i]));
1728 fs_give((void **) &((*flist)->folders));
1729 fs_give((void **) flist);
1734 * return the number of folders associated with the given folder list
1737 folder_total(FLIST *flist)
1739 return((int) flist->used);
1744 * return the index number of the given name in the given folder list
1747 folder_index(char *name, CONTEXT_S *cntxt, int flags)
1749 register int i = 0;
1750 FOLDER_S *f;
1751 char *fname;
1753 for(i = 0; (f = folder_entry(i, FOLDERS(cntxt))); i++)
1754 if(((flags & FI_FOLDER) && (f->isfolder || (cntxt->use & CNTXT_INCMNG)))
1755 || ((flags & FI_DIR) && f->isdir)){
1756 fname = FLDR_NAME(f);
1757 #if defined(DOS) || defined(OS2)
1758 if(flags & FI_RENAME){ /* case-dependent for rename */
1759 if(*name == *fname && strcmp(name, fname) == 0)
1760 return(i);
1762 else{
1763 if(toupper((unsigned char)(*name))
1764 == toupper((unsigned char)(*fname)) && strucmp(name, fname) == 0)
1765 return(i);
1767 #else
1768 if(*name == *fname && strcmp(name, fname) == 0)
1769 return(i);
1770 #endif
1773 return(-1);
1778 * folder_is_nick - check to see if the given name is a nickname
1779 * for some folder in the given context...
1781 * NOTE: no need to check if mm_list_names has been done as
1782 * nicknames can only be set by configuration...
1784 char *
1785 folder_is_nick(char *nickname, FLIST *flist, int flags)
1787 register int i = 0;
1788 FOLDER_S *f;
1790 if(!(nickname && *nickname && flist))
1791 return(NULL);
1793 while((f = folder_entry(i, flist)) != NULL){
1795 * The second part of the OR is checking in a case-indep
1796 * way for INBOX. It should be restricted to the context
1797 * to which we add the INBOX folder, which would be either
1798 * the Incoming Folders collection or the first collection
1799 * if there is no Incoming collection. We don't need to check
1800 * the collection because nickname assignment has already
1801 * done that for us. Most folders don't have nicknames, only
1802 * incoming folders and folders like inbox if not in incoming.
1804 if(f->nickname
1805 && (!strcmp(nickname, f->nickname)
1806 || (!strucmp(nickname, f->nickname)
1807 && !strucmp(nickname, ps_global->inbox_name)))){
1808 char source[MAILTMPLEN], *target = NULL;
1811 * If f is a maildrop, then we want to return the
1812 * destination folder, not the whole #move thing.
1814 if(!(flags & FN_WHOLE_NAME)
1815 && check_for_move_mbox(f->name, source, sizeof(source), &target))
1816 return(target);
1817 else
1818 return(f->name);
1820 else
1821 i++;
1824 return(NULL);
1828 char *
1829 folder_is_target_of_nick(char *longname, CONTEXT_S *cntxt)
1831 register int i = 0;
1832 FOLDER_S *f;
1833 FLIST *flist = NULL;
1835 if(cntxt && cntxt == ps_global->context_list)
1836 flist = FOLDERS(cntxt);
1838 if(!(longname && *longname && flist))
1839 return(NULL);
1841 while((f = folder_entry(i, flist)) != NULL){
1842 if(f->nickname && f->name && !strcmp(longname, f->name))
1843 return(f->nickname);
1844 else
1845 i++;
1848 return(NULL);
1852 /*----------------------------------------------------------------------
1853 Insert the given folder name into the sorted folder list
1854 associated with the given context. Only allow ambiguous folder
1855 names IF associated with a nickname.
1857 Args: index -- Index to insert at, OR insert in sorted order if -1
1858 folder -- folder structure to insert into list
1859 flist -- folder list to insert folder into
1861 **** WARNING ****
1862 DON'T count on the folder pointer being valid after this returns
1863 *** ALL FOLDER ELEMENT READS SHOULD BE THRU folder_entry() ***
1865 ----*/
1867 folder_insert(int index, FOLDER_S *folder, FLIST *flist)
1869 /* requested index < 0 means add to sorted list */
1870 if(index < 0 && (index = folder_total(flist)) > 0)
1871 index = folder_insert_sorted(index / 2, 0, index, folder, flist,
1872 (ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_FIRST)
1873 ? compare_folders_dir_alpha
1874 : (ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_LAST)
1875 ? compare_folders_alpha_dir
1876 : compare_folders_alpha);
1878 folder_insert_index(folder, index, flist);
1879 return(index);
1884 * folder_insert_sorted - Insert given folder struct into given list
1885 * observing sorting specified by given
1886 * comparison function
1889 folder_insert_sorted(int index, int min_index, int max_index, FOLDER_S *folder,
1890 FLIST *flist, int (*compf)(FOLDER_S *, FOLDER_S *))
1892 int i;
1894 return(((i = (*compf)(folder_entry(index, flist), folder)) == 0)
1895 ? index
1896 : (i < 0)
1897 ? ((++index >= max_index)
1898 ? max_index
1899 : ((*compf)(folder_entry(index, flist), folder) > 0)
1900 ? index
1901 : folder_insert_sorted((index + max_index) / 2, index,
1902 max_index, folder, flist, compf))
1903 : ((index <= min_index)
1904 ? min_index
1905 : folder_insert_sorted((min_index + index) / 2, min_index, index,
1906 folder, flist, compf)));
1911 * folder_insert_index - Insert the given folder struct into the global list
1912 * at the given index.
1914 void
1915 folder_insert_index(FOLDER_S *folder, int index, FLIST *flist)
1917 register FOLDER_S **flp, **iflp;
1919 /* if index is beyond size, place at end of list */
1920 index = MIN(index, flist->used);
1922 /* grow array ? */
1923 if(flist->used + 1 > flist->allocated){
1924 flist->allocated += FCHUNK;
1925 fs_resize((void **)&(flist->folders),
1926 flist->allocated * sizeof(FOLDER_S *));
1929 /* shift array left */
1930 iflp = &((flist->folders)[index]);
1931 for(flp = &((flist->folders)[flist->used]);
1932 flp > iflp; flp--)
1933 flp[0] = flp[-1];
1935 flist->folders[index] = folder;
1936 flist->used += 1;
1940 void
1941 resort_folder_list(FLIST *flist)
1943 if(flist && folder_total(flist) > 1 && flist->folders)
1944 qsort(flist->folders, folder_total(flist), sizeof(flist->folders[0]),
1945 (ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_FIRST)
1946 ? compare_folders_dir_alpha_qsort
1947 : (ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_LAST)
1948 ? compare_folders_alpha_dir_qsort
1949 : compare_folders_alpha_qsort);
1953 /*----------------------------------------------------------------------
1954 Removes a folder at the given index in the given context's
1955 list.
1957 Args: index -- Index in folder list of folder to be removed
1958 flist -- folder list
1959 ----*/
1960 void
1961 folder_delete(int index, FLIST *flist)
1963 register int i;
1964 FOLDER_S *f;
1966 if(flist->used
1967 && (index < 0 || index >= flist->used))
1968 return; /* bogus call! */
1970 if((f = folder_entry(index, flist))->nickname)
1971 fs_give((void **)&(f->nickname));
1973 fs_give((void **) &(flist->folders[index]));
1974 for(i = index; i < flist->used - 1; i++)
1975 flist->folders[i] = flist->folders[i+1];
1978 flist->used -= 1;
1982 /*----------------------------------------------------------------------
1983 compare two names for qsort, case independent
1985 Args: pointers to strings to compare
1987 Result: integer result of strcmp of the names. Uses simple
1988 efficiency hack to speed the string comparisons up a bit.
1990 ----------------------------------------------------------------------*/
1992 compare_names(const qsort_t *x, const qsort_t *y)
1994 char *a = *(char **)x, *b = *(char **)y;
1995 int r;
1996 #define CMPI(X,Y) ((X)[0] - (Y)[0])
1997 #define UCMPI(X,Y) ((isupper((unsigned char)((X)[0])) \
1998 ? (X)[0] - 'A' + 'a' : (X)[0]) \
1999 - (isupper((unsigned char)((Y)[0])) \
2000 ? (Y)[0] - 'A' + 'a' : (Y)[0]))
2002 /*---- Inbox always sorts to the top ----*/
2003 if(UCMPI(a, ps_global->inbox_name) == 0
2004 && strucmp(a, ps_global->inbox_name) == 0)
2005 return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : -1);
2006 else if((UCMPI(b, ps_global->inbox_name)) == 0
2007 && strucmp(b, ps_global->inbox_name) == 0)
2008 return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : 1);
2010 /*----- The sent-mail folder, is always next unless... ---*/
2011 else if(F_OFF(F_SORT_DEFAULT_FCC_ALPHA, ps_global)
2012 && CMPI(a, ps_global->VAR_DEFAULT_FCC) == 0
2013 && strcmp(a, ps_global->VAR_DEFAULT_FCC) == 0)
2014 return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : -1);
2015 else if(F_OFF(F_SORT_DEFAULT_FCC_ALPHA, ps_global)
2016 && CMPI(b, ps_global->VAR_DEFAULT_FCC) == 0
2017 && strcmp(b, ps_global->VAR_DEFAULT_FCC) == 0)
2018 return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : 1);
2020 /*----- The saved-messages folder, is always next unless... ---*/
2021 else if(F_OFF(F_SORT_DEFAULT_SAVE_ALPHA, ps_global)
2022 && CMPI(a, ps_global->VAR_DEFAULT_SAVE_FOLDER) == 0
2023 && strcmp(a, ps_global->VAR_DEFAULT_SAVE_FOLDER) == 0)
2024 return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : -1);
2025 else if(F_OFF(F_SORT_DEFAULT_SAVE_ALPHA, ps_global)
2026 && CMPI(b, ps_global->VAR_DEFAULT_SAVE_FOLDER) == 0
2027 && strcmp(b, ps_global->VAR_DEFAULT_SAVE_FOLDER) == 0)
2028 return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : 1);
2030 else
2031 return((r = CMPI(a, b)) ? r : strcmp(a, b));
2035 /*----------------------------------------------------------------------
2036 compare two folder structs for ordering alphabetically
2038 Args: pointers to folder structs to compare
2040 Result: integer result of dir-bit and strcmp of the folders.
2041 ----------------------------------------------------------------------*/
2043 compare_folders_alpha(FOLDER_S *f1, FOLDER_S *f2)
2045 int i;
2046 char *f1name = FLDR_NAME(f1),
2047 *f2name = FLDR_NAME(f2);
2049 return(((i = compare_names(&f1name, &f2name)) != 0)
2050 ? i : (f2->isdir - f1->isdir));
2055 compare_folders_alpha_qsort(const qsort_t *a1, const qsort_t *a2)
2057 FOLDER_S *f1 = *((FOLDER_S **) a1);
2058 FOLDER_S *f2 = *((FOLDER_S **) a2);
2060 return(compare_folders_alpha(f1, f2));
2064 /*----------------------------------------------------------------------
2065 compare two folder structs alphabetically with dirs first
2067 Args: pointers to folder structs to compare
2069 Result: integer result of dir-bit and strcmp of the folders.
2070 ----------------------------------------------------------------------*/
2072 compare_folders_dir_alpha(FOLDER_S *f1, FOLDER_S *f2)
2074 int i;
2076 if((i = (f2->isdir - f1->isdir)) == 0){
2077 char *f1name = FLDR_NAME(f1),
2078 *f2name = FLDR_NAME(f2);
2080 return(compare_names(&f1name, &f2name));
2083 return(i);
2088 compare_folders_dir_alpha_qsort(const qsort_t *a1, const qsort_t *a2)
2090 FOLDER_S *f1 = *((FOLDER_S **) a1);
2091 FOLDER_S *f2 = *((FOLDER_S **) a2);
2093 return(compare_folders_dir_alpha(f1, f2));
2097 /*----------------------------------------------------------------------
2098 compare two folder structs alphabetically with dirs last
2100 Args: pointers to folder structs to compare
2102 Result: integer result of dir-bit and strcmp of the folders.
2103 ----------------------------------------------------------------------*/
2105 compare_folders_alpha_dir(FOLDER_S *f1, FOLDER_S *f2)
2107 int i;
2109 if((i = (f1->isdir - f2->isdir)) == 0){
2110 char *f1name = FLDR_NAME(f1),
2111 *f2name = FLDR_NAME(f2);
2113 return(compare_names(&f1name, &f2name));
2116 return(i);
2121 compare_folders_alpha_dir_qsort(const qsort_t *a1, const qsort_t *a2)
2123 FOLDER_S *f1 = *((FOLDER_S **) a1);
2124 FOLDER_S *f2 = *((FOLDER_S **) a2);
2126 return(compare_folders_alpha_dir(f1, f2));
2131 * Find incoming folders and update the unseen counts
2132 * if necessary.
2134 void
2135 folder_unseen_count_updater(unsigned long flags)
2137 CONTEXT_S *ctxt;
2138 time_t oldest, started_checking;
2139 int ftotal, i, first = -1;
2140 FOLDER_S *f;
2143 * We would only do this if there is an incoming collection, the
2144 * user wants us to monitor, and we're in the folder screen.
2146 if(ps_global->in_folder_screen && F_ON(F_ENABLE_INCOMING_CHECKING, ps_global)
2147 && (ctxt=ps_global->context_list) && ctxt->use & CNTXT_INCMNG
2148 && (ftotal = folder_total(FOLDERS(ctxt)))){
2150 * Search through the last_unseen_update times to find
2151 * the one that was updated longest ago, and start with
2152 * that one. We don't want to delay long doing these
2153 * checks so may only check some of them each time we
2154 * get called. An update time equal to 1 means don't check
2155 * this folder at all.
2157 for(i = 0; i < ftotal; i++){
2158 f = folder_entry(i, FOLDERS(ctxt));
2159 if(f && LUU_YES(f->last_unseen_update)){
2160 first = i;
2161 oldest = f->last_unseen_update;
2162 break;
2167 * Now first is the first in the list that
2168 * should ever be checked. Next find the
2169 * one that should be checked next, the one
2170 * that was checked longest ago.
2172 if(first >= 0){
2173 for(i = 1; i < ftotal; i++){
2174 f = folder_entry(i, FOLDERS(ctxt));
2175 if(f && LUU_YES(f->last_unseen_update) && f->last_unseen_update < oldest){
2176 first = i;
2177 oldest = f->last_unseen_update;
2182 /* now first is the next one to be checked */
2184 started_checking = time(0);
2186 for(i = first; i < ftotal; i++){
2187 /* update the next one */
2188 f = folder_entry(i, FOLDERS(ctxt));
2189 if(f && LUU_YES(f->last_unseen_update)
2190 && (flags & UFU_FORCE
2191 /* or it's been long enough and we've not been in this function too long */
2192 || (((time(0) - f->last_unseen_update) >= ps_global->inc_check_interval)
2193 && ((time(0) - started_checking) < MIN(4,ps_global->inc_check_timeout)))))
2194 update_folder_unseen(f, ctxt, flags, NULL);
2197 for(i = 0; i < first; i++){
2198 f = folder_entry(i, FOLDERS(ctxt));
2199 if(f && LUU_YES(f->last_unseen_update)
2200 && (flags & UFU_FORCE
2201 || (((time(0) - f->last_unseen_update) >= ps_global->inc_check_interval)
2202 && ((time(0) - started_checking) < MIN(4,ps_global->inc_check_timeout)))))
2203 update_folder_unseen(f, ctxt, flags, NULL);
2210 * Update the count of unseen in the FOLDER_S struct
2211 * for this folder. This will update if the time
2212 * interval has passed or if the FORCE flag is set.
2214 void
2215 update_folder_unseen(FOLDER_S *f, CONTEXT_S *ctxt, unsigned long flags,
2216 MAILSTREAM *this_is_the_stream)
2218 time_t now;
2219 int orig_valid;
2220 int use_imap_interval = 0;
2221 int stream_is_open = 0;
2222 unsigned long orig_unseen, orig_new, orig_tot;
2223 char mailbox_name[MAILTMPLEN];
2224 char *target = NULL;
2225 DRIVER *d;
2227 if(!f || !LUU_YES(f->last_unseen_update))
2228 return;
2230 now = time(0);
2231 context_apply(mailbox_name, ctxt, f->name, MAILTMPLEN);
2233 if(!mailbox_name[0])
2234 return;
2236 if(check_for_move_mbox(mailbox_name, NULL, 0, &target)){
2237 MAILSTREAM *strm;
2240 * If this maildrop is the currently open stream use that.
2241 * I'm not altogether sure that this is a good way to
2242 * check this.
2244 if(target
2245 && ((strm=ps_global->mail_stream)
2246 && strm->snarf.name
2247 && (!strcmp(target,strm->mailbox)
2248 || !strcmp(target,strm->original_mailbox)))){
2249 stream_is_open++;
2252 else{
2253 MAILSTREAM *m = NULL;
2255 stream_is_open = (this_is_the_stream
2256 || (m=sp_stream_get(mailbox_name, SP_MATCH | SP_RO_OK))
2257 || ((m=ps_global->mail_stream) && !sp_dead_stream(m)
2258 && same_stream_and_mailbox(mailbox_name,m))
2259 || (!IS_REMOTE(mailbox_name)
2260 && (m=already_open_stream(mailbox_name, AOS_NONE)))) ? 1 : 0;
2262 if(stream_is_open){
2263 if(!this_is_the_stream)
2264 this_is_the_stream = m;
2266 else{
2268 * If it's IMAP or local we use a shorter interval.
2270 d = mail_valid(NIL, mailbox_name, (char *) NIL);
2271 if((d && !strcmp(d->name, "imap")) || !IS_REMOTE(mailbox_name))
2272 use_imap_interval++;
2277 * Update if forced, or if it's been a while, or if we have a
2278 * stream open to this mailbox already.
2280 if(flags & UFU_FORCE
2281 || stream_is_open
2282 || ((use_imap_interval
2283 && (now - f->last_unseen_update) >= ps_global->inc_check_interval)
2284 || ((now - f->last_unseen_update) >= ps_global->inc_second_check_interval))){
2285 unsigned long tot, uns, new;
2286 unsigned long *totp = NULL, *unsp = NULL, *newp = NULL;
2288 orig_valid = f->unseen_valid;
2289 orig_unseen = f->unseen;
2290 orig_new = f->new;
2291 orig_tot = f->total;
2293 if(F_ON(F_INCOMING_CHECKING_RECENT, ps_global))
2294 newp = &new;
2295 else
2296 unsp = &uns;
2298 if(F_ON(F_INCOMING_CHECKING_TOTAL, ps_global))
2299 totp = &tot;
2301 f->unseen_valid = 0;
2303 dprint((9, "update_folder_unseen(%s)", FLDR_NAME(f)));
2304 if(get_recent_in_folder(mailbox_name, newp, unsp, totp, this_is_the_stream)){
2305 f->last_unseen_update = time(0);
2306 f->unseen_valid = 1;
2307 if(unsp)
2308 f->unseen = uns;
2310 if(newp)
2311 f->new = new;
2313 if(totp)
2314 f->total = tot;
2316 if(!orig_valid){
2317 dprint((9, "update_folder_unseen(%s): original: %s%s%s%s",
2318 FLDR_NAME(f),
2319 F_ON(F_INCOMING_CHECKING_RECENT,ps_global) ? "new=" : "unseen=",
2320 F_ON(F_INCOMING_CHECKING_RECENT,ps_global) ? comatose(f->new) : comatose(f->unseen),
2321 F_ON(F_INCOMING_CHECKING_TOTAL,ps_global) ? " tot=" : "",
2322 F_ON(F_INCOMING_CHECKING_TOTAL,ps_global) ? comatose(f->total) : ""));
2325 if(orig_valid
2326 && ((F_ON(F_INCOMING_CHECKING_RECENT, ps_global)
2327 && orig_new != f->new)
2329 (F_OFF(F_INCOMING_CHECKING_RECENT, ps_global)
2330 && orig_unseen != f->unseen)
2332 (F_ON(F_INCOMING_CHECKING_TOTAL, ps_global)
2333 && orig_tot != f->total))){
2335 if(ps_global->in_folder_screen)
2336 ps_global->noticed_change_in_unseen = 1;
2338 dprint((9, "update_folder_unseen(%s): changed: %s%s%s%s",
2339 FLDR_NAME(f),
2340 F_ON(F_INCOMING_CHECKING_RECENT,ps_global) ? "new=" : "unseen=",
2341 F_ON(F_INCOMING_CHECKING_RECENT,ps_global) ? comatose(f->new) : comatose(f->unseen),
2342 F_ON(F_INCOMING_CHECKING_TOTAL,ps_global) ? " tot=" : "",
2343 F_ON(F_INCOMING_CHECKING_TOTAL,ps_global) ? comatose(f->total) : ""));
2345 if(flags & UFU_ANNOUNCE
2346 && ((F_ON(F_INCOMING_CHECKING_RECENT, ps_global)
2347 && orig_new < f->new)
2349 (F_OFF(F_INCOMING_CHECKING_RECENT, ps_global)
2350 && orig_unseen < f->unseen))){
2351 if(F_ON(F_INCOMING_CHECKING_RECENT, ps_global))
2352 q_status_message3(SM_ASYNC, 1, 3, "%s: %s %s",
2353 FLDR_NAME(f), comatose(f->new),
2354 _("new"));
2355 else
2356 q_status_message3(SM_ASYNC, 1, 3, "%s: %s %s",
2357 FLDR_NAME(f), comatose(f->unseen),
2358 _("unseen"));
2362 else
2363 f->last_unseen_update = LUU_NOMORECHK; /* no further checking */
2368 void
2369 update_folder_unseen_by_stream(MAILSTREAM *strm, unsigned long flags)
2371 CONTEXT_S *ctxt;
2372 int ftotal, i;
2373 char mailbox_name[MAILTMPLEN], *target;
2374 char *cn, tmp[MAILTMPLEN];
2375 FOLDER_S *f;
2378 * Attempt to figure out which incoming folder this stream
2379 * is open to, if any, so we can update the unseen counters.
2381 if(strm
2382 && F_ON(F_ENABLE_INCOMING_CHECKING, ps_global)
2383 && (ctxt=ps_global->context_list) && ctxt->use & CNTXT_INCMNG
2384 && (ftotal = folder_total(FOLDERS(ctxt)))){
2385 for(i = 0; i < ftotal; i++){
2386 f = folder_entry(i, FOLDERS(ctxt));
2387 context_apply(mailbox_name, ctxt, f->name, MAILTMPLEN);
2389 if((check_for_move_mbox(mailbox_name, NULL, 0, &target)
2390 && strm->snarf.name
2391 && (!strcmp(target,strm->mailbox)
2392 || !strcmp(target,strm->original_mailbox)))
2393 || same_stream_and_mailbox(mailbox_name, strm)
2394 || (!IS_REMOTE(mailbox_name) && (cn=mailboxfile(tmp,mailbox_name)) && (*cn) && (!strcmp(cn, strm->mailbox) || !strcmp(cn, strm->original_mailbox)))){
2395 /* if we failed earlier on this one, give it another go */
2396 if(f->last_unseen_update == LUU_NOMORECHK)
2397 init_incoming_unseen_data(ps_global, f);
2399 update_folder_unseen(f, ctxt, flags, strm);
2400 return;
2408 * Find the number of new, unseen, and the total number of
2409 * messages in mailbox_name.
2410 * If the corresponding arg is NULL it will skip the work
2411 * necessary for that flag.
2413 * Returns 1 if successful, 0 if not.
2416 get_recent_in_folder(char *mailbox_name, long unsigned int *new,
2417 long unsigned int *unseen, long unsigned int *total,
2418 MAILSTREAM *this_is_the_stream)
2420 MAILSTREAM *strm = NIL;
2421 unsigned long tot, nw, uns;
2422 int gotit = 0;
2423 int maildrop = 0;
2424 char *target = NULL;
2425 MSGNO_S *msgmap;
2426 long excluded, flags;
2427 extern MAILSTATUS mm_status_result;
2429 dprint((9, "get_recent_in_folder(%s)", mailbox_name ? mailbox_name : "?"));
2431 if(check_for_move_mbox(mailbox_name, NULL, 0, &target)){
2433 maildrop++;
2436 * If this maildrop is the currently open stream use that.
2438 if(target
2439 && ((strm=ps_global->mail_stream)
2440 && strm->snarf.name
2441 && (!strcmp(target,strm->mailbox)
2442 || !strcmp(target,strm->original_mailbox)))){
2443 gotit++;
2444 msgmap = sp_msgmap(strm);
2445 excluded = any_lflagged(msgmap, MN_EXLD);
2447 tot = strm->nmsgs - excluded;
2448 if(tot){
2449 if(new){
2450 if(sp_recent_since_visited(strm) == 0)
2451 nw = 0;
2452 else
2453 nw = count_flagged(strm, F_RECENT | F_UNSEEN | F_UNDEL);
2456 if(unseen)
2457 uns = count_flagged(strm, F_UNSEEN | F_UNDEL);
2459 else{
2460 nw = 0;
2461 uns = 0;
2464 /* else fall through to just open it case */
2467 /* do we already have it selected? */
2468 if(!gotit
2469 && ((strm = this_is_the_stream)
2470 || (strm = sp_stream_get(mailbox_name, SP_MATCH | SP_RO_OK))
2471 || (!IS_REMOTE(mailbox_name)
2472 && (strm = already_open_stream(mailbox_name, AOS_NONE))))){
2473 gotit++;
2476 * Unfortunately, we have to worry about excluded
2477 * messages. The user doesn't want to have
2478 * excluded messages count in the totals, especially
2479 * recent excluded messages.
2482 msgmap = sp_msgmap(strm);
2483 excluded = any_lflagged(msgmap, MN_EXLD);
2485 tot = strm->nmsgs - excluded;
2486 if(tot){
2487 if(new){
2488 if(sp_recent_since_visited(strm) == 0)
2489 nw = 0;
2490 else
2491 nw = count_flagged(strm, F_RECENT | F_UNSEEN | F_UNDEL);
2494 if(unseen)
2495 uns = count_flagged(strm, F_UNSEEN | F_UNDEL);
2497 else{
2498 nw = 0;
2499 uns = 0;
2503 * No, but how about another stream to same server which
2504 * could be used for a STATUS command?
2506 else if(!gotit && (strm = sp_stream_get(mailbox_name, SP_SAME))
2507 && modern_imap_stream(strm)){
2509 flags = 0L;
2510 if(total)
2511 flags |= SA_MESSAGES;
2513 if(new)
2514 flags |= SA_RECENT;
2516 if(unseen)
2517 flags |= SA_UNSEEN;
2519 mm_status_result.flags = 0L;
2521 pine_mail_status(strm, mailbox_name, flags);
2522 if(total){
2523 if(mm_status_result.flags & SA_MESSAGES){
2524 tot = mm_status_result.messages;
2525 gotit++;
2529 if(!(total && !gotit)){
2530 if(new){
2531 if(mm_status_result.flags & SA_RECENT){
2532 nw = mm_status_result.recent;
2533 gotit++;
2535 else
2536 gotit = 0;
2540 if(!((total || new) && !gotit)){
2541 if(unseen){
2542 if(mm_status_result.flags & SA_UNSEEN){
2543 uns = mm_status_result.unseen;
2544 gotit++;
2546 else
2547 gotit = 0;
2552 /* Let's just Select it. */
2553 if(!gotit){
2554 long saved_timeout;
2555 long openflags;
2558 * Traditional unix folders don't notice new mail if
2559 * they are opened readonly. So maildrops with unix folder
2560 * targets will snarf to the file but the stream that is
2561 * opened won't see the new mail. So make all maildrop
2562 * opens non-readonly here.
2564 openflags = SP_USEPOOL | SP_TEMPUSE | (maildrop ? 0 : OP_READONLY);
2566 saved_timeout = (long) mail_parameters(NULL, GET_OPENTIMEOUT, NULL);
2567 mail_parameters(NULL, SET_OPENTIMEOUT, (void *) (long) ps_global->inc_check_timeout);
2568 strm = pine_mail_open(NULL, mailbox_name, openflags, NULL);
2569 mail_parameters(NULL, SET_OPENTIMEOUT, (void *) saved_timeout);
2571 if(strm){
2572 gotit++;
2573 msgmap = sp_msgmap(strm);
2574 excluded = any_lflagged(msgmap, MN_EXLD);
2576 tot = strm->nmsgs - excluded;
2577 if(tot){
2578 if(new){
2579 if(sp_recent_since_visited(strm) == 0)
2580 nw = 0;
2581 else
2582 nw = count_flagged(strm, F_RECENT | F_UNSEEN | F_UNDEL);
2585 if(unseen)
2586 uns = count_flagged(strm, F_UNSEEN | F_UNDEL);
2588 else{
2589 nw = 0;
2590 uns = 0;
2593 pine_mail_close(strm);
2597 if(gotit){
2598 if(new)
2599 *new = nw;
2601 if(unseen)
2602 *unseen = uns;
2604 if(total)
2605 *total = tot;
2608 return(gotit);
2612 void
2613 clear_incoming_valid_bits(void)
2615 CONTEXT_S *ctxt;
2616 int ftotal, i;
2617 FOLDER_S *f;
2619 if(F_ON(F_ENABLE_INCOMING_CHECKING, ps_global)
2620 && (ctxt=ps_global->context_list) && ctxt->use & CNTXT_INCMNG
2621 && (ftotal = folder_total(FOLDERS(ctxt))))
2622 for(i = 0; i < ftotal; i++){
2623 f = folder_entry(i, FOLDERS(ctxt));
2624 init_incoming_unseen_data(ps_global, f);
2630 selected_folders(CONTEXT_S *context)
2632 int i, n, total;
2634 n = folder_total(FOLDERS(context));
2635 for(total = i = 0; i < n; i++)
2636 if(folder_entry(i, FOLDERS(context))->selected)
2637 total++;
2639 return(total);
2643 SELECTED_S *
2644 new_selected(void)
2646 SELECTED_S *selp;
2648 selp = (SELECTED_S *)fs_get(sizeof(SELECTED_S));
2649 selp->sub = NULL;
2650 selp->reference = NULL;
2651 selp->folders = NULL;
2652 selp->zoomed = 0;
2654 return selp;
2659 * Free the current selected struct and all of the
2660 * following structs in the list
2662 void
2663 free_selected(SELECTED_S **selp)
2665 if(!selp || !(*selp))
2666 return;
2667 if((*selp)->sub)
2668 free_selected(&((*selp)->sub));
2670 free_strlist(&(*selp)->folders);
2671 if((*selp)->reference)
2672 fs_give((void **) &(*selp)->reference);
2674 fs_give((void **) selp);
2678 void
2679 folder_select_preserve(CONTEXT_S *context)
2681 if(context
2682 && !(context->use & CNTXT_PARTFIND)){
2683 FOLDER_S *fp;
2684 STRLIST_S **slpp;
2685 SELECTED_S *selp = &context->selected;
2686 int i, folder_n;
2688 if(!context->dir->ref){
2689 if(!context->selected.folders)
2690 slpp = &context->selected.folders;
2691 else
2692 return;
2694 else{
2695 if(!selected_folders(context))
2696 return;
2697 else{
2698 while(selp->sub){
2699 selp = selp->sub;
2700 if(!strcmp(selp->reference, context->dir->ref))
2701 return;
2703 selp->sub = new_selected();
2704 selp = selp->sub;
2705 slpp = &(selp->folders);
2708 folder_n = folder_total(FOLDERS(context));
2710 for(i = 0; i < folder_n; i++)
2711 if((fp = folder_entry(i, FOLDERS(context)))->selected){
2712 *slpp = new_strlist(fp->name);
2713 slpp = &(*slpp)->next;
2716 /* Only remember "ref" if any folders were selected */
2717 if(selp->folders && context->dir->ref)
2718 selp->reference = cpystr(context->dir->ref);
2720 selp->zoomed = (context->use & CNTXT_ZOOM) != 0;
2726 folder_select_restore(CONTEXT_S *context)
2728 int rv = 0;
2730 if(context
2731 && !(context->use & CNTXT_PARTFIND)){
2732 STRLIST_S *slp;
2733 SELECTED_S *selp, *pselp = NULL;
2734 int i, found = 0;
2736 selp = &(context->selected);
2738 if(context->dir->ref){
2739 pselp = selp;
2740 selp = selp->sub;
2741 while(selp && strcmp(selp->reference, context->dir->ref)){
2742 pselp = selp;
2743 selp = selp->sub;
2745 if (selp)
2746 found = 1;
2748 else
2749 found = selp->folders != 0;
2750 if(found){
2751 for(slp = selp->folders; slp; slp = slp->next)
2752 if(slp->name
2753 && (i = folder_index(slp->name, context, FI_FOLDER)) >= 0){
2754 folder_entry(i, FOLDERS(context))->selected = 1;
2755 rv++;
2758 /* Used, always clean them up */
2759 free_strlist(&selp->folders);
2760 if(selp->reference)
2761 fs_give((void **) &selp->reference);
2763 if(selp->zoomed){
2764 context->use |= CNTXT_ZOOM;
2765 selp->zoomed = 0;
2767 if(!(selp == &context->selected)){
2768 if(pselp){
2769 pselp->sub = selp->sub;
2770 fs_give((void **) &selp);
2776 return(rv);