* Implement a different way to delete a password from the cache.
[alpine.git] / pith / folder.c
blob81f331a532ef8a827c9da4551b599841e33e9a1d
1 /*
2 * ========================================================================
3 * Copyright 2013-2022 Eduardo Chappa
4 * Copyright 2006-2008 University of Washington
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
15 #include "../pith/headers.h"
16 #include "../c-client/utf8aux.h"
17 #include "../pith/folder.h"
18 #include "../pith/state.h"
19 #include "../pith/context.h"
20 #include "../pith/init.h"
21 #include "../pith/conf.h"
22 #include "../pith/stream.h"
23 #include "../pith/imap.h"
24 #include "../pith/util.h"
25 #include "../pith/flag.h"
26 #include "../pith/status.h"
27 #include "../pith/busy.h"
28 #include "../pith/mailindx.h"
31 typedef struct _build_folder_list_data {
32 long mask; /* bitmap of responses to ignore */
33 LISTARGS_S args;
34 LISTRES_S response;
35 int is_move_folder;
36 FLIST *list;
37 } BFL_DATA_S;
40 #define FCHUNK 64
44 * Internal prototypes
46 void mail_list_exists(MAILSTREAM *, char *, int, long, void *, unsigned);
47 void init_incoming_folder_list(struct pine *, CONTEXT_S *);
48 void mail_list_filter(MAILSTREAM *, char *, int, long, void *, unsigned);
49 void mail_lsub_filter(MAILSTREAM *, char *, int, long, void *, unsigned);
50 int mail_list_in_collection(char **, char *, char *, char *);
51 char *folder_last_cmpnt(char *, int);
52 void free_folder_entries(FLIST **);
53 int folder_insert_sorted(int, int, int, FOLDER_S *, FLIST *,
54 int (*)(FOLDER_S *, FOLDER_S *));
55 void folder_insert_index(FOLDER_S *, int, FLIST *);
56 void resort_folder_list(FLIST *flist);
57 int compare_folders_alpha_qsort(const qsort_t *a1, const qsort_t *a2);
58 int compare_folders_dir_alpha_qsort(const qsort_t *a1, const qsort_t *a2);
59 int compare_folders_alpha_dir_qsort(const qsort_t *a1, const qsort_t *a2);
60 int compare_names(const qsort_t *, const qsort_t *);
61 void init_incoming_unseen_data(struct pine *, FOLDER_S *f);
64 char *
65 folder_lister_desc(CONTEXT_S *cntxt, FDIR_S *fdp)
67 char *p, *q;
68 unsigned char *fname;
70 q = ((p = strstr(cntxt->context, "%s")) && !*(p+2)
71 && !strncmp(fdp->ref, cntxt->context, p - cntxt->context))
72 ? fdp->ref + (p - cntxt->context) : fdp->ref;
73 fname = folder_name_decoded((unsigned char *) q);
74 /* Provide context in new collection header */
75 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "Dir: %s", fname ? (char *) fname : q);
76 if(fname) fs_give((void **)&fname);
78 return(cpystr(tmp_20k_buf));
82 void
83 reset_context_folders(CONTEXT_S *cntxt)
85 CONTEXT_S *tc;
87 for(tc = cntxt; tc && tc->prev ; tc = tc->prev)
88 ; /* start at beginning */
90 for( ; tc ; tc = tc->next){
91 free_folder_list(tc);
93 while(tc->dir->prev){
94 FDIR_S *tp = tc->dir->prev;
95 free_fdir(&tc->dir, 0);
96 tc->dir = tp;
103 * next_folder_dir - return a directory structure with the folders it
104 * contains
106 FDIR_S *
107 next_folder_dir(CONTEXT_S *context, char *new_dir, int build_list, MAILSTREAM **streamp)
109 char tmp[MAILTMPLEN], dir[3];
110 FDIR_S *tmp_fp, *fp;
112 fp = (FDIR_S *) fs_get(sizeof(FDIR_S));
113 memset(fp, 0, sizeof(FDIR_S));
114 (void) context_apply(tmp, context, new_dir, MAILTMPLEN);
115 dir[0] = context->dir->delim;
116 dir[1] = '\0';
117 strncat(tmp, dir, sizeof(tmp)-1-strlen(tmp));
118 fp->ref = cpystr(tmp);
119 fp->delim = context->dir->delim;
120 fp->view.internal = cpystr(NEWS_TEST(context) ? "*" : "%");
121 fp->folders = init_folder_entries();
122 fp->status = CNTXT_NOFIND;
123 tmp_fp = context->dir; /* temporarily rebind */
124 context->dir = fp;
126 if(build_list)
127 build_folder_list(streamp, context, NULL, NULL,
128 NEWS_TEST(context) ? BFL_LSUB : BFL_NONE);
130 context->dir = tmp_fp;
131 return(fp);
136 * Return which pinerc incoming folder #index is in.
138 EditWhich
139 config_containing_inc_fldr(FOLDER_S *folder)
141 char **t;
142 int i, keep_going = 1, inheriting = 0;
143 struct variable *v = &ps_global->vars[V_INCOMING_FOLDERS];
145 if(v->is_fixed)
146 return(None);
148 /* is it in exceptions config? */
149 if(v->post_user_val.l){
150 for(i = 0, t=v->post_user_val.l; t[i]; i++){
151 if(expand_variables(tmp_20k_buf, SIZEOF_20KBUF, t[i], 0)){
152 keep_going = 0;
154 if(!strcmp(tmp_20k_buf, INHERIT))
155 inheriting = 1;
156 else if(folder->varhash == line_hash(tmp_20k_buf))
157 return(Post);
162 if(inheriting)
163 keep_going = 1;
165 /* is it in main config? */
166 if(keep_going && v->main_user_val.l){
167 for(i = 0, t=v->main_user_val.l; t[i]; i++){
168 if(expand_variables(tmp_20k_buf, SIZEOF_20KBUF, t[i], 0) &&
169 folder->varhash == line_hash(tmp_20k_buf))
170 return(Main);
174 return(None);
178 /*----------------------------------------------------------------------
179 Format the given folder name for display for the user
181 Args: folder -- The folder name to fix up
183 Not sure this always makes it prettier. It could do nice truncation if we
184 passed in a length. Right now it adds the path name of the mail
185 subdirectory if appropriate.
186 ----*/
188 char *
189 pretty_fn(char *folder)
191 if(!strucmp(folder, ps_global->inbox_name))
192 return(ps_global->inbox_name);
193 else
194 return(folder);
198 /*----------------------------------------------------------------------
199 Return the path delimiter for the given folder on the given server
201 Args: folder -- folder type for delimiter
203 ----*/
205 get_folder_delimiter(char *folder)
207 MM_LIST_S ldata;
208 LISTRES_S response;
209 int ourstream = 0;
211 memset(mm_list_info = &ldata, 0, sizeof(MM_LIST_S));
212 ldata.filter = mail_list_response;
213 memset(ldata.data = &response, 0, sizeof(LISTRES_S));
215 if(*folder == '{'
216 && !(ldata.stream = sp_stream_get(folder, SP_MATCH))
217 && !(ldata.stream = sp_stream_get(folder, SP_SAME))){
218 if((ldata.stream = pine_mail_open(NULL,folder,
219 OP_HALFOPEN|OP_SILENT|SP_USEPOOL|SP_TEMPUSE,
220 NULL)) != NULL){
221 ourstream++;
223 else{
224 return(FEX_ERROR);
228 pine_mail_list(ldata.stream, folder, "", NULL);
230 if(ourstream)
231 pine_mail_close(ldata.stream);
233 return(response.delim);
237 /*----------------------------------------------------------------------
238 Check to see if folder exists in given context
240 Args: cntxt -- context inwhich to interpret "file" arg
241 file -- name of folder to check
243 Result: returns FEX_ISFILE if the folder exists and is a folder
244 FEX_ISDIR if the folder exists and is a directory
245 FEX_NOENT if it doesn't exist
246 FEX_ERROR on error
248 The two existence return values above may be logically OR'd
250 Uses mail_list to sniff out the existence of the requested folder.
251 The context string is just here for convenience. Checking for
252 folder's existence within a given context is probably more efficiently
253 handled outside this function for now using build_folder_list().
255 ----*/
257 folder_exists(CONTEXT_S *cntxt, char *file)
259 return(folder_name_exists(cntxt, file, NULL));
263 /*----------------------------------------------------------------------
264 Check to see if folder exists in given context
266 Args: cntxt -- context in which to interpret "file" arg
267 file -- name of folder to check
268 name -- name of folder folder with context applied
270 Result: returns FEX_ISFILE if the folder exists and is a folder
271 FEX_ISDIR if the folder exists and is a directory
272 FEX_NOENT if it doesn't exist
273 FEX_ERROR on error
275 The two existence return values above may be logically OR'd
277 Uses mail_list to sniff out the existence of the requested folder.
278 The context string is just here for convenience. Checking for
279 folder's existence within a given context is probably more efficiently
280 handled outside this function for now using build_folder_list().
282 ----*/
284 folder_name_exists(CONTEXT_S *cntxt, char *file, char **fullpath)
286 MM_LIST_S ldata;
287 EXISTDATA_S parms;
288 int we_cancel = 0, res;
289 char *p, reference[MAILTMPLEN], tmp[MAILTMPLEN], *tfolder = NULL;
292 * No folder means "inbox".
294 if(*file == '{' && (p = strchr(file, '}')) && (!*(p+1))){
295 size_t l;
297 l = strlen(file)+strlen("inbox");
298 tfolder = (char *) fs_get((l+1) * sizeof(char));
299 snprintf(tfolder, l+1, "%s%s", file, "inbox");
300 file = tfolder;
301 cntxt = NULL;
304 mm_list_info = &ldata; /* tie down global reference */
305 memset(&ldata, 0, sizeof(ldata));
306 ldata.filter = mail_list_exists;
308 ldata.stream = sp_stream_get(context_apply(tmp, cntxt, file, sizeof(tmp)),
309 SP_SAME);
311 memset(ldata.data = &parms, 0, sizeof(EXISTDATA_S));
314 * If no preset reference string, must be at top of context
316 if(cntxt && context_isambig(file)){
317 /* inbox in first context is the real inbox */
318 if(ps_global->context_list == cntxt && !strucmp(file, ps_global->inbox_name)){
319 reference[0] = '\0';
320 parms.args.reference = reference;
322 else if(!(parms.args.reference = cntxt->dir->ref)){
323 char *p;
325 if((p = strstr(cntxt->context, "%s")) != NULL){
326 strncpy(parms.args.reference = reference,
327 cntxt->context,
328 MIN(p - cntxt->context, sizeof(reference)-1));
329 reference[MIN(p - cntxt->context, sizeof(reference)-1)] = '\0';
330 if(*(p += 2))
331 parms.args.tail = p;
333 else
334 parms.args.reference = cntxt->context;
337 parms.fullname = fullpath;
340 ps_global->mm_log_error = 0;
341 ps_global->noshow_error = 1;
343 we_cancel = busy_cue(NULL, NULL, 1);
345 parms.args.name = file;
347 res = pine_mail_list(ldata.stream, parms.args.reference, parms.args.name,
348 &ldata.options);
350 if(we_cancel)
351 cancel_busy_cue(-1);
353 ps_global->noshow_error = 0;
355 if(cntxt && cntxt->dir && parms.response.delim)
356 cntxt->dir->delim = parms.response.delim;
358 if(tfolder)
359 fs_give((void **)&tfolder);
360 return(((res == FALSE) || ps_global->mm_log_error)
361 ? FEX_ERROR
362 : (((parms.response.isfile)
363 ? FEX_ISFILE : 0 )
364 | ((parms.response.isdir)
365 ? FEX_ISDIR : 0)
366 | ((parms.response.ismarked)
367 ? FEX_ISMARKED : 0)
368 | ((parms.response.unmarked)
369 ? FEX_UNMARKED : 0)));
373 void
374 mail_list_exists(MAILSTREAM *stream, char *mailbox, int delim, long int attribs,
375 void *data, unsigned int options)
377 if(delim)
378 ((EXISTDATA_S *) data)->response.delim = delim;
380 if(mailbox && *mailbox){
381 if(!(attribs & LATT_NOSELECT)){
382 ((EXISTDATA_S *) data)->response.isfile = 1;
383 ((EXISTDATA_S *) data)->response.count += 1;
386 if(!(attribs & LATT_NOINFERIORS)){
387 ((EXISTDATA_S *) data)->response.isdir = 1;
388 ((EXISTDATA_S *) data)->response.count += 1;
391 if(attribs & LATT_MARKED)
392 ((EXISTDATA_S *) data)->response.ismarked = 1;
394 /* don't mark #move folders unmarked */
395 if(attribs & LATT_UNMARKED && !(options & PML_IS_MOVE_MBOX))
396 ((EXISTDATA_S *) data)->response.unmarked = 1;
398 if(attribs & LATT_HASCHILDREN)
399 ((EXISTDATA_S *) data)->response.haschildren = 1;
401 if(attribs & LATT_HASNOCHILDREN)
402 ((EXISTDATA_S *) data)->response.hasnochildren = 1;
404 if(((EXISTDATA_S *) data)->fullname
405 && ((((EXISTDATA_S *) data)->args.reference[0] != '\0')
406 ? struncmp(((EXISTDATA_S *) data)->args.reference, mailbox,
407 strlen(((EXISTDATA_S *)data)->args.reference))
408 : struncmp(((EXISTDATA_S *) data)->args.name, mailbox,
409 strlen(((EXISTDATA_S *) data)->args.name)))){
410 char *p;
411 size_t alloclen;
412 size_t len = (((stream && stream->mailbox)
413 ? strlen(stream->mailbox) : 0)
414 + strlen(((EXISTDATA_S *) data)->args.reference)
415 + strlen(((EXISTDATA_S *) data)->args.name)
416 + strlen(mailbox)) * sizeof(char);
419 * Fully qualify (in the c-client name structure sense)
420 * anything that's not in the context of the "reference"...
422 if(*((EXISTDATA_S *) data)->fullname)
423 fs_give((void **) ((EXISTDATA_S *) data)->fullname);
425 alloclen = len;
426 *((EXISTDATA_S *) data)->fullname = (char *) fs_get(alloclen);
427 if(*mailbox != '{'
428 && stream && stream->mailbox && *stream->mailbox == '{'
429 && (p = strindex(stream->mailbox, '}'))){
430 len = (p - stream->mailbox) + 1;
431 strncpy(*((EXISTDATA_S *) data)->fullname,
432 stream->mailbox, MIN(len,alloclen));
433 p = *((EXISTDATA_S *) data)->fullname + len;
435 else
436 p = *((EXISTDATA_S *) data)->fullname;
438 strncpy(p, mailbox, alloclen-(p-(*((EXISTDATA_S *) data)->fullname)));
439 (*((EXISTDATA_S *) data)->fullname)[alloclen-1] = '\0';;
445 void
446 mail_list_response(MAILSTREAM *stream, char *mailbox, int delim, long int attribs,
447 void *data, unsigned int options)
449 int counted = 0;
451 if(delim)
452 ((LISTRES_S *) data)->delim = delim;
454 if(mailbox && *mailbox){
455 if(!(attribs & LATT_NOSELECT)){
456 counted++;
457 ((LISTRES_S *) data)->isfile = 1;
458 ((LISTRES_S *) data)->count += 1;
461 if(!(attribs & LATT_NOINFERIORS)){
462 ((LISTRES_S *) data)->isdir = 1;
464 if(!counted)
465 ((LISTRES_S *) data)->count += 1;
468 if(attribs & LATT_HASCHILDREN)
469 ((LISTRES_S *) data)->haschildren = 1;
471 if(attribs & LATT_HASNOCHILDREN)
472 ((LISTRES_S *) data)->hasnochildren = 1;
477 char *
478 folder_as_breakout(CONTEXT_S *cntxt, char *name)
480 if(context_isambig(name)){ /* if simple check doesn't pan out */
481 char tmp[2*MAILTMPLEN], *p, *f; /* look harder */
483 if(!cntxt->dir->delim){
484 (void) context_apply(tmp, cntxt, "", sizeof(tmp)/2);
485 cntxt->dir->delim = get_folder_delimiter(tmp);
488 if((p = strindex(name, cntxt->dir->delim)) != NULL){
489 if(p == name){ /* assumption 6,321: delim is root */
490 if(cntxt->context[0] == '{'
491 && (p = strindex(cntxt->context, '}'))){
492 strncpy(tmp, cntxt->context,
493 MIN((p - cntxt->context) + 1, sizeof(tmp)/2));
494 tmp[MIN((p - cntxt->context) + 1, sizeof(tmp)/2)] = '\0';
495 strncpy(&tmp[MIN((p - cntxt->context) + 1, sizeof(tmp)/2)],
496 name, sizeof(tmp)/2-strlen(tmp));
497 tmp[sizeof(tmp)-1] = '\0';
498 return(cpystr(tmp));
500 else
501 return(cpystr(name));
503 else{ /* assumption 6,322: no create ~foo */
504 strncpy(tmp, name, MIN(p - name, MAILTMPLEN));
505 /* lop off trailingpath */
506 tmp[MIN(p - name, sizeof(tmp)/2)] = '\0';
507 f = NULL;
508 (void)folder_name_exists(cntxt, tmp, &f);
509 if(f){
510 snprintf(tmp, sizeof(tmp), "%s%s",f,p);
511 tmp[sizeof(tmp)-1] = '\0';
512 fs_give((void **) &f);
513 return(cpystr(tmp));
519 return(NULL);
523 /*----------------------------------------------------------------------
524 Initialize global list of contexts for folder collections.
526 Interprets collections defined in the pinerc and orders them for
527 pine's use. Parses user-provided context labels and sets appropriate
528 use flags and the default prototype for that collection.
529 (See find_folders for how the actual folder list is found).
531 ----*/
532 void
533 init_folders(struct pine *ps)
535 CONTEXT_S *tc, *top = NULL, **clist;
536 int i, prime = 0;
538 clist = ⊤
541 * If no incoming folders are config'd, but the user asked for
542 * them via feature, make sure at least "inbox" ends up there...
544 if(F_ON(F_ENABLE_INCOMING, ps) && !ps->VAR_INCOMING_FOLDERS){
545 ps->VAR_INCOMING_FOLDERS = (char **)fs_get(2 * sizeof(char *));
546 ps->VAR_INCOMING_FOLDERS[0] = cpystr(ps->inbox_name);
547 ps->VAR_INCOMING_FOLDERS[1] = NULL;
551 * Build context that's a list of folders the user's defined
552 * as receiving new messages. At some point, this should
553 * probably include adding a prefix with the new message count.
554 * fake new context...
556 if(ps->VAR_INCOMING_FOLDERS && ps->VAR_INCOMING_FOLDERS[0]
557 && (tc = new_context("Incoming-Folders []", NULL))){
558 tc->dir->status &= ~CNTXT_NOFIND;
559 tc->use |= CNTXT_INCMNG; /* mark this as incoming collection */
560 if(tc->label)
561 fs_give((void **) &tc->label);
563 /* TRANSLATORS: a label */
564 tc->label = cpystr(_("Incoming Message Folders"));
566 *clist = tc;
567 clist = &tc->next;
569 init_incoming_folder_list(ps, tc);
573 * Build list of folder collections. Because of the way init.c
574 * works, we're guaranteed at least a default. Also write any
575 * "bogus format" messages...
577 for(i = 0; ps->VAR_FOLDER_SPEC && ps->VAR_FOLDER_SPEC[i] ; i++)
578 if((tc = new_context(ps->VAR_FOLDER_SPEC[i], &prime)) != NULL){
579 *clist = tc; /* add it to list */
580 clist = &tc->next; /* prepare for next */
581 tc->var.v = &ps->vars[V_FOLDER_SPEC];
582 tc->var.i = i;
587 * Whoah cowboy!!! Guess we couldn't find a valid folder
588 * collection???
590 if(!prime)
591 alpine_panic(_("No folder collections defined"));
594 * At this point, insert the INBOX mapping as the leading
595 * folder entry of the first collection...
597 init_inbox_mapping(ps->VAR_INBOX_PATH, top);
599 set_news_spec_current_val(TRUE, TRUE);
602 * If news groups, loop thru list adding to collection list
604 for(i = 0; ps->VAR_NEWS_SPEC && ps->VAR_NEWS_SPEC[i] ; i++)
605 if(ps->VAR_NEWS_SPEC[i][0]
606 && (tc = new_context(ps->VAR_NEWS_SPEC[i], NULL))){
607 *clist = tc; /* add it to list */
608 clist = &tc->next; /* prepare for next */
609 tc->var.v = &ps->vars[V_NEWS_SPEC];
610 tc->var.i = i;
613 ps->context_list = top; /* init pointers */
614 ps->context_current = (top->use & CNTXT_INCMNG) ? top->next : top;
615 ps->context_last = NULL;
616 /* Tie up all the previous pointers */
617 for(; top; top = top->next)
618 if(top->next)
619 top->next->prev = top;
621 #ifdef DEBUG
622 dump_contexts();
623 #endif
628 * Add incoming list of folders to context.
630 void
631 init_incoming_folder_list(struct pine *ps, CONTEXT_S *cntxt)
633 int i;
634 char *folder_string, *nickname;
635 FOLDER_S *f;
637 for(i = 0; ps->VAR_INCOMING_FOLDERS[i] ; i++){
639 * Parse folder line for nickname and folder name.
640 * No nickname on line is OK.
642 get_pair(ps->VAR_INCOMING_FOLDERS[i], &nickname, &folder_string,0,0);
645 * Allow for inbox to be specified in the incoming list, but
646 * don't let it show up along side the one magically inserted
647 * above!
649 if(!folder_string || !strucmp(ps->inbox_name, folder_string)){
650 if(folder_string)
651 fs_give((void **)&folder_string);
653 if(nickname)
654 fs_give((void **)&nickname);
656 continue;
658 else if(update_bboard_spec(folder_string, tmp_20k_buf, SIZEOF_20KBUF)){
659 fs_give((void **) &folder_string);
660 folder_string = cpystr(tmp_20k_buf);
663 f = new_folder(folder_string,
664 line_hash(ps->VAR_INCOMING_FOLDERS[i]));
665 f->isfolder = 1;
666 fs_give((void **)&folder_string);
668 if(nickname){
669 if(strucmp(ps->inbox_name, nickname)){
670 f->nickname = nickname;
671 f->name_len = strlen(f->nickname);
673 else
674 fs_give((void **)&nickname);
677 init_incoming_unseen_data(ps, f);
679 folder_insert(f->nickname
680 && (strucmp(f->nickname, ps->inbox_name) == 0)
681 ? -1 : folder_total(FOLDERS(cntxt)),
682 f, FOLDERS(cntxt));
687 void
688 init_incoming_unseen_data(struct pine *ps, FOLDER_S *f)
690 int j, check_this = 0;
692 if(!f)
693 return;
695 /* see if this folder is in the monitoring list */
696 if(F_ON(F_ENABLE_INCOMING_CHECKING, ps)){
697 if(!ps->VAR_INCCHECKLIST)
698 check_this++; /* everything in by default */
699 else{
700 for(j = 0; !check_this && ps->VAR_INCCHECKLIST[j]; j++){
701 if((f->nickname && !strucmp(ps->VAR_INCCHECKLIST[j],f->nickname))
702 || (f->name && !strucmp(ps->VAR_INCCHECKLIST[j],f->name)))
703 check_this++;
708 if(check_this)
709 f->last_unseen_update = LUU_INIT;
710 else
711 f->last_unseen_update = LUU_NEVERCHK;
715 void
716 reinit_incoming_folder_list(struct pine *ps, CONTEXT_S *context)
718 free_folder_entries(&(FOLDERS(context)));
719 FOLDERS(context) = init_folder_entries();
720 init_incoming_folder_list(ps_global, context);
721 init_inbox_mapping(ps_global->VAR_INBOX_PATH, context);
728 void
729 init_inbox_mapping(char *path, CONTEXT_S *cntxt)
731 FOLDER_S *f;
732 int check_this, j;
735 * If mapping already exists, blast it and replace it below...
737 if((f = folder_entry(0, FOLDERS(cntxt)))
738 && f->nickname && !strcmp(f->nickname, ps_global->inbox_name))
739 folder_delete(0, FOLDERS(cntxt));
741 if(path){
742 f = new_folder(path, 0);
743 f->nickname = cpystr(ps_global->inbox_name);
744 f->name_len = strlen(f->nickname);
746 else
747 f = new_folder(ps_global->inbox_name, 0);
749 f->isfolder = 1;
751 /* see if this folder is in the monitoring list */
752 check_this = 0;
753 if(F_ON(F_ENABLE_INCOMING_CHECKING, ps_global) && ps_global->VAR_INCOMING_FOLDERS && ps_global->VAR_INCOMING_FOLDERS[0]){
754 if(!ps_global->VAR_INCCHECKLIST)
755 check_this++; /* everything in by default */
756 else{
757 for(j = 0; !check_this && ps_global->VAR_INCCHECKLIST[j]; j++){
758 if((f->nickname && !strucmp(ps_global->VAR_INCCHECKLIST[j],f->nickname))
759 || (f->name && !strucmp(ps_global->VAR_INCCHECKLIST[j],f->name)))
760 check_this++;
765 if(check_this)
766 f->last_unseen_update = LUU_INIT;
767 else
768 f->last_unseen_update = LUU_NEVERCHK;
770 folder_insert(0, f, FOLDERS(cntxt));
774 FDIR_S *
775 new_fdir(char *ref, char *view, int wildcard)
777 FDIR_S *rv = (FDIR_S *) fs_get(sizeof(FDIR_S));
779 memset((void *) rv, 0, sizeof(FDIR_S));
781 /* Monkey with the view to make sure it has wildcard? */
782 if(view && *view){
783 rv->view.user = cpystr(view);
786 * This is sorta hairy since, for simplicity we allow
787 * users to use '*' in the view, but for mail
788 * we really mean '%' as def'd in 2060...
790 if(wildcard == '*'){ /* must be #news. */
791 if(strindex(view, wildcard))
792 rv->view.internal = cpystr(view);
794 else{ /* must be mail */
795 char *p;
797 if((p = strpbrk(view, "*%")) != NULL){
798 rv->view.internal = p = cpystr(view);
799 while((p = strpbrk(p, "*%")) != NULL)
800 *p++ = '%'; /* convert everything to '%' */
804 if(!rv->view.internal){
805 size_t l;
807 l = strlen(view)+2;
808 rv->view.internal = (char *) fs_get((l+1) * sizeof(char));
809 snprintf(rv->view.internal, l+1, "%c%s%c", wildcard, view, wildcard);
812 else{
813 rv->view.internal = (char *) fs_get(2 * sizeof(char));
814 snprintf(rv->view.internal, 2, "%c", wildcard);
817 if(ref)
818 rv->ref = ref;
820 rv->folders = init_folder_entries();
821 rv->status = CNTXT_NOFIND;
822 return(rv);
826 void
827 free_fdir(FDIR_S **f, int recur)
829 if(f && *f){
830 if((*f)->prev && recur)
831 free_fdir(&(*f)->prev, 1);
833 if((*f)->ref)
834 fs_give((void **)&(*f)->ref);
836 if((*f)->view.user)
837 fs_give((void **)&(*f)->view.user);
839 if((*f)->view.internal)
840 fs_give((void **)&(*f)->view.internal);
842 if((*f)->desc)
843 fs_give((void **)&(*f)->desc);
845 free_folder_entries(&(*f)->folders);
846 fs_give((void **) f);
852 update_bboard_spec(char *bboard, char *buf, size_t buflen)
854 char *p = NULL, *origbuf;
855 int nntp = 0, bracket = 0;
857 origbuf = buf;
859 if(*bboard == '*'
860 || (*bboard == '{' && (p = strindex(bboard, '}')) && *(p+1) == '*')){
861 /* server name ? */
862 if(p || (*(bboard+1) == '{' && (p = strindex(++bboard, '}'))))
863 while(bboard <= p && (buf-origbuf < buflen)) /* copy it */
864 if((*buf++ = *bboard++) == '/' && !strncmp(bboard, "nntp", 4))
865 nntp++;
867 if(*bboard == '*')
868 bboard++;
870 if(!nntp){
872 * See if path portion looks newsgroup-ish while being aware
873 * of the "view" portion of the spec...
875 for(p = bboard; *p; p++)
876 if(*p == '['){
877 if(bracket) /* only one set allowed! */
878 break;
879 else
880 bracket++;
882 else if(*p == ']'){
883 if(bracket != 1) /* must be closing bracket */
884 break;
885 else
886 bracket++;
888 else if(!(isalnum((unsigned char) *p) || strindex(".-", *p)))
889 break;
892 snprintf(buf, buflen-(buf-origbuf), "%s%s%s",
893 (!nntp && *p) ? "#public" : "#news.",
894 (!nntp && *p && *bboard != '/') ? "/" : "",
895 bboard);
897 return(1);
900 return(0);
905 * build_folder_list - call mail_list to fetch us a list of folders
906 * from the given context.
908 void
909 build_folder_list(MAILSTREAM **stream, CONTEXT_S *context, char *pat, char *content, int flags)
911 MM_LIST_S ldata;
912 BFL_DATA_S response;
913 int local_open = 0, we_cancel = 0, resort = 0;
914 char reference[2*MAILTMPLEN], *p;
916 if(!(context->dir->status & CNTXT_NOFIND)
917 || (context->dir->status & CNTXT_PARTFIND))
918 return; /* find already done! */
920 dprint((7, "build_folder_list: %s %s\n",
921 context ? context->context : "NULL",
922 pat ? pat : "NULL"));
924 we_cancel = busy_cue(NULL, NULL, 1);
927 * Set up the pattern of folder name's to match within the
928 * given context.
930 if(!pat || ((*pat == '*' || *pat == '%') && *(pat+1) == '\0')){
931 context->dir->status &= ~CNTXT_NOFIND; /* let'em know we tried */
932 pat = context->dir->view.internal;
934 else
935 context->use |= CNTXT_PARTFIND; /* or are in a partial find */
937 memset(mm_list_info = &ldata, 0, sizeof(MM_LIST_S));
938 ldata.filter = (NEWS_TEST(context)) ? mail_lsub_filter : mail_list_filter;
939 memset(ldata.data = &response, 0, sizeof(BFL_DATA_S));
940 response.list = FOLDERS(context);
942 if(flags & BFL_FLDRONLY)
943 response.mask = LATT_NOSELECT;
946 * if context is associated with a server, prepare a stream for
947 * sending our request.
949 if(*context->context == '{'){
951 * Try using a stream we've already got open...
953 if(stream && *stream
954 && !(ldata.stream = same_stream(context->context, *stream))){
955 pine_mail_close(*stream);
956 *stream = NULL;
959 if(!ldata.stream)
960 ldata.stream = sp_stream_get(context->context, SP_MATCH);
962 if(!ldata.stream)
963 ldata.stream = sp_stream_get(context->context, SP_SAME);
965 /* gotta open a new one? */
966 if((F_OFF(F_CMBND_FOLDER_DISP, ps_global)
967 || context->update == LUU_INIT) && !ldata.stream){
968 ldata.stream = mail_cmd_stream(context, &local_open);
969 if(stream)
970 *stream = ldata.stream;
973 dprint((ldata.stream ? 7 : 1, "build_folder_list: mail_open(%s) %s.\n",
974 context->server ? context->server : "?",
975 ldata.stream ? "OK" : "FAILED"));
977 if(!ldata.stream){
978 context->use &= ~CNTXT_PARTFIND; /* unset partial find bit */
979 context->update = LUU_NOMORECHK;
980 if(we_cancel)
981 cancel_busy_cue(-1);
983 return;
986 else if(stream && *stream){ /* no server, simple case */
987 if(!sp_flagged(*stream, SP_LOCKED))
988 pine_mail_close(*stream);
990 *stream = NULL;
994 * If preset reference string, we're somewhere in the hierarchy.
995 * ELSE we must be at top of context...
997 response.args.name = pat;
998 if(!(response.args.reference = context->dir->ref)){
999 if((p = strstr(context->context, "%s")) != NULL){
1000 strncpy(response.args.reference = reference,
1001 context->context,
1002 MIN(p - context->context, sizeof(reference)/2));
1003 reference[MIN(p - context->context, sizeof(reference)/2)] = '\0';
1004 if(*(p += 2))
1005 response.args.tail = p;
1007 else
1008 response.args.reference = context->context;
1011 if(flags & BFL_SCAN)
1012 mail_scan(ldata.stream, response.args.reference,
1013 response.args.name, content);
1014 else if(flags & BFL_LSUB)
1015 mail_lsub(ldata.stream, response.args.reference, response.args.name);
1016 else{
1017 set_read_predicted(1);
1018 pine_mail_list(ldata.stream, response.args.reference, response.args.name,
1019 &ldata.options);
1020 set_read_predicted(0);
1023 context->update = LUU_INIT;
1024 if(context->dir && response.response.delim)
1025 context->dir->delim = response.response.delim;
1027 if(!(flags & (BFL_LSUB|BFL_SCAN)) && F_ON(F_QUELL_EMPTY_DIRS, ps_global)){
1028 LISTRES_S listres;
1029 FOLDER_S *f;
1030 int i;
1032 for(i = 0; i < folder_total(response.list); i++)
1033 if((f = folder_entry(i, FOLDERS(context)))->isdir){
1035 * we don't need to do a list if we know already
1036 * whether there are children or not.
1038 if(!f->haschildren && !f->hasnochildren){
1039 memset(ldata.data = &listres, 0, sizeof(LISTRES_S));
1040 ldata.filter = mail_list_response;
1042 if(context->dir->ref)
1043 snprintf(reference, sizeof(reference), "%s%s", context->dir->ref, f->name);
1044 else
1045 context_apply(reference, context, f->name, sizeof(reference)/2);
1047 /* append the delimiter to the reference */
1048 for(p = reference; *p; p++)
1051 *p++ = context->dir->delim;
1052 *p = '\0';
1054 pine_mail_list(ldata.stream, reference, "%", NULL);
1056 /* anything interesting inside? */
1057 f->hasnochildren = (listres.count <= 1L);
1058 f->haschildren = !f->hasnochildren;;
1061 if(f->hasnochildren){
1062 if(f->isfolder){
1063 f->isdir = 0;
1064 if(ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_FIRST
1065 || ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_LAST)
1066 resort = 1;
1069 * We don't want to hide directories
1070 * that are only directories even if they are
1071 * empty. We only want to hide the directory
1072 * piece of a dual-use folder when there are
1073 * no children in the directory (and the user
1074 * most likely thinks of it as a folder instead
1075 * of a folder and a directory).
1077 else if(f->isdir && f->isdual){
1078 folder_delete(i, FOLDERS(context));
1079 i--;
1085 if(resort)
1086 resort_folder_list(response.list);
1088 if(local_open && !stream)
1089 pine_mail_close(ldata.stream);
1091 if(context->use & CNTXT_PRESRV)
1092 folder_select_restore(context);
1094 context->use &= ~CNTXT_PARTFIND; /* unset partial list bit */
1095 if(we_cancel)
1096 cancel_busy_cue(-1);
1101 * Validate LIST response to command issued from build_folder_list
1104 void
1105 mail_list_filter(MAILSTREAM *stream, char *mailbox, int delim, long int attribs, void *data, unsigned int options)
1107 BFL_DATA_S *ld = (BFL_DATA_S *) data;
1108 FOLDER_S *new_f = NULL, *dual_f = NULL;
1109 int suppress_folder_add = 0;
1111 if(!ld)
1112 return;
1114 if(delim)
1115 ld->response.delim = delim;
1117 /* test against mask of DIS-allowed attributes */
1118 if((ld->mask & attribs)){
1119 dprint((3, "mail_list_filter: failed attribute test"));
1120 return;
1124 * First, make sure response fits our "reference" arg
1125 * NOTE: build_folder_list can't supply breakout?
1127 if(!mail_list_in_collection(&mailbox, ld->args.reference,
1128 ld->args.name, ld->args.tail))
1129 return;
1131 /* ignore dotfolders unless told not to */
1132 if(F_OFF(F_ENABLE_DOT_FOLDERS, ps_global) && *mailbox == '.'){
1133 dprint((3, "mail_list_filter: dotfolder disallowed"));
1134 return;
1138 * If this response is INBOX we have to handle it specially.
1139 * The special cases are:
1141 * Incoming folders are enabled and this INBOX is in the Incoming
1142 * folders list already. We don't want to list it here, as well,
1143 * unless it is also a directory.
1145 * Incoming folders are not enabled, but we inserted INBOX into
1146 * this primary collection (with init_inbox_mapping()) so it is
1147 * already accounted for unless it is also a directory.
1149 * Incoming folders are not enabled, but we inserted INBOX into
1150 * the primary collection (which we are not looking at here) so
1151 * it is already accounted for there. We'll add it as a directory
1152 * as well here if that is what is called for.
1154 if(!strucmp(mailbox, ps_global->inbox_name)){
1155 int ftotal, i;
1156 FOLDER_S *f = NULL;
1157 char fullname[1000], tmp1[1000], tmp2[1000], *l1, *l2;
1159 /* fullname is the name of the mailbox in the list response */
1160 if(ld->args.reference){
1161 strncpy(fullname, ld->args.reference, sizeof(fullname)-1);
1162 fullname[sizeof(fullname)-1] = '\0';
1164 else
1165 fullname[0] = '\0';
1167 strncat(fullname, mailbox, sizeof(fullname)-strlen(fullname)-1);
1168 fullname[sizeof(fullname)-1] = '\0';
1170 /* check if Incoming Folders are enabled */
1171 if(ps_global->context_list && ps_global->context_list->use & CNTXT_INCMNG){
1172 int this_inbox_is_in_incoming = 0;
1175 * Figure out if this INBOX is already in the Incoming list.
1178 /* compare fullname to each incoming folder */
1179 ftotal = folder_total(FOLDERS(ps_global->context_list));
1180 for(i = 0; i < ftotal && !this_inbox_is_in_incoming; i++){
1181 f = folder_entry(i, FOLDERS(ps_global->context_list));
1182 if(f && f->name){
1183 if(same_remote_mailboxes(fullname, f->name)
1184 || ((!IS_REMOTE(fullname) && !IS_REMOTE(f->name))
1185 && (l1=mailboxfile(tmp1,fullname))
1186 && (l2=mailboxfile(tmp2,f->name))
1187 && !strcmp(l1,l2)))
1188 this_inbox_is_in_incoming++;
1192 if(this_inbox_is_in_incoming){
1194 * Don't add a folder for this, only a directory if called for.
1195 * If it isn't a directory, skip it.
1197 if(!(delim && !(attribs & LATT_NOINFERIORS)))
1198 return;
1200 suppress_folder_add++;
1203 else{
1204 int inbox_is_in_this_collection = 0;
1206 /* is INBOX already in this folder list? */
1207 ftotal = folder_total(ld->list);
1208 for(i = 0; i < ftotal && !inbox_is_in_this_collection; i++){
1209 f = folder_entry(i, ld->list);
1210 if(!strucmp(FLDR_NAME(f), ps_global->inbox_name))
1211 inbox_is_in_this_collection++;
1214 if(inbox_is_in_this_collection){
1217 * Inbox is already inserted in this collection. Unless
1218 * it is also a directory, we are done.
1220 if(!(delim && !(attribs & LATT_NOINFERIORS)))
1221 return;
1224 * Since it is also a directory, what we do depends on
1225 * the F_SEP feature. If that feature is not set we just
1226 * want to mark the existing entry dual-use. If F_SEP is
1227 * set we want to add a new directory entry.
1229 if(F_ON(F_SEPARATE_FLDR_AS_DIR, ps_global)){
1230 f->isdir = 0;
1231 f->haschildren = 0;
1232 f->hasnochildren = 0;
1233 /* fall through and add a new directory */
1235 else{
1236 /* mark existing entry dual-use and return */
1237 ld->response.count++;
1238 ld->response.isdir = 1;
1239 f->isdir = 1;
1240 if(attribs & LATT_HASCHILDREN)
1241 f->haschildren = 1;
1243 if(attribs & LATT_HASNOCHILDREN)
1244 f->hasnochildren = 1;
1246 return;
1249 suppress_folder_add++;
1251 else{
1252 int found_it = 0;
1253 int this_inbox_is_primary_inbox = 0;
1256 * See if this INBOX is the same as the INBOX we inserted
1257 * in the primary collection.
1260 /* first find the existing INBOX entry */
1261 ftotal = folder_total(FOLDERS(ps_global->context_list));
1262 for(i = 0; i < ftotal && !found_it; i++){
1263 f = folder_entry(i, FOLDERS(ps_global->context_list));
1264 if(!strucmp(FLDR_NAME(f), ps_global->inbox_name))
1265 found_it++;
1268 if(found_it && f && f->name){
1269 if(same_remote_mailboxes(fullname, f->name)
1270 || ((!IS_REMOTE(fullname) && !IS_REMOTE(f->name))
1271 && (l1=mailboxfile(tmp1,fullname))
1272 && (l2=mailboxfile(tmp2,f->name))
1273 && !strcmp(l1,l2)))
1274 this_inbox_is_primary_inbox++;
1277 if(this_inbox_is_primary_inbox){
1279 * Don't add a folder for this, only a directory if called for.
1280 * If it isn't a directory, skip it.
1282 if(!(delim && !(attribs & LATT_NOINFERIORS)))
1283 return;
1285 suppress_folder_add++;
1291 /* is it a mailbox? */
1292 if(!(attribs & LATT_NOSELECT) && !suppress_folder_add){
1293 ld->response.count++;
1294 ld->response.isfile = 1;
1295 new_f = new_folder(mailbox, 0);
1296 new_f->isfolder = 1;
1298 if(F_ON(F_SEPARATE_FLDR_AS_DIR, ps_global)){
1299 folder_insert(-1, new_f, ld->list);
1300 dual_f = new_f;
1301 new_f = NULL;
1305 /* directory? */
1306 if(delim && !(attribs & LATT_NOINFERIORS)){
1307 ld->response.count++;
1308 ld->response.isdir = 1;
1310 if(!new_f)
1311 new_f = new_folder(mailbox, 0);
1313 new_f->isdir = 1;
1314 if(attribs & LATT_HASCHILDREN)
1315 new_f->haschildren = 1;
1316 if(attribs & LATT_HASNOCHILDREN)
1317 new_f->hasnochildren = 1;
1320 * When we have F_SEPARATE_FLDR_AS_DIR we still want to know
1321 * whether the name really represents both so that we don't
1322 * inadvertently delete both when the user meant one or the
1323 * other.
1325 if(dual_f){
1326 if(attribs & LATT_HASCHILDREN)
1327 dual_f->haschildren = 1;
1329 if(attribs & LATT_HASNOCHILDREN)
1330 dual_f->hasnochildren = 1;
1332 dual_f->isdual = 1;
1333 new_f->isdual = 1;
1337 if(new_f)
1338 folder_insert(-1, new_f, ld->list);
1340 if(attribs & LATT_MARKED)
1341 ld->response.ismarked = 1;
1343 /* don't mark #move folders unmarked */
1344 if(attribs & LATT_UNMARKED && !(options & PML_IS_MOVE_MBOX))
1345 ld->response.unmarked = 1;
1347 if(attribs & LATT_HASCHILDREN)
1348 ld->response.haschildren = 1;
1350 if(attribs & LATT_HASNOCHILDREN)
1351 ld->response.hasnochildren = 1;
1356 * Validate LSUB response to command issued from build_folder_list
1359 void
1360 mail_lsub_filter(MAILSTREAM *stream, char *mailbox, int delim, long int attribs,
1361 void *data, unsigned int options)
1363 BFL_DATA_S *ld = (BFL_DATA_S *) data;
1364 FOLDER_S *new_f = NULL;
1365 char *ref;
1367 if(delim)
1368 ld->response.delim = delim;
1370 /* test against mask of DIS-allowed attributes */
1371 if((ld->mask & attribs)){
1372 dprint((3, "mail_lsub_filter: failed attribute test"));
1373 return;
1376 /* Normalize mailbox and reference strings re: namespace */
1377 if(!strncmp(mailbox, "#news.", 6))
1378 mailbox += 6;
1380 if(!strncmp(ref = ld->args.reference, "#news.", 6))
1381 ref += 6;
1383 if(!mail_list_in_collection(&mailbox, ref, ld->args.name, ld->args.tail))
1384 return;
1386 if(!(attribs & LATT_NOSELECT)){
1387 ld->response.count++;
1388 ld->response.isfile = 1;
1389 new_f = new_folder(mailbox, 0);
1390 new_f->isfolder = 1;
1391 folder_insert(F_ON(F_READ_IN_NEWSRC_ORDER, ps_global)
1392 ? folder_total(ld->list) : -1,
1393 new_f, ld->list);
1396 /* We don't support directories in #news */
1399 /* human readable name for a folder. memory freed by caller
1400 * This is a jacket to conversion from modified utf7 to utf8.
1402 unsigned char *folder_name_decoded(unsigned char *mailbox)
1404 unsigned char *s;
1405 s = (unsigned char *) utf8_from_mutf7((unsigned char *) mailbox);
1406 if (s == NULL) s = (unsigned char *) cpystr((char *)mailbox);
1407 return s;
1410 /* mutf7 encoded name of a folder, from its name in utf8.
1411 * memory freed by caller.
1413 unsigned char *folder_name_encoded(unsigned char *mailbox)
1415 unsigned char *s;
1416 if ((s = utf8_to_mutf7(mailbox)) == NULL)
1417 s = (unsigned char *) cpystr((char *) mailbox);
1418 return s;
1422 mail_list_in_collection(char **mailbox, char *ref, char *name, char *tail)
1424 int boxlen, reflen, taillen;
1425 char *p;
1427 boxlen = strlen(*mailbox);
1428 reflen = ref ? strlen(ref) : 0;
1429 taillen = tail ? strlen(tail) : 0;
1431 if(boxlen
1432 && (reflen ? !struncmp(*mailbox, ref, reflen)
1433 : (p = strpbrk(name, "%*"))
1434 ? !struncmp(*mailbox, name, p - name)
1435 : !strucmp(*mailbox,name))
1436 && (!taillen
1437 || (taillen < boxlen - reflen
1438 && !strucmp(&(*mailbox)[boxlen - taillen], tail)))){
1439 if(taillen)
1440 (*mailbox)[boxlen - taillen] = '\0';
1442 if(*(*mailbox += reflen))
1443 return(TRUE);
1446 * else don't worry about context "breakouts" since
1447 * build_folder_list doesn't let the user introduce
1448 * one...
1451 return(FALSE);
1456 * rebuild_folder_list -- free up old list and re-issue commands to build
1457 * a new list.
1459 void
1460 refresh_folder_list(CONTEXT_S *context, int nodirs, int startover, MAILSTREAM **streamp)
1462 if(startover)
1463 free_folder_list(context);
1465 build_folder_list(streamp, context, NULL, NULL,
1466 (NEWS_TEST(context) ? BFL_LSUB : BFL_NONE)
1467 | ((nodirs) ? BFL_FLDRONLY : BFL_NONE));
1472 * free_folder_list - loop thru the context's lists of folders
1473 * clearing all entries without nicknames
1474 * (as those were user provided) AND reset the
1475 * context's find flag.
1477 * NOTE: if fetched() information (e.g., like message counts come back
1478 * in bboard collections), we may want to have a check before
1479 * executing the loop and setting the FIND flag.
1481 void
1482 free_folder_list(CONTEXT_S *cntxt)
1484 int n, i;
1487 * In this case, don't blast the list as it was given to us by the
1488 * user and not the result of a mail_list call...
1490 if(cntxt->use & CNTXT_INCMNG)
1491 return;
1493 if(cntxt->use & CNTXT_PRESRV)
1494 folder_select_preserve(cntxt);
1496 for(n = folder_total(FOLDERS(cntxt)), i = 0; n > 0; n--)
1497 if(folder_entry(i, FOLDERS(cntxt))->nickname)
1498 i++; /* entry wasn't from LIST */
1499 else
1500 folder_delete(i, FOLDERS(cntxt));
1502 cntxt->dir->status |= CNTXT_NOFIND; /* do find next time... */
1503 /* or add the fake entry */
1504 cntxt->use &= ~(CNTXT_PSEUDO | CNTXT_PRESRV | CNTXT_ZOOM);
1509 * default_save_context - return the default context for saved messages
1511 CONTEXT_S *
1512 default_save_context(CONTEXT_S *cntxt)
1514 while(cntxt)
1515 if((cntxt->use) & CNTXT_SAVEDFLT)
1516 return(cntxt);
1517 else
1518 cntxt = cntxt->next;
1520 return(NULL);
1526 * folder_complete - foldername completion routine
1528 * Result: returns 0 if the folder doesn't have a any completion
1529 * 1 if the folder has a completion (*AND* "name" is
1530 * replaced with the completion)
1534 folder_complete(CONTEXT_S *context, char *name, size_t namelen, int *completions)
1536 return(folder_complete_internal(context, name, namelen, completions, FC_NONE));
1544 folder_complete_internal(CONTEXT_S *context, char *name, size_t namelen,
1545 int *completions, int flags)
1547 int i, match = -1, ftotal;
1548 char tmp[MAXFOLDER+2], *a, *b, *fn, *pat = NULL;
1549 FOLDER_S *f;
1551 if(completions)
1552 *completions = 0;
1554 if(*name == '\0' || !context_isambig(name))
1555 return(0);
1557 if(!((context->use & CNTXT_INCMNG) || ALL_FOUND(context))){
1559 * Build the folder list from scratch since we may need to
1560 * traverse hierarchy...
1563 free_folder_list(context);
1564 snprintf(tmp, sizeof(tmp), "%s%c", name, NEWS_TEST(context) ? '*' : '%');
1565 build_folder_list(NULL, context, tmp, NULL,
1566 (NEWS_TEST(context) & !(flags & FC_FORCE_LIST))
1567 ? BFL_LSUB : BFL_NONE);
1570 *tmp = '\0'; /* find uniq substring */
1571 ftotal = folder_total(FOLDERS(context));
1572 for(i = 0; i < ftotal; i++){
1573 f = folder_entry(i, FOLDERS(context));
1574 fn = FLDR_NAME(f);
1575 pat = name;
1576 if(!(NEWS_TEST(context) || (context->use & CNTXT_INCMNG))){
1577 fn = folder_last_cmpnt(fn, context->dir->delim);
1578 pat = folder_last_cmpnt(pat, context->dir->delim);
1581 if(!strncmp(fn, pat, strlen(pat))){
1582 if(match != -1){ /* oh well, do best we can... */
1583 a = fn;
1584 if(match >= 0){
1585 f = folder_entry(match, FOLDERS(context));
1586 fn = FLDR_NAME(f);
1587 if(!NEWS_TEST(context))
1588 fn = folder_last_cmpnt(fn, context->dir->delim);
1590 strncpy(tmp, fn, sizeof(tmp)-1);
1591 tmp[sizeof(tmp)-1] = '\0';
1594 match = -2;
1595 b = tmp; /* remember largest common text */
1596 while(*a && *b && *a == *b)
1597 *b++ = *a++;
1599 *b = '\0';
1601 else
1602 match = i; /* bingo?? */
1606 if(match >= 0){ /* found! */
1607 f = folder_entry(match, FOLDERS(context));
1608 fn = FLDR_NAME(f);
1609 if(!(NEWS_TEST(context) || (context->use & CNTXT_INCMNG)))
1610 fn = folder_last_cmpnt(fn, context->dir->delim);
1612 strncpy(pat, fn, namelen-(pat-name));
1613 name[namelen-1] = '\0';
1614 if(f->isdir && !f->isfolder){
1615 name[i = strlen(name)] = context->dir->delim;
1616 name[i+1] = '\0';
1619 else if(match == -2){ /* closest we could find */
1620 strncpy(pat, tmp, namelen-(pat-name));
1621 name[namelen-1] = '\0';
1624 if(completions)
1625 *completions = ftotal;
1627 if(!((context->use & CNTXT_INCMNG) || ALL_FOUND(context)))
1628 free_folder_list(context);
1630 return((match >= 0) ? ftotal : 0);
1634 char *
1635 folder_last_cmpnt(char *s, int d)
1637 register char *p;
1639 if(d)
1640 for(p = s; (p = strindex(p, d)); s = ++p)
1643 return(s);
1648 * init_folder_entries - return a piece of memory suitable for attaching
1649 * a list of folders...
1652 FLIST *
1653 init_folder_entries(void)
1655 FLIST *flist = (FLIST *) fs_get(sizeof(FLIST));
1656 flist->folders = (FOLDER_S **) fs_get(FCHUNK * sizeof(FOLDER_S *));
1657 memset((void *)flist->folders, 0, (FCHUNK * sizeof(FOLDER_S *)));
1658 flist->allocated = FCHUNK;
1659 flist->used = 0;
1660 return(flist);
1665 * new_folder - return a brand new folder entry, with the given name
1666 * filled in.
1668 * NOTE: THIS IS THE ONLY WAY TO PUT A NAME INTO A FOLDER ENTRY!!!
1669 * STRCPY WILL NOT WORK!!!
1671 FOLDER_S *
1672 new_folder(char *name, long unsigned int hash)
1674 FOLDER_S *tmp;
1675 size_t l = strlen(name);
1677 tmp = (FOLDER_S *)fs_get(sizeof(FOLDER_S) + (l * sizeof(char)));
1678 memset((void *)tmp, 0, sizeof(FOLDER_S));
1679 strncpy(tmp->name, name, l);
1680 tmp->name[l] = '\0';
1681 tmp->name_len = (unsigned char) l;
1682 tmp->varhash = hash;
1683 return(tmp);
1688 * folder_entry - folder struct access routine. Permit reference to
1689 * folder structs via index number. Serves two purposes:
1690 * 1) easy way for callers to access folder data
1691 * conveniently
1692 * 2) allows for a custom manager to limit memory use
1693 * under certain rather limited "operating systems"
1694 * who shall renameless, but whose initials are DOS
1698 FOLDER_S *
1699 folder_entry(int i, FLIST *flist)
1701 return((i >= flist->used) ? NULL:flist->folders[i]);
1706 * free_folder_entries - release all resources associated with the given
1707 * list of folder entries
1709 void
1710 free_folder_entries(FLIST **flist)
1712 register int i;
1714 if(!(flist && *flist))
1715 return;
1717 i = (*flist)->used;
1718 while(i--){
1719 if((*flist)->folders[i]->nickname)
1720 fs_give((void **) &(*flist)->folders[i]->nickname);
1722 fs_give((void **) &((*flist)->folders[i]));
1725 fs_give((void **) &((*flist)->folders));
1726 fs_give((void **) flist);
1731 * return the number of folders associated with the given folder list
1734 folder_total(FLIST *flist)
1736 return((int) flist->used);
1741 * return the index number of the given name in the given folder list
1744 folder_index(char *name, CONTEXT_S *cntxt, int flags)
1746 register int i = 0;
1747 FOLDER_S *f;
1748 char *fname;
1750 for(i = 0; (f = folder_entry(i, FOLDERS(cntxt))); i++)
1751 if(((flags & FI_FOLDER) && (f->isfolder || (cntxt->use & CNTXT_INCMNG)))
1752 || ((flags & FI_DIR) && f->isdir)){
1753 fname = FLDR_NAME(f);
1754 #if defined(DOS) || defined(OS2)
1755 if(flags & FI_RENAME){ /* case-dependent for rename */
1756 if(*name == *fname && strcmp(name, fname) == 0)
1757 return(i);
1759 else{
1760 if(toupper((unsigned char)(*name))
1761 == toupper((unsigned char)(*fname)) && strucmp(name, fname) == 0)
1762 return(i);
1764 #else
1765 if(*name == *fname && strcmp(name, fname) == 0)
1766 return(i);
1767 #endif
1770 return(-1);
1775 * folder_is_nick - check to see if the given name is a nickname
1776 * for some folder in the given context...
1778 * NOTE: no need to check if mm_list_names has been done as
1779 * nicknames can only be set by configuration...
1781 char *
1782 folder_is_nick(char *nickname, FLIST *flist, int flags)
1784 register int i = 0;
1785 FOLDER_S *f;
1787 if(!(nickname && *nickname && flist))
1788 return(NULL);
1790 while((f = folder_entry(i, flist)) != NULL){
1792 * The second part of the OR is checking in a case-indep
1793 * way for INBOX. It should be restricted to the context
1794 * to which we add the INBOX folder, which would be either
1795 * the Incoming Folders collection or the first collection
1796 * if there is no Incoming collection. We don't need to check
1797 * the collection because nickname assignment has already
1798 * done that for us. Most folders don't have nicknames, only
1799 * incoming folders and folders like inbox if not in incoming.
1801 if(f->nickname
1802 && (!strcmp(nickname, f->nickname)
1803 || (!strucmp(nickname, f->nickname)
1804 && !strucmp(nickname, ps_global->inbox_name)))){
1805 char source[MAILTMPLEN], *target = NULL;
1808 * If f is a maildrop, then we want to return the
1809 * destination folder, not the whole #move thing.
1811 if(!(flags & FN_WHOLE_NAME)
1812 && check_for_move_mbox(f->name, source, sizeof(source), &target))
1813 return(target);
1814 else
1815 return(f->name);
1817 else
1818 i++;
1821 return(NULL);
1825 char *
1826 folder_is_target_of_nick(char *longname, CONTEXT_S *cntxt)
1828 register int i = 0;
1829 FOLDER_S *f;
1830 FLIST *flist = NULL;
1832 if(cntxt && cntxt == ps_global->context_list)
1833 flist = FOLDERS(cntxt);
1835 if(!(longname && *longname && flist))
1836 return(NULL);
1838 while((f = folder_entry(i, flist)) != NULL){
1839 if(f->nickname && f->name && !strcmp(longname, f->name))
1840 return(f->nickname);
1841 else
1842 i++;
1845 return(NULL);
1849 /*----------------------------------------------------------------------
1850 Insert the given folder name into the sorted folder list
1851 associated with the given context. Only allow ambiguous folder
1852 names IF associated with a nickname.
1854 Args: index -- Index to insert at, OR insert in sorted order if -1
1855 folder -- folder structure to insert into list
1856 flist -- folder list to insert folder into
1858 **** WARNING ****
1859 DON'T count on the folder pointer being valid after this returns
1860 *** ALL FOLDER ELEMENT READS SHOULD BE THROUGH folder_entry() ***
1862 ----*/
1864 folder_insert(int index, FOLDER_S *folder, FLIST *flist)
1866 /* requested index < 0 means add to sorted list */
1867 if(index < 0 && (index = folder_total(flist)) > 0)
1868 index = folder_insert_sorted(index / 2, 0, index, folder, flist,
1869 (ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_FIRST)
1870 ? compare_folders_dir_alpha
1871 : (ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_LAST)
1872 ? compare_folders_alpha_dir
1873 : compare_folders_alpha);
1875 folder_insert_index(folder, index, flist);
1876 return(index);
1881 * folder_insert_sorted - Insert given folder struct into given list
1882 * observing sorting specified by given
1883 * comparison function
1886 folder_insert_sorted(int index, int min_index, int max_index, FOLDER_S *folder,
1887 FLIST *flist, int (*compf)(FOLDER_S *, FOLDER_S *))
1889 int i;
1891 return(((i = (*compf)(folder_entry(index, flist), folder)) == 0)
1892 ? index
1893 : (i < 0)
1894 ? ((++index >= max_index)
1895 ? max_index
1896 : ((*compf)(folder_entry(index, flist), folder) > 0)
1897 ? index
1898 : folder_insert_sorted((index + max_index) / 2, index,
1899 max_index, folder, flist, compf))
1900 : ((index <= min_index)
1901 ? min_index
1902 : folder_insert_sorted((min_index + index) / 2, min_index, index,
1903 folder, flist, compf)));
1908 * folder_insert_index - Insert the given folder struct into the global list
1909 * at the given index.
1911 void
1912 folder_insert_index(FOLDER_S *folder, int index, FLIST *flist)
1914 register FOLDER_S **flp, **iflp;
1916 /* if index is beyond size, place at end of list */
1917 index = MIN(index, flist->used);
1919 /* grow array ? */
1920 if(flist->used + 1 > flist->allocated){
1921 flist->allocated += FCHUNK;
1922 fs_resize((void **)&(flist->folders),
1923 flist->allocated * sizeof(FOLDER_S *));
1926 /* shift array left */
1927 iflp = &((flist->folders)[index]);
1928 for(flp = &((flist->folders)[flist->used]);
1929 flp > iflp; flp--)
1930 flp[0] = flp[-1];
1932 flist->folders[index] = folder;
1933 flist->used += 1;
1937 void
1938 resort_folder_list(FLIST *flist)
1940 if(flist && folder_total(flist) > 1 && flist->folders)
1941 qsort(flist->folders, folder_total(flist), sizeof(flist->folders[0]),
1942 (ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_FIRST)
1943 ? compare_folders_dir_alpha_qsort
1944 : (ps_global->fld_sort_rule == FLD_SORT_ALPHA_DIR_LAST)
1945 ? compare_folders_alpha_dir_qsort
1946 : compare_folders_alpha_qsort);
1950 /*----------------------------------------------------------------------
1951 Removes a folder at the given index in the given context's
1952 list.
1954 Args: index -- Index in folder list of folder to be removed
1955 flist -- folder list
1956 ----*/
1957 void
1958 folder_delete(int index, FLIST *flist)
1960 register int i;
1961 FOLDER_S *f;
1963 if(flist->used
1964 && (index < 0 || index >= flist->used))
1965 return; /* bogus call! */
1967 if((f = folder_entry(index, flist))->nickname)
1968 fs_give((void **)&(f->nickname));
1970 fs_give((void **) &(flist->folders[index]));
1971 for(i = index; i < flist->used - 1; i++)
1972 flist->folders[i] = flist->folders[i+1];
1975 flist->used -= 1;
1979 /*----------------------------------------------------------------------
1980 compare two names for qsort, case independent
1982 Args: pointers to strings to compare
1984 Result: integer result of strcmp of the names. Uses simple
1985 efficiency hack to speed the string comparisons up a bit.
1987 ----------------------------------------------------------------------*/
1989 compare_names(const qsort_t *x, const qsort_t *y)
1991 char *a = *(char **)x, *b = *(char **)y;
1992 int r;
1993 #define CMPI(X,Y) ((X)[0] - (Y)[0])
1994 #define UCMPI(X,Y) ((isupper((unsigned char)((X)[0])) \
1995 ? (X)[0] - 'A' + 'a' : (X)[0]) \
1996 - (isupper((unsigned char)((Y)[0])) \
1997 ? (Y)[0] - 'A' + 'a' : (Y)[0]))
1999 /*---- Inbox always sorts to the top ----*/
2000 if(UCMPI(a, ps_global->inbox_name) == 0
2001 && strucmp(a, ps_global->inbox_name) == 0)
2002 return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : -1);
2003 else if((UCMPI(b, ps_global->inbox_name)) == 0
2004 && strucmp(b, ps_global->inbox_name) == 0)
2005 return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : 1);
2007 /*----- The sent-mail folder, is always next unless... ---*/
2008 else if(F_OFF(F_SORT_DEFAULT_FCC_ALPHA, ps_global)
2009 && CMPI(a, ps_global->VAR_DEFAULT_FCC) == 0
2010 && strcmp(a, ps_global->VAR_DEFAULT_FCC) == 0)
2011 return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : -1);
2012 else if(F_OFF(F_SORT_DEFAULT_FCC_ALPHA, ps_global)
2013 && CMPI(b, ps_global->VAR_DEFAULT_FCC) == 0
2014 && strcmp(b, ps_global->VAR_DEFAULT_FCC) == 0)
2015 return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : 1);
2017 /*----- The saved-messages folder, is always next unless... ---*/
2018 else if(F_OFF(F_SORT_DEFAULT_SAVE_ALPHA, ps_global)
2019 && CMPI(a, ps_global->VAR_DEFAULT_SAVE_FOLDER) == 0
2020 && strcmp(a, ps_global->VAR_DEFAULT_SAVE_FOLDER) == 0)
2021 return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : -1);
2022 else if(F_OFF(F_SORT_DEFAULT_SAVE_ALPHA, ps_global)
2023 && CMPI(b, ps_global->VAR_DEFAULT_SAVE_FOLDER) == 0
2024 && strcmp(b, ps_global->VAR_DEFAULT_SAVE_FOLDER) == 0)
2025 return((CMPI(a, b) == 0 && strcmp(a, b) == 0) ? 0 : 1);
2027 else
2028 return((r = CMPI(a, b)) ? r : strcmp(a, b));
2032 /*----------------------------------------------------------------------
2033 compare two folder structs for ordering alphabetically
2035 Args: pointers to folder structs to compare
2037 Result: integer result of dir-bit and strcmp of the folders.
2038 ----------------------------------------------------------------------*/
2040 compare_folders_alpha(FOLDER_S *f1, FOLDER_S *f2)
2042 int i;
2043 char *f1name = FLDR_NAME(f1),
2044 *f2name = FLDR_NAME(f2);
2046 return(((i = compare_names(&f1name, &f2name)) != 0)
2047 ? i : (f2->isdir - f1->isdir));
2052 compare_folders_alpha_qsort(const qsort_t *a1, const qsort_t *a2)
2054 FOLDER_S *f1 = *((FOLDER_S **) a1);
2055 FOLDER_S *f2 = *((FOLDER_S **) a2);
2057 return(compare_folders_alpha(f1, f2));
2061 /*----------------------------------------------------------------------
2062 compare two folder structs alphabetically with dirs first
2064 Args: pointers to folder structs to compare
2066 Result: integer result of dir-bit and strcmp of the folders.
2067 ----------------------------------------------------------------------*/
2069 compare_folders_dir_alpha(FOLDER_S *f1, FOLDER_S *f2)
2071 int i;
2073 if((i = (f2->isdir - f1->isdir)) == 0){
2074 char *f1name = FLDR_NAME(f1),
2075 *f2name = FLDR_NAME(f2);
2077 return(compare_names(&f1name, &f2name));
2080 return(i);
2085 compare_folders_dir_alpha_qsort(const qsort_t *a1, const qsort_t *a2)
2087 FOLDER_S *f1 = *((FOLDER_S **) a1);
2088 FOLDER_S *f2 = *((FOLDER_S **) a2);
2090 return(compare_folders_dir_alpha(f1, f2));
2094 /*----------------------------------------------------------------------
2095 compare two folder structs alphabetically with dirs last
2097 Args: pointers to folder structs to compare
2099 Result: integer result of dir-bit and strcmp of the folders.
2100 ----------------------------------------------------------------------*/
2102 compare_folders_alpha_dir(FOLDER_S *f1, FOLDER_S *f2)
2104 int i;
2106 if((i = (f1->isdir - f2->isdir)) == 0){
2107 char *f1name = FLDR_NAME(f1),
2108 *f2name = FLDR_NAME(f2);
2110 return(compare_names(&f1name, &f2name));
2113 return(i);
2118 compare_folders_alpha_dir_qsort(const qsort_t *a1, const qsort_t *a2)
2120 FOLDER_S *f1 = *((FOLDER_S **) a1);
2121 FOLDER_S *f2 = *((FOLDER_S **) a2);
2123 return(compare_folders_alpha_dir(f1, f2));
2128 * Find incoming folders and update the unseen counts
2129 * if necessary.
2131 void
2132 folder_unseen_count_updater(unsigned long flags)
2134 CONTEXT_S *ctxt;
2135 time_t oldest, started_checking;
2136 int ftotal, i, first = -1;
2137 FOLDER_S *f;
2140 * We would only do this if there is an incoming collection, the
2141 * user wants us to monitor, and we're in the folder screen.
2143 if(ps_global->in_folder_screen && F_ON(F_ENABLE_INCOMING_CHECKING, ps_global)
2144 && (ctxt=ps_global->context_list) && ctxt->use & CNTXT_INCMNG
2145 && (ftotal = folder_total(FOLDERS(ctxt)))){
2147 * Search through the last_unseen_update times to find
2148 * the one that was updated longest ago, and start with
2149 * that one. We don't want to delay long doing these
2150 * checks so may only check some of them each time we
2151 * get called. An update time equal to 1 means don't check
2152 * this folder at all.
2154 for(i = 0; i < ftotal; i++){
2155 f = folder_entry(i, FOLDERS(ctxt));
2156 if(f && LUU_YES(f->last_unseen_update)){
2157 first = i;
2158 oldest = f->last_unseen_update;
2159 break;
2164 * Now first is the first in the list that
2165 * should ever be checked. Next find the
2166 * one that should be checked next, the one
2167 * that was checked longest ago.
2169 if(first >= 0){
2170 for(i = 1; i < ftotal; i++){
2171 f = folder_entry(i, FOLDERS(ctxt));
2172 if(f && LUU_YES(f->last_unseen_update) && f->last_unseen_update < oldest){
2173 first = i;
2174 oldest = f->last_unseen_update;
2179 /* now first is the next one to be checked */
2181 started_checking = time(0);
2183 for(i = first; i < ftotal; i++){
2184 /* update the next one */
2185 f = folder_entry(i, FOLDERS(ctxt));
2186 if(f && LUU_YES(f->last_unseen_update)
2187 && (flags & UFU_FORCE
2188 /* or it's been long enough and we've not been in this function too long */
2189 || (((time(0) - f->last_unseen_update) >= ps_global->inc_check_interval)
2190 && ((time(0) - started_checking) < MIN(4,ps_global->inc_check_timeout)))))
2191 update_folder_unseen(f, ctxt, flags, NULL);
2194 for(i = 0; i < first; i++){
2195 f = folder_entry(i, FOLDERS(ctxt));
2196 if(f && LUU_YES(f->last_unseen_update)
2197 && (flags & UFU_FORCE
2198 || (((time(0) - f->last_unseen_update) >= ps_global->inc_check_interval)
2199 && ((time(0) - started_checking) < MIN(4,ps_global->inc_check_timeout)))))
2200 update_folder_unseen(f, ctxt, flags, NULL);
2207 * Update the count of unseen in the FOLDER_S struct
2208 * for this folder. This will update if the time
2209 * interval has passed or if the FORCE flag is set.
2211 void
2212 update_folder_unseen(FOLDER_S *f, CONTEXT_S *ctxt, unsigned long flags,
2213 MAILSTREAM *this_is_the_stream)
2215 time_t now;
2216 int orig_valid;
2217 int use_imap_interval = 0;
2218 int stream_is_open = 0;
2219 unsigned long orig_unseen, orig_new, orig_tot;
2220 char mailbox_name[MAILTMPLEN];
2221 char *target = NULL;
2222 DRIVER *d;
2224 if(!f || !LUU_YES(f->last_unseen_update))
2225 return;
2227 now = time(0);
2228 context_apply(mailbox_name, ctxt, f->name, MAILTMPLEN);
2230 if(!mailbox_name[0])
2231 return;
2233 if(check_for_move_mbox(mailbox_name, NULL, 0, &target)){
2234 MAILSTREAM *strm;
2237 * If this maildrop is the currently open stream use that.
2238 * I'm not altogether sure that this is a good way to
2239 * check this.
2241 if(target
2242 && ((strm=ps_global->mail_stream)
2243 && strm->snarf.name
2244 && (!strcmp(target,strm->mailbox)
2245 || !strcmp(target,strm->original_mailbox)))){
2246 stream_is_open++;
2249 else{
2250 MAILSTREAM *m = NULL;
2252 stream_is_open = (this_is_the_stream
2253 || (m=sp_stream_get(mailbox_name, SP_MATCH | SP_RO_OK))
2254 || ((m=ps_global->mail_stream) && !sp_dead_stream(m)
2255 && same_stream_and_mailbox(mailbox_name,m))
2256 || (!IS_REMOTE(mailbox_name)
2257 && (m=already_open_stream(mailbox_name, AOS_NONE)))) ? 1 : 0;
2259 if(stream_is_open){
2260 if(!this_is_the_stream)
2261 this_is_the_stream = m;
2263 else{
2265 * If it's IMAP or local we use a shorter interval.
2267 d = mail_valid(NIL, mailbox_name, (char *) NIL);
2268 if((d && !strcmp(d->name, "imap")) || !IS_REMOTE(mailbox_name))
2269 use_imap_interval++;
2274 * Update if forced, or if it's been a while, or if we have a
2275 * stream open to this mailbox already.
2277 if(flags & UFU_FORCE
2278 || stream_is_open
2279 || ((use_imap_interval
2280 && (now - f->last_unseen_update) >= ps_global->inc_check_interval)
2281 || ((now - f->last_unseen_update) >= ps_global->inc_second_check_interval))){
2282 unsigned long tot, uns, new;
2283 unsigned long *totp = NULL, *unsp = NULL, *newp = NULL;
2285 orig_valid = f->unseen_valid;
2286 orig_unseen = f->unseen;
2287 orig_new = f->new;
2288 orig_tot = f->total;
2290 if(F_ON(F_INCOMING_CHECKING_RECENT, ps_global))
2291 newp = &new;
2292 else
2293 unsp = &uns;
2295 if(F_ON(F_INCOMING_CHECKING_TOTAL, ps_global))
2296 totp = &tot;
2298 f->unseen_valid = 0;
2300 dprint((9, "update_folder_unseen(%s)", FLDR_NAME(f)));
2301 if(get_recent_in_folder(mailbox_name, newp, unsp, totp, this_is_the_stream)){
2302 f->last_unseen_update = time(0);
2303 f->unseen_valid = 1;
2304 if(unsp)
2305 f->unseen = uns;
2307 if(newp)
2308 f->new = new;
2310 if(totp)
2311 f->total = tot;
2313 if(!orig_valid){
2314 dprint((9, "update_folder_unseen(%s): original: %s%s%s%s",
2315 FLDR_NAME(f),
2316 F_ON(F_INCOMING_CHECKING_RECENT,ps_global) ? "new=" : "unseen=",
2317 F_ON(F_INCOMING_CHECKING_RECENT,ps_global) ? comatose(f->new) : comatose(f->unseen),
2318 F_ON(F_INCOMING_CHECKING_TOTAL,ps_global) ? " tot=" : "",
2319 F_ON(F_INCOMING_CHECKING_TOTAL,ps_global) ? comatose(f->total) : ""));
2322 if(orig_valid
2323 && ((F_ON(F_INCOMING_CHECKING_RECENT, ps_global)
2324 && orig_new != f->new)
2326 (F_OFF(F_INCOMING_CHECKING_RECENT, ps_global)
2327 && orig_unseen != f->unseen)
2329 (F_ON(F_INCOMING_CHECKING_TOTAL, ps_global)
2330 && orig_tot != f->total))){
2332 if(ps_global->in_folder_screen)
2333 ps_global->noticed_change_in_unseen = 1;
2335 dprint((9, "update_folder_unseen(%s): changed: %s%s%s%s",
2336 FLDR_NAME(f),
2337 F_ON(F_INCOMING_CHECKING_RECENT,ps_global) ? "new=" : "unseen=",
2338 F_ON(F_INCOMING_CHECKING_RECENT,ps_global) ? comatose(f->new) : comatose(f->unseen),
2339 F_ON(F_INCOMING_CHECKING_TOTAL,ps_global) ? " tot=" : "",
2340 F_ON(F_INCOMING_CHECKING_TOTAL,ps_global) ? comatose(f->total) : ""));
2342 if(flags & UFU_ANNOUNCE
2343 && ((F_ON(F_INCOMING_CHECKING_RECENT, ps_global)
2344 && orig_new < f->new)
2346 (F_OFF(F_INCOMING_CHECKING_RECENT, ps_global)
2347 && orig_unseen < f->unseen))){
2348 if(F_ON(F_INCOMING_CHECKING_RECENT, ps_global))
2349 q_status_message3(SM_ASYNC, 1, 3, "%s: %s %s",
2350 FLDR_NAME(f), comatose(f->new),
2351 _("new"));
2352 else
2353 q_status_message3(SM_ASYNC, 1, 3, "%s: %s %s",
2354 FLDR_NAME(f), comatose(f->unseen),
2355 _("unseen"));
2359 else
2360 f->last_unseen_update = LUU_NOMORECHK; /* no further checking */
2365 void
2366 update_folder_unseen_by_stream(MAILSTREAM *strm, unsigned long flags)
2368 CONTEXT_S *ctxt;
2369 int ftotal, i;
2370 char mailbox_name[MAILTMPLEN], *target;
2371 char *cn, tmp[MAILTMPLEN];
2372 FOLDER_S *f;
2375 * Attempt to figure out which incoming folder this stream
2376 * is open to, if any, so we can update the unseen counters.
2378 if(strm
2379 && F_ON(F_ENABLE_INCOMING_CHECKING, ps_global)
2380 && (ctxt=ps_global->context_list) && ctxt->use & CNTXT_INCMNG
2381 && (ftotal = folder_total(FOLDERS(ctxt)))){
2382 for(i = 0; i < ftotal; i++){
2383 f = folder_entry(i, FOLDERS(ctxt));
2384 context_apply(mailbox_name, ctxt, f->name, MAILTMPLEN);
2386 if((check_for_move_mbox(mailbox_name, NULL, 0, &target)
2387 && strm->snarf.name
2388 && (!strcmp(target,strm->mailbox)
2389 || !strcmp(target,strm->original_mailbox)))
2390 || same_stream_and_mailbox(mailbox_name, strm)
2391 || (!IS_REMOTE(mailbox_name) && (cn=mailboxfile(tmp,mailbox_name)) && (*cn) && (!strcmp(cn, strm->mailbox) || !strcmp(cn, strm->original_mailbox)))){
2392 /* if we failed earlier on this one, give it another go */
2393 if(f->last_unseen_update == LUU_NOMORECHK)
2394 init_incoming_unseen_data(ps_global, f);
2396 update_folder_unseen(f, ctxt, flags, strm);
2397 return;
2405 * Find the number of new, unseen, and the total number of
2406 * messages in mailbox_name.
2407 * If the corresponding arg is NULL it will skip the work
2408 * necessary for that flag.
2410 * Returns 1 if successful, 0 if not.
2413 get_recent_in_folder(char *mailbox_name, long unsigned int *new,
2414 long unsigned int *unseen, long unsigned int *total,
2415 MAILSTREAM *this_is_the_stream)
2417 MAILSTREAM *strm = NIL;
2418 unsigned long tot = 0L, nw = 0L, uns = 0L;
2419 int gotit = 0;
2420 int maildrop = 0;
2421 char *target = NULL;
2422 MSGNO_S *msgmap;
2423 long excluded, flags;
2424 extern MAILSTATUS mm_status_result;
2426 dprint((9, "get_recent_in_folder(%s)", mailbox_name ? mailbox_name : "?"));
2428 if(check_for_move_mbox(mailbox_name, NULL, 0, &target)){
2430 maildrop++;
2433 * If this maildrop is the currently open stream use that.
2435 if(target
2436 && ((strm=ps_global->mail_stream)
2437 && strm->snarf.name
2438 && (!strcmp(target,strm->mailbox)
2439 || !strcmp(target,strm->original_mailbox)))){
2440 gotit++;
2441 msgmap = sp_msgmap(strm);
2442 excluded = any_lflagged(msgmap, MN_EXLD);
2444 tot = strm->nmsgs - excluded;
2445 if(tot){
2446 if(new){
2447 if(sp_recent_since_visited(strm) == 0)
2448 nw = 0;
2449 else
2450 nw = count_flagged(strm, F_RECENT | F_UNSEEN | F_UNDEL);
2453 if(unseen)
2454 uns = count_flagged(strm, F_UNSEEN | F_UNDEL);
2456 else{
2457 nw = 0;
2458 uns = 0;
2461 /* else fall through to just open it case */
2464 /* do we already have it selected? */
2465 if(!gotit
2466 && ((strm = this_is_the_stream)
2467 || (strm = sp_stream_get(mailbox_name, SP_MATCH | SP_RO_OK))
2468 || (!IS_REMOTE(mailbox_name)
2469 && (strm = already_open_stream(mailbox_name, AOS_NONE))))){
2470 gotit++;
2473 * Unfortunately, we have to worry about excluded
2474 * messages. The user doesn't want to have
2475 * excluded messages count in the totals, especially
2476 * recent excluded messages.
2479 msgmap = sp_msgmap(strm);
2480 excluded = any_lflagged(msgmap, MN_EXLD);
2482 tot = strm->nmsgs - excluded;
2483 if(tot){
2484 if(new){
2485 if(sp_recent_since_visited(strm) == 0)
2486 nw = 0;
2487 else
2488 nw = count_flagged(strm, F_RECENT | F_UNSEEN | F_UNDEL);
2491 if(unseen)
2492 uns = count_flagged(strm, F_UNSEEN | F_UNDEL);
2494 else{
2495 nw = 0;
2496 uns = 0;
2500 * No, but how about another stream to same server which
2501 * could be used for a STATUS command?
2503 else if(!gotit && (strm = sp_stream_get(mailbox_name, SP_SAME))
2504 && modern_imap_stream(strm)){
2506 flags = 0L;
2507 if(total)
2508 flags |= SA_MESSAGES;
2510 if(new)
2511 flags |= SA_RECENT;
2513 if(unseen)
2514 flags |= SA_UNSEEN;
2516 mm_status_result.flags = 0L;
2518 pine_mail_status(strm, mailbox_name, flags);
2519 if(total){
2520 if(mm_status_result.flags & SA_MESSAGES){
2521 tot = mm_status_result.messages;
2522 gotit++;
2526 if(!(total && !gotit)){
2527 if(new){
2528 if(mm_status_result.flags & SA_RECENT){
2529 nw = mm_status_result.recent;
2530 gotit++;
2532 else
2533 gotit = 0;
2537 if(!((total || new) && !gotit)){
2538 if(unseen){
2539 if(mm_status_result.flags & SA_UNSEEN){
2540 uns = mm_status_result.unseen;
2541 gotit++;
2543 else
2544 gotit = 0;
2549 /* Let's just Select it. */
2550 if(!gotit){
2551 long saved_timeout;
2552 long openflags;
2555 * Traditional unix folders don't notice new mail if
2556 * they are opened readonly. So maildrops with unix folder
2557 * targets will snarf to the file but the stream that is
2558 * opened won't see the new mail. So make all maildrop
2559 * opens non-readonly here.
2561 openflags = SP_USEPOOL | SP_TEMPUSE | (maildrop ? 0 : OP_READONLY);
2563 saved_timeout = (long) mail_parameters(NULL, GET_OPENTIMEOUT, NULL);
2564 mail_parameters(NULL, SET_OPENTIMEOUT, (void *) (long) ps_global->inc_check_timeout);
2565 strm = pine_mail_open(NULL, mailbox_name, openflags, NULL);
2566 mail_parameters(NULL, SET_OPENTIMEOUT, (void *) saved_timeout);
2568 if(strm){
2569 gotit++;
2570 msgmap = sp_msgmap(strm);
2571 excluded = any_lflagged(msgmap, MN_EXLD);
2573 tot = strm->nmsgs - excluded;
2574 if(tot){
2575 if(new){
2576 if(sp_recent_since_visited(strm) == 0)
2577 nw = 0;
2578 else
2579 nw = count_flagged(strm, F_RECENT | F_UNSEEN | F_UNDEL);
2582 if(unseen)
2583 uns = count_flagged(strm, F_UNSEEN | F_UNDEL);
2585 else{
2586 nw = 0;
2587 uns = 0;
2590 pine_mail_close(strm);
2594 if(gotit){
2595 if(new)
2596 *new = nw;
2598 if(unseen)
2599 *unseen = uns;
2601 if(total)
2602 *total = tot;
2605 return(gotit);
2609 void
2610 clear_incoming_valid_bits(void)
2612 CONTEXT_S *ctxt;
2613 int ftotal, i;
2614 FOLDER_S *f;
2616 if(F_ON(F_ENABLE_INCOMING_CHECKING, ps_global)
2617 && (ctxt=ps_global->context_list) && ctxt->use & CNTXT_INCMNG
2618 && (ftotal = folder_total(FOLDERS(ctxt))))
2619 for(i = 0; i < ftotal; i++){
2620 f = folder_entry(i, FOLDERS(ctxt));
2621 init_incoming_unseen_data(ps_global, f);
2627 selected_folders(CONTEXT_S *context)
2629 int i, n, total;
2631 n = folder_total(FOLDERS(context));
2632 for(total = i = 0; i < n; i++)
2633 if(folder_entry(i, FOLDERS(context))->selected)
2634 total++;
2636 return(total);
2640 SELECTED_S *
2641 new_selected(void)
2643 SELECTED_S *selp;
2645 selp = (SELECTED_S *)fs_get(sizeof(SELECTED_S));
2646 selp->sub = NULL;
2647 selp->reference = NULL;
2648 selp->folders = NULL;
2649 selp->zoomed = 0;
2651 return selp;
2656 * Free the current selected struct and all of the
2657 * following structs in the list
2659 void
2660 free_selected(SELECTED_S **selp)
2662 if(!selp || !(*selp))
2663 return;
2664 if((*selp)->sub)
2665 free_selected(&((*selp)->sub));
2667 free_strlist(&(*selp)->folders);
2668 if((*selp)->reference)
2669 fs_give((void **) &(*selp)->reference);
2671 fs_give((void **) selp);
2675 void
2676 folder_select_preserve(CONTEXT_S *context)
2678 if(context
2679 && !(context->use & CNTXT_PARTFIND)){
2680 FOLDER_S *fp;
2681 STRLIST_S **slpp;
2682 SELECTED_S *selp = &context->selected;
2683 int i, folder_n;
2685 if(!context->dir->ref){
2686 if(!context->selected.folders)
2687 slpp = &context->selected.folders;
2688 else
2689 return;
2691 else{
2692 if(!selected_folders(context))
2693 return;
2694 else{
2695 while(selp->sub){
2696 selp = selp->sub;
2697 if(!strcmp(selp->reference, context->dir->ref))
2698 return;
2700 selp->sub = new_selected();
2701 selp = selp->sub;
2702 slpp = &(selp->folders);
2705 folder_n = folder_total(FOLDERS(context));
2707 for(i = 0; i < folder_n; i++)
2708 if((fp = folder_entry(i, FOLDERS(context)))->selected){
2709 *slpp = new_strlist(fp->name);
2710 slpp = &(*slpp)->next;
2713 /* Only remember "ref" if any folders were selected */
2714 if(selp->folders && context->dir->ref)
2715 selp->reference = cpystr(context->dir->ref);
2717 selp->zoomed = (context->use & CNTXT_ZOOM) != 0;
2723 folder_select_restore(CONTEXT_S *context)
2725 int rv = 0;
2727 if(context
2728 && !(context->use & CNTXT_PARTFIND)){
2729 STRLIST_S *slp;
2730 SELECTED_S *selp, *pselp = NULL;
2731 int i, found = 0;
2733 selp = &(context->selected);
2735 if(context->dir->ref){
2736 pselp = selp;
2737 selp = selp->sub;
2738 while(selp && strcmp(selp->reference, context->dir->ref)){
2739 pselp = selp;
2740 selp = selp->sub;
2742 if (selp)
2743 found = 1;
2745 else
2746 found = selp->folders != 0;
2747 if(found){
2748 for(slp = selp->folders; slp; slp = slp->next)
2749 if(slp->name
2750 && (i = folder_index(slp->name, context, FI_FOLDER)) >= 0){
2751 folder_entry(i, FOLDERS(context))->selected = 1;
2752 rv++;
2755 /* Used, always clean them up */
2756 free_strlist(&selp->folders);
2757 if(selp->reference)
2758 fs_give((void **) &selp->reference);
2760 if(selp->zoomed){
2761 context->use |= CNTXT_ZOOM;
2762 selp->zoomed = 0;
2764 if(!(selp == &context->selected)){
2765 if(pselp){
2766 pselp->sub = selp->sub;
2767 fs_give((void **) &selp);
2773 return(rv);