mutt stable branch with some hacks
1/*
2 * Copyright (C) 1996-2000,2007,2010,2013 Michael R. Elkins <me@mutt.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19#if HAVE_CONFIG_H
20# include "config.h"
21#endif
22
23#include "mutt.h"
24#include "mutt_curses.h"
25#include "mutt_menu.h"
26#include "attach.h"
27#include "buffy.h"
28#include "mapping.h"
29#include "sort.h"
30#include "mailbox.h"
31#include "browser.h"
32#ifdef USE_IMAP
33#include "imap.h"
34#endif
35
36#include <stdlib.h>
37#include <dirent.h>
38#include <string.h>
39#include <ctype.h>
40#include <unistd.h>
41#include <sys/stat.h>
42#include <errno.h>
43#include <locale.h>
44
45static const struct mapping_t FolderHelp[] = {
46 { N_("Exit"), OP_EXIT },
47 { N_("Chdir"), OP_CHANGE_DIRECTORY },
48 { N_("Mask"), OP_ENTER_MASK },
49 { N_("Help"), OP_HELP },
50 { NULL, 0 }
51};
52
53typedef struct folder_t
54{
55 struct folder_file *ff;
56 int num;
57} FOLDER;
58
59static BUFFER *LastDir = NULL;
60static BUFFER *LastDirBackup = NULL;
61
62void mutt_browser_cleanup (void)
63{
64 mutt_buffer_free (&LastDir);
65 mutt_buffer_free (&LastDirBackup);
66}
67
68/* Frees up the memory allocated for the local-global variables. */
69static void destroy_state (struct browser_state *state)
70{
71 int c;
72
73 for (c = 0; c < state->entrylen; c++)
74 {
75 FREE (&((state->entry)[c].display_name));
76 FREE (&((state->entry)[c].full_path));
77 }
78#ifdef USE_IMAP
79 FREE (&state->folder);
80#endif
81 FREE (&state->entry);
82}
83
84static int browser_compare_subject (const void *a, const void *b)
85{
86 struct folder_file *pa = (struct folder_file *) a;
87 struct folder_file *pb = (struct folder_file *) b;
88
89 int r = mutt_strcoll (pa->display_name, pb->display_name);
90
91 return ((BrowserSort & SORT_REVERSE) ? -r : r);
92}
93
94static int browser_compare_date (const void *a, const void *b)
95{
96 struct folder_file *pa = (struct folder_file *) a;
97 struct folder_file *pb = (struct folder_file *) b;
98
99 int r = pa->mtime - pb->mtime;
100
101 return ((BrowserSort & SORT_REVERSE) ? -r : r);
102}
103
104static int browser_compare_size (const void *a, const void *b)
105{
106 struct folder_file *pa = (struct folder_file *) a;
107 struct folder_file *pb = (struct folder_file *) b;
108
109 int r = pa->size - pb->size;
110
111 return ((BrowserSort & SORT_REVERSE) ? -r : r);
112}
113
114static int browser_compare_count (const void *a, const void *b)
115{
116 struct folder_file *pa = (struct folder_file *) a;
117 struct folder_file *pb = (struct folder_file *) b;
118
119 int r = pa->msg_count - pb->msg_count;
120
121 return ((BrowserSort & SORT_REVERSE) ? -r : r);
122}
123
124static int browser_compare_unread (const void *a, const void *b)
125{
126 struct folder_file *pa = (struct folder_file *) a;
127 struct folder_file *pb = (struct folder_file *) b;
128
129 int r = pa->msg_unread - pb->msg_unread;
130
131 return ((BrowserSort & SORT_REVERSE) ? -r : r);
132}
133
134static void browser_sort (struct browser_state *state)
135{
136 int (*f) (const void *, const void *);
137
138 switch (BrowserSort & SORT_MASK)
139 {
140 case SORT_ORDER:
141 return;
142 case SORT_DATE:
143 f = browser_compare_date;
144 break;
145 case SORT_SIZE:
146 f = browser_compare_size;
147 break;
148 case SORT_COUNT:
149 f = browser_compare_count;
150 break;
151 case SORT_UNREAD:
152 f = browser_compare_unread;
153 break;
154 case SORT_SUBJECT:
155 default:
156 f = browser_compare_subject;
157 break;
158 }
159 qsort (state->entry, state->entrylen, sizeof (struct folder_file), f);
160}
161
162static int link_is_dir (const char *full_path)
163{
164 struct stat st;
165 int retval = 0;
166
167 if (stat (full_path, &st) == 0)
168 retval = S_ISDIR (st.st_mode);
169
170 return retval;
171}
172
173static const char *
174folder_format_str (char *dest, size_t destlen, size_t col, int cols, char op, const char *src,
175 const char *fmt, const char *ifstring, const char *elsestring,
176 unsigned long data, format_flag flags)
177{
178 char fn[SHORT_STRING], tmp[SHORT_STRING], permission[11];
179 char date[SHORT_STRING], *t_fmt;
180 time_t tnow;
181 FOLDER *folder = (FOLDER *) data;
182 struct passwd *pw;
183 struct group *gr;
184 int optional = (flags & MUTT_FORMAT_OPTIONAL);
185
186 switch (op)
187 {
188 case 'C':
189 snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
190 snprintf (dest, destlen, tmp, folder->num + 1);
191 break;
192
193 case 'd':
194 case 'D':
195 if (folder->ff->local)
196 {
197 int do_locales = TRUE;
198
199 if (op == 'D')
200 {
201 t_fmt = NONULL(DateFmt);
202 if (*t_fmt == '!')
203 {
204 ++t_fmt;
205 do_locales = FALSE;
206 }
207 }
208 else
209 {
210 tnow = time (NULL);
211 t_fmt = tnow - folder->ff->mtime < 31536000 ? "%b %d %H:%M" : "%b %d %Y";
212 }
213
214 if (!do_locales)
215 setlocale (LC_TIME, "C");
216 strftime (date, sizeof (date), t_fmt, localtime (&folder->ff->mtime));
217 if (!do_locales)
218 setlocale (LC_TIME, "");
219
220 mutt_format_s (dest, destlen, fmt, date);
221 }
222 else
223 mutt_format_s (dest, destlen, fmt, "");
224 break;
225
226 case 'f':
227 {
228 char *s = NONULL (folder->ff->display_name);
229
230 snprintf (fn, sizeof (fn), "%s%s", s,
231 folder->ff->local ?
232 (S_ISLNK (folder->ff->mode) ?
233 "@" :
234 (S_ISDIR (folder->ff->mode) ?
235 "/" :
236 ((folder->ff->mode & S_IXUSR) != 0 ?
237 "*" :
238 ""))) :
239 "");
240
241 mutt_format_s (dest, destlen, fmt, fn);
242 break;
243 }
244 case 'F':
245 if (folder->ff->local)
246 {
247 snprintf (permission, sizeof (permission), "%c%c%c%c%c%c%c%c%c%c",
248 S_ISDIR(folder->ff->mode) ? 'd' : (S_ISLNK(folder->ff->mode) ? 'l' : '-'),
249 (folder->ff->mode & S_IRUSR) != 0 ? 'r': '-',
250 (folder->ff->mode & S_IWUSR) != 0 ? 'w' : '-',
251 (folder->ff->mode & S_ISUID) != 0 ? 's' : (folder->ff->mode & S_IXUSR) != 0 ? 'x': '-',
252 (folder->ff->mode & S_IRGRP) != 0 ? 'r' : '-',
253 (folder->ff->mode & S_IWGRP) != 0 ? 'w' : '-',
254 (folder->ff->mode & S_ISGID) != 0 ? 's' : (folder->ff->mode & S_IXGRP) != 0 ? 'x': '-',
255 (folder->ff->mode & S_IROTH) != 0 ? 'r' : '-',
256 (folder->ff->mode & S_IWOTH) != 0 ? 'w' : '-',
257 (folder->ff->mode & S_ISVTX) != 0 ? 't' : (folder->ff->mode & S_IXOTH) != 0 ? 'x': '-');
258 mutt_format_s (dest, destlen, fmt, permission);
259 }
260#ifdef USE_IMAP
261 else if (folder->ff->imap)
262 {
263 /* mark folders with subfolders AND mail */
264 snprintf (permission, sizeof (permission), "IMAP %c",
265 (folder->ff->inferiors && folder->ff->selectable) ? '+' : ' ');
266 mutt_format_s (dest, destlen, fmt, permission);
267 }
268#endif
269 else
270 mutt_format_s (dest, destlen, fmt, "");
271 break;
272
273 case 'g':
274 if (folder->ff->local)
275 {
276 if ((gr = getgrgid (folder->ff->gid)))
277 mutt_format_s (dest, destlen, fmt, gr->gr_name);
278 else
279 {
280 snprintf (tmp, sizeof (tmp), "%%%sld", fmt);
281 snprintf (dest, destlen, tmp, folder->ff->gid);
282 }
283 }
284 else
285 mutt_format_s (dest, destlen, fmt, "");
286 break;
287
288 case 'l':
289 if (folder->ff->local)
290 {
291 snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
292 snprintf (dest, destlen, tmp, folder->ff->nlink);
293 }
294 else
295 mutt_format_s (dest, destlen, fmt, "");
296 break;
297
298 case 'm':
299 if (!optional)
300 {
301 if (folder->ff->has_buffy)
302 {
303 snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
304 snprintf (dest, destlen, tmp, folder->ff->msg_count);
305 }
306 else
307 mutt_format_s (dest, destlen, fmt, "");
308 }
309 else if (!folder->ff->msg_count)
310 optional = 0;
311 break;
312
313 case 'N':
314 snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
315 snprintf (dest, destlen, tmp, folder->ff->new ? 'N' : ' ');
316 break;
317
318 case 'n':
319 if (!optional)
320 {
321 if (folder->ff->has_buffy)
322 {
323 snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
324 snprintf (dest, destlen, tmp, folder->ff->msg_unread);
325 }
326 else
327 mutt_format_s (dest, destlen, fmt, "");
328 }
329 else if (!folder->ff->msg_unread)
330 optional = 0;
331 break;
332
333 case 's':
334 if (folder->ff->local)
335 {
336 mutt_pretty_size(fn, sizeof(fn), folder->ff->size);
337 snprintf (tmp, sizeof (tmp), "%%%ss", fmt);
338 snprintf (dest, destlen, tmp, fn);
339 }
340 else
341 mutt_format_s (dest, destlen, fmt, "");
342 break;
343
344 case 't':
345 snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
346 snprintf (dest, destlen, tmp, folder->ff->tagged ? '*' : ' ');
347 break;
348
349 case 'u':
350 if (folder->ff->local)
351 {
352 if ((pw = getpwuid (folder->ff->uid)))
353 mutt_format_s (dest, destlen, fmt, pw->pw_name);
354 else
355 {
356 snprintf (tmp, sizeof (tmp), "%%%sld", fmt);
357 snprintf (dest, destlen, tmp, folder->ff->uid);
358 }
359 }
360 else
361 mutt_format_s (dest, destlen, fmt, "");
362 break;
363
364 default:
365 snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
366 snprintf (dest, destlen, tmp, op);
367 break;
368 }
369
370 if (optional)
371 mutt_FormatString (dest, destlen, col, cols, ifstring, folder_format_str, data, 0);
372 else if (flags & MUTT_FORMAT_OPTIONAL)
373 mutt_FormatString (dest, destlen, col, cols, elsestring, folder_format_str, data, 0);
374
375 return (src);
376}
377
378static void add_folder (MUTTMENU *m, struct browser_state *state,
379 const char *display_name, const char *full_path,
380 const struct stat *s, BUFFY *b)
381{
382 if (state->entrylen == state->entrymax)
383 {
384 /* need to allocate more space */
385 safe_realloc (&state->entry,
386 sizeof (struct folder_file) * (state->entrymax += 256));
387 memset (&state->entry[state->entrylen], 0,
388 sizeof (struct folder_file) * 256);
389 if (m)
390 m->data = state->entry;
391 }
392
393 if (s != NULL)
394 {
395 (state->entry)[state->entrylen].mode = s->st_mode;
396 (state->entry)[state->entrylen].mtime = s->st_mtime;
397 (state->entry)[state->entrylen].size = s->st_size;
398 (state->entry)[state->entrylen].gid = s->st_gid;
399 (state->entry)[state->entrylen].uid = s->st_uid;
400 (state->entry)[state->entrylen].nlink = s->st_nlink;
401
402 (state->entry)[state->entrylen].local = 1;
403 }
404
405 if (b)
406 {
407 (state->entry)[state->entrylen].has_buffy = 1;
408 (state->entry)[state->entrylen].new = b->new;
409 (state->entry)[state->entrylen].msg_count = b->msg_count;
410 (state->entry)[state->entrylen].msg_unread = b->msg_unread;
411 }
412
413 (state->entry)[state->entrylen].display_name = safe_strdup (display_name);
414 (state->entry)[state->entrylen].full_path = safe_strdup (full_path);
415#ifdef USE_IMAP
416 (state->entry)[state->entrylen].imap = 0;
417#endif
418 (state->entrylen)++;
419}
420
421static void init_state (struct browser_state *state, MUTTMENU *menu)
422{
423 state->entrylen = 0;
424 state->entrymax = 256;
425 state->entry = (struct folder_file *) safe_calloc (state->entrymax, sizeof (struct folder_file));
426#ifdef USE_IMAP
427 state->imap_browse = 0;
428#endif
429 if (menu)
430 menu->data = state->entry;
431}
432
433static int examine_directory (MUTTMENU *menu, struct browser_state *state,
434 const char *d, const char *prefix)
435{
436 struct stat s;
437 DIR *dp;
438 struct dirent *de;
439 BUFFER *full_path = NULL;
440 BUFFY *tmp;
441
442 while (stat (d, &s) == -1)
443 {
444 if (errno == ENOENT)
445 {
446 /* The last used directory is deleted, try to use the parent dir. */
447 char *c = strrchr (d, '/');
448
449 if (c && (c > d))
450 {
451 *c = 0;
452 continue;
453 }
454 }
455 mutt_perror (d);
456 return (-1);
457 }
458
459 if (!S_ISDIR (s.st_mode))
460 {
461 mutt_error (_("%s is not a directory."), d);
462 return (-1);
463 }
464
465 mutt_buffy_check (0);
466
467 if ((dp = opendir (d)) == NULL)
468 {
469 mutt_perror (d);
470 return (-1);
471 }
472
473 full_path = mutt_buffer_pool_get ();
474 init_state (state, menu);
475
476 while ((de = readdir (dp)) != NULL)
477 {
478 if (mutt_strcmp (de->d_name, ".") == 0)
479 continue; /* we don't need . */
480
481 if (prefix && *prefix && mutt_strncmp (prefix, de->d_name, mutt_strlen (prefix)) != 0)
482 continue;
483 if (!((regexec (Mask.rx, de->d_name, 0, NULL, 0) == 0) ^ Mask.not))
484 continue;
485
486 mutt_buffer_concat_path (full_path, d, de->d_name);
487 if (lstat (mutt_b2s (full_path), &s) == -1)
488 continue;
489
490 /* No size for directories or symlinks */
491 if (S_ISDIR (s.st_mode) || S_ISLNK (s.st_mode))
492 s.st_size = 0;
493 else if (! S_ISREG (s.st_mode))
494 continue;
495
496 tmp = Incoming;
497 while (tmp && mutt_strcmp (mutt_b2s (full_path), mutt_b2s (tmp->pathbuf)))
498 tmp = tmp->next;
499 if (tmp && Context &&
500 !mutt_strcmp (tmp->realpath, Context->realpath))
501 {
502 tmp->msg_count = Context->msgcount;
503 tmp->msg_unread = Context->unread;
504 }
505 add_folder (menu, state, de->d_name, mutt_b2s (full_path), &s, tmp);
506 }
507 closedir (dp);
508 browser_sort (state);
509
510 mutt_buffer_pool_release (&full_path);
511 return 0;
512}
513
514static int examine_mailboxes (MUTTMENU *menu, struct browser_state *state)
515{
516 struct stat s;
517 BUFFY *tmp = Incoming;
518 BUFFER *mailbox = NULL;
519 BUFFER *md = NULL;
520
521 if (!Incoming)
522 return (-1);
523 mutt_buffy_check (0);
524
525 mailbox = mutt_buffer_pool_get ();
526 md = mutt_buffer_pool_get ();
527 init_state (state, menu);
528
529 do
530 {
531 if (Context &&
532 !mutt_strcmp (tmp->realpath, Context->realpath))
533 {
534 tmp->msg_count = Context->msgcount;
535 tmp->msg_unread = Context->unread;
536 }
537
538 mutt_buffer_strcpy (mailbox, mutt_b2s (tmp->pathbuf));
539 if (option (OPTBROWSERABBRMAILBOXES))
540 mutt_buffer_pretty_mailbox (mailbox);
541
542#ifdef USE_IMAP
543 if (mx_is_imap (mutt_b2s (tmp->pathbuf)))
544 {
545 add_folder (menu, state, mutt_b2s (mailbox), mutt_b2s (tmp->pathbuf), NULL, tmp);
546 continue;
547 }
548#endif
549#ifdef USE_POP
550 if (mx_is_pop (mutt_b2s (tmp->pathbuf)))
551 {
552 add_folder (menu, state, mutt_b2s (mailbox), mutt_b2s (tmp->pathbuf), NULL, tmp);
553 continue;
554 }
555#endif
556 if (lstat (mutt_b2s (tmp->pathbuf), &s) == -1)
557 continue;
558
559 if ((! S_ISREG (s.st_mode)) && (! S_ISDIR (s.st_mode)) &&
560 (! S_ISLNK (s.st_mode)))
561 continue;
562
563 if (mx_is_maildir (mutt_b2s (tmp->pathbuf)))
564 {
565 struct stat st2;
566
567 mutt_buffer_printf (md, "%s/new", mutt_b2s (tmp->pathbuf));
568 if (stat (mutt_b2s (md), &s) < 0)
569 s.st_mtime = 0;
570 mutt_buffer_printf (md, "%s/cur", mutt_b2s (tmp->pathbuf));
571 if (stat (mutt_b2s (md), &st2) < 0)
572 st2.st_mtime = 0;
573 if (st2.st_mtime > s.st_mtime)
574 s.st_mtime = st2.st_mtime;
575 }
576
577 add_folder (menu, state, mutt_b2s (mailbox), mutt_b2s (tmp->pathbuf), &s, tmp);
578 }
579 while ((tmp = tmp->next));
580 browser_sort (state);
581
582 mutt_buffer_pool_release (&mailbox);
583 mutt_buffer_pool_release (&md);
584 return 0;
585}
586
587static int select_file_search (MUTTMENU *menu, regex_t *re, int n)
588{
589 return (regexec (re, ((struct folder_file *) menu->data)[n].display_name, 0, NULL, 0));
590}
591
592static void folder_entry (char *s, size_t slen, MUTTMENU *menu, int num)
593{
594 FOLDER folder;
595
596 folder.ff = &((struct folder_file *) menu->data)[num];
597 folder.num = num;
598
599 mutt_FormatString (s, slen, 0, MuttIndexWindow->cols, NONULL(FolderFormat), folder_format_str,
600 (unsigned long) &folder, MUTT_FORMAT_ARROWCURSOR);
601}
602
603static void set_sticky_cursor (struct browser_state *state, MUTTMENU *menu, const char *defaultsel)
604{
605 int i;
606
607 if (option (OPTBROWSERSTICKYCURSOR) && defaultsel && *defaultsel)
608 {
609 for (i = 0; i < menu->max; i++)
610 {
611 if (!mutt_strcmp (defaultsel, state->entry[i].full_path))
612 {
613 menu->current = i;
614 break;
615 }
616 }
617 }
618}
619
620static void init_menu (struct browser_state *state, MUTTMENU *menu, char *title,
621 size_t titlelen, int buffy, const char *defaultsel)
622{
623 BUFFER *path = NULL;
624
625 path = mutt_buffer_pool_get ();
626
627 menu->max = state->entrylen;
628
629 if (menu->current >= menu->max)
630 menu->current = menu->max - 1;
631 if (menu->current < 0)
632 menu->current = 0;
633 if (menu->top > menu->current)
634 menu->top = 0;
635
636 menu->tagged = 0;
637
638 if (buffy)
639 snprintf (title, titlelen, _("Mailboxes [%d]"), mutt_buffy_check (0));
640 else
641 {
642 mutt_buffer_strcpy (path, mutt_b2s (LastDir));
643 mutt_buffer_pretty_mailbox (path);
644#ifdef USE_IMAP
645 if (state->imap_browse && option (OPTIMAPLSUB))
646 snprintf (title, titlelen, _("Subscribed [%s], File mask: %s"),
647 mutt_b2s (path), NONULL (Mask.pattern));
648 else
649#endif
650 snprintf (title, titlelen, _("Directory [%s], File mask: %s"),
651 mutt_b2s (path), NONULL(Mask.pattern));
652 }
653 menu->redraw = REDRAW_FULL;
654
655 set_sticky_cursor (state, menu, defaultsel);
656
657 mutt_buffer_pool_release (&path);
658}
659
660static int file_tag (MUTTMENU *menu, int n, int m)
661{
662 struct folder_file *ff = &(((struct folder_file *)menu->data)[n]);
663 int ot;
664 if (S_ISDIR (ff->mode) ||
665 (S_ISLNK (ff->mode) && link_is_dir (ff->full_path)))
666 {
667 mutt_error _("Can't attach a directory!");
668 return 0;
669 }
670
671 ot = ff->tagged;
672 ff->tagged = (m >= 0 ? m : !ff->tagged);
673
674 return ff->tagged - ot;
675}
676
677void _mutt_select_file (char *f, size_t flen, int flags, char ***files, int *numfiles)
678{
679 BUFFER *f_buf = NULL;
680
681 f_buf = mutt_buffer_pool_get ();
682
683 mutt_buffer_strcpy (f_buf, NONULL (f));
684 _mutt_buffer_select_file (f_buf, flags, files, numfiles);
685 strfcpy (f, mutt_b2s (f_buf), flen);
686
687 mutt_buffer_pool_release (&f_buf);
688}
689
690void _mutt_buffer_select_file (BUFFER *f, int flags, char ***files, int *numfiles)
691{
692 BUFFER *buf = NULL;
693 BUFFER *prefix = NULL;
694 BUFFER *tmp = NULL;
695 BUFFER *OldLastDir = NULL;
696 BUFFER *defaultsel = NULL;
697 char helpstr[LONG_STRING];
698 char title[STRING];
699 struct browser_state state;
700 MUTTMENU *menu = NULL;
701 struct stat st;
702 int op, killPrefix = 0;
703 int i, j;
704 int multiple = (flags & MUTT_SEL_MULTI) ? 1 : 0;
705 int folder = (flags & MUTT_SEL_FOLDER) ? 1 : 0;
706 int buffy = (flags & MUTT_SEL_BUFFY) ? 1 : 0;
707
708 buffy = buffy && folder;
709
710 buf = mutt_buffer_pool_get ();
711 prefix = mutt_buffer_pool_get ();
712 tmp = mutt_buffer_pool_get ();
713 OldLastDir = mutt_buffer_pool_get ();
714 defaultsel = mutt_buffer_pool_get ();
715
716 memset (&state, 0, sizeof (struct browser_state));
717
718 if (!LastDir)
719 {
720 LastDir = mutt_buffer_new ();
721 mutt_buffer_increase_size (LastDir, _POSIX_PATH_MAX);
722 LastDirBackup = mutt_buffer_new ();
723 mutt_buffer_increase_size (LastDirBackup, _POSIX_PATH_MAX);
724 }
725
726 if (!folder)
727 mutt_buffer_strcpy (LastDirBackup, mutt_b2s (LastDir));
728
729 if (*(mutt_b2s (f)))
730 {
731 mutt_buffer_expand_path (f);
732#ifdef USE_IMAP
733 if (mx_is_imap (mutt_b2s (f)))
734 {
735 init_state (&state, NULL);
736 state.imap_browse = 1;
737 if (!imap_browse (mutt_b2s (f), &state))
738 mutt_buffer_strcpy (LastDir, state.folder);
739 }
740 else
741 {
742#endif
743 for (i = mutt_buffer_len (f) - 1;
744 i > 0 && (mutt_b2s (f))[i] != '/' ;
745 i--);
746 if (i > 0)
747 {
748 if ((mutt_b2s (f))[0] == '/')
749 mutt_buffer_strcpy_n (LastDir, mutt_b2s (f), i);
750 else
751 {
752 mutt_getcwd (LastDir);
753 mutt_buffer_addch (LastDir, '/');
754 mutt_buffer_addstr_n (LastDir, mutt_b2s (f), i);
755 }
756 }
757 else
758 {
759 if ((mutt_b2s (f))[0] == '/')
760 mutt_buffer_strcpy (LastDir, "/");
761 else
762 mutt_getcwd (LastDir);
763 }
764
765 if (i <= 0 && (mutt_b2s (f))[0] != '/')
766 mutt_buffer_strcpy (prefix, mutt_b2s (f));
767 else
768 mutt_buffer_strcpy (prefix, mutt_b2s (f) + i + 1);
769 killPrefix = 1;
770#ifdef USE_IMAP
771 }
772#endif
773 }
774 else
775 {
776 if (!folder)
777 mutt_getcwd (LastDir);
778 else if (!*(mutt_b2s (LastDir)))
779 mutt_buffer_strcpy (LastDir, NONULL(Maildir));
780
781 if (Context)
782 mutt_buffer_strcpy (defaultsel, NONULL (Context->path));
783
784#ifdef USE_IMAP
785 if (!buffy && mx_is_imap (mutt_b2s (LastDir)))
786 {
787 init_state (&state, NULL);
788 state.imap_browse = 1;
789 imap_browse (mutt_b2s (LastDir), &state);
790 browser_sort (&state);
791 }
792 else
793#endif
794 {
795 i = mutt_buffer_len (LastDir);
796 while (i && mutt_b2s (LastDir)[--i] == '/')
797 LastDir->data[i] = '\0';
798 mutt_buffer_fix_dptr (LastDir);
799 if (!*(mutt_b2s (LastDir)))
800 mutt_getcwd (LastDir);
801 }
802 }
803
804 mutt_buffer_clear (f);
805
806 if (buffy)
807 {
808 if (examine_mailboxes (NULL, &state) == -1)
809 goto bail;
810 }
811 else
812#ifdef USE_IMAP
813 if (!state.imap_browse)
814#endif
815 if (examine_directory (NULL, &state, mutt_b2s (LastDir), mutt_b2s (prefix)) == -1)
816 goto bail;
817
818 menu = mutt_new_menu (MENU_FOLDER);
819 menu->make_entry = folder_entry;
820 menu->search = select_file_search;
821 menu->title = title;
822 menu->data = state.entry;
823 if (multiple)
824 menu->tag = file_tag;
825
826 menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_FOLDER,
827 FolderHelp);
828 mutt_push_current_menu (menu);
829
830 init_menu (&state, menu, title, sizeof (title), buffy, mutt_b2s (defaultsel));
831
832 FOREVER
833 {
834 op = mutt_menuLoop (menu);
835
836 if (state.entrylen)
837 mutt_buffer_strcpy (defaultsel, state.entry[menu->current].full_path);
838
839 switch (op)
840 {
841 case OP_DESCEND_DIRECTORY:
842 case OP_GENERIC_SELECT_ENTRY:
843
844 if (!state.entrylen)
845 {
846 mutt_error _("No files match the file mask");
847 break;
848 }
849
850 if (S_ISDIR (state.entry[menu->current].mode) ||
851 (S_ISLNK (state.entry[menu->current].mode) &&
852 link_is_dir (state.entry[menu->current].full_path))
853#ifdef USE_IMAP
854 || state.entry[menu->current].inferiors
855#endif
856 )
857 {
858 if (op == OP_DESCEND_DIRECTORY
859 || (mx_get_magic (state.entry[menu->current].full_path) <= 0)
860#ifdef USE_IMAP
861 || state.entry[menu->current].inferiors
862#endif
863 )
864 {
865 /* save the old directory */
866 mutt_buffer_strcpy (OldLastDir, mutt_b2s (LastDir));
867
868 mutt_buffer_strcpy (defaultsel, mutt_b2s (OldLastDir));
869 if (mutt_buffer_len (defaultsel) && (*(defaultsel->dptr - 1) == '/'))
870 {
871 defaultsel->dptr--;
872 *(defaultsel->dptr) = '\0';
873 }
874
875 if (mutt_strcmp (state.entry[menu->current].display_name, "..") == 0)
876 {
877 size_t lastdirlen = mutt_buffer_len (LastDir);
878
879 if ((lastdirlen > 1) &&
880 mutt_strcmp ("..", mutt_b2s (LastDir) + lastdirlen - 2) == 0)
881 {
882 mutt_buffer_addstr (LastDir, "/..");
883 }
884 else
885 {
886 char *p = NULL;
887 if (lastdirlen > 1)
888 p = strrchr (mutt_b2s (LastDir) + 1, '/');
889
890 if (p)
891 {
892 *p = 0;
893 mutt_buffer_fix_dptr (LastDir);
894 }
895 else
896 {
897 if (mutt_b2s (LastDir)[0] == '/')
898 mutt_buffer_strcpy (LastDir, "/");
899 else
900 mutt_buffer_addstr (LastDir, "/..");
901 }
902 }
903 }
904 else if (buffy)
905 {
906 mutt_buffer_strcpy (LastDir, state.entry[menu->current].full_path);
907 }
908#ifdef USE_IMAP
909 else if (state.imap_browse)
910 {
911 ciss_url_t url;
912
913 mutt_buffer_strcpy (LastDir, state.entry[menu->current].full_path);
914 /* tack on delimiter here */
915
916 /* special case "" needs no delimiter */
917 url_parse_ciss (&url, state.entry[menu->current].full_path);
918 if (url.path &&
919 (state.entry[menu->current].delim != '\0'))
920 {
921 mutt_buffer_addch (LastDir, state.entry[menu->current].delim);
922 }
923 }
924#endif
925 else
926 {
927 mutt_buffer_strcpy (LastDir, state.entry[menu->current].full_path);
928 }
929
930 destroy_state (&state);
931 if (killPrefix)
932 {
933 mutt_buffer_clear (prefix);
934 killPrefix = 0;
935 }
936 buffy = 0;
937#ifdef USE_IMAP
938 if (state.imap_browse)
939 {
940 init_state (&state, NULL);
941 state.imap_browse = 1;
942 imap_browse (mutt_b2s (LastDir), &state);
943 browser_sort (&state);
944 menu->data = state.entry;
945 }
946 else
947#endif
948 if (examine_directory (menu, &state, mutt_b2s (LastDir), mutt_b2s (prefix)) == -1)
949 {
950 /* try to restore the old values */
951 mutt_buffer_strcpy (LastDir, mutt_b2s (OldLastDir));
952 if (examine_directory (menu, &state, mutt_b2s (LastDir), mutt_b2s (prefix)) == -1)
953 {
954 mutt_buffer_strcpy (LastDir, NONULL(Homedir));
955 goto bail;
956 }
957 }
958 menu->current = 0;
959 menu->top = 0;
960 init_menu (&state, menu, title, sizeof (title), buffy, mutt_b2s (defaultsel));
961 break;
962 }
963 }
964 else if (op == OP_DESCEND_DIRECTORY)
965 {
966 mutt_error (_("%s is not a directory."), state.entry[menu->current].display_name);
967 break;
968 }
969
970 mutt_buffer_strcpy (f, state.entry[menu->current].full_path);
971
972 /* fall through */
973
974 case OP_EXIT:
975
976 if (multiple)
977 {
978 char **tfiles;
979
980 if (menu->tagged)
981 {
982 *numfiles = menu->tagged;
983 tfiles = safe_calloc (*numfiles, sizeof (char *));
984 for (i = 0, j = 0; i < state.entrylen; i++)
985 if (state.entry[i].tagged)
986 tfiles[j++] = safe_strdup (state.entry[i].full_path);
987 *files = tfiles;
988 }
989 else if ((mutt_b2s (f))[0]) /* no tagged entries. return selected entry */
990 {
991 *numfiles = 1;
992 tfiles = safe_calloc (*numfiles, sizeof (char *));
993 tfiles[0] = safe_strdup (mutt_b2s (f));
994 *files = tfiles;
995 }
996 }
997
998 destroy_state (&state);
999 goto bail;
1000
1001 case OP_BROWSER_TELL:
1002 if (state.entrylen)
1003 mutt_message("%s", state.entry[menu->current].full_path);
1004 break;
1005
1006#ifdef USE_IMAP
1007 case OP_BROWSER_SUBSCRIBE:
1008 imap_subscribe (state.entry[menu->current].full_path, 1);
1009 break;
1010
1011 case OP_BROWSER_UNSUBSCRIBE:
1012 imap_subscribe (state.entry[menu->current].full_path, 0);
1013 break;
1014
1015 case OP_BROWSER_TOGGLE_LSUB:
1016 if (option (OPTIMAPLSUB))
1017 unset_option (OPTIMAPLSUB);
1018 else
1019 set_option (OPTIMAPLSUB);
1020
1021 mutt_unget_event (0, OP_CHECK_NEW);
1022 break;
1023
1024 case OP_CREATE_MAILBOX:
1025 if (!state.imap_browse)
1026 {
1027 mutt_error (_("Create is only supported for IMAP mailboxes"));
1028 break;
1029 }
1030
1031 if (!imap_mailbox_create (mutt_b2s (LastDir), defaultsel))
1032 {
1033 /* TODO: find a way to detect if the new folder would appear in
1034 * this window, and insert it without starting over. */
1035 destroy_state (&state);
1036 init_state (&state, NULL);
1037 state.imap_browse = 1;
1038 imap_browse (mutt_b2s (LastDir), &state);
1039 browser_sort (&state);
1040 menu->data = state.entry;
1041 menu->current = 0;
1042 menu->top = 0;
1043 init_menu (&state, menu, title, sizeof (title), buffy, mutt_b2s (defaultsel));
1044 }
1045 /* else leave error on screen */
1046 break;
1047
1048 case OP_RENAME_MAILBOX:
1049 if (!state.entry[menu->current].imap)
1050 mutt_error (_("Rename is only supported for IMAP mailboxes"));
1051 else
1052 {
1053 int nentry = menu->current;
1054
1055 if (imap_mailbox_rename (state.entry[nentry].full_path, defaultsel) >= 0)
1056 {
1057 destroy_state (&state);
1058 init_state (&state, NULL);
1059 state.imap_browse = 1;
1060 imap_browse (mutt_b2s (LastDir), &state);
1061 browser_sort (&state);
1062 menu->data = state.entry;
1063 menu->current = 0;
1064 menu->top = 0;
1065 init_menu (&state, menu, title, sizeof (title), buffy, mutt_b2s (defaultsel));
1066 }
1067 }
1068 break;
1069
1070 case OP_DELETE_MAILBOX:
1071 if (!state.entry[menu->current].imap)
1072 mutt_error (_("Delete is only supported for IMAP mailboxes"));
1073 else
1074 {
1075 char msg[SHORT_STRING];
1076 IMAP_MBOX mx;
1077 int nentry = menu->current;
1078
1079 imap_parse_path (state.entry[nentry].full_path, &mx);
1080 if (!mx.mbox)
1081 {
1082 mutt_error _("Cannot delete root folder");
1083 break;
1084 }
1085 snprintf (msg, sizeof (msg), _("Really delete mailbox \"%s\"?"),
1086 mx.mbox);
1087 if (mutt_yesorno (msg, MUTT_NO) == MUTT_YES)
1088 {
1089 if (!imap_delete_mailbox (Context, mx))
1090 {
1091 /* free the mailbox from the browser */
1092 FREE (&((state.entry)[nentry].display_name));
1093 FREE (&((state.entry)[nentry].full_path));
1094 /* and move all other entries up */
1095 if (nentry+1 < state.entrylen)
1096 memmove (state.entry + nentry, state.entry + nentry + 1,
1097 sizeof (struct folder_file) * (state.entrylen - (nentry+1)));
1098 memset (&state.entry[state.entrylen - 1], 0,
1099 sizeof (struct folder_file));
1100 state.entrylen--;
1101 mutt_message _("Mailbox deleted.");
1102 mutt_buffer_clear (defaultsel);
1103 init_menu (&state, menu, title, sizeof (title), buffy, mutt_b2s (defaultsel));
1104 }
1105 else
1106 mutt_error _("Mailbox deletion failed.");
1107 }
1108 else
1109 mutt_message _("Mailbox not deleted.");
1110 FREE (&mx.mbox);
1111 }
1112 break;
1113#endif
1114
1115 case OP_CHANGE_DIRECTORY:
1116
1117 mutt_buffer_strcpy (buf, mutt_b2s (LastDir));
1118 mutt_buffer_clear (defaultsel);
1119#ifdef USE_IMAP
1120 if (!state.imap_browse)
1121#endif
1122 {
1123 /* add '/' at the end of the directory name if not already there */
1124 size_t len = mutt_buffer_len (LastDir);
1125 if (len && (mutt_b2s (LastDir)[len-1] != '/'))
1126 mutt_buffer_addch (buf, '/');
1127 }
1128
1129 /* buf comes from the buffer pool, so defaults to size LONG_STRING */
1130 if ((mutt_buffer_get_field (_("Chdir to: "), buf, MUTT_FILE) == 0) &&
1131 mutt_buffer_len (buf))
1132 {
1133 buffy = 0;
1134 mutt_buffer_expand_path (buf);
1135#ifdef USE_IMAP
1136 if (mx_is_imap (mutt_b2s (buf)))
1137 {
1138 mutt_buffer_strcpy (LastDir, mutt_b2s (buf));
1139 destroy_state (&state);
1140 init_state (&state, NULL);
1141 state.imap_browse = 1;
1142 imap_browse (mutt_b2s (LastDir), &state);
1143 browser_sort (&state);
1144 menu->data = state.entry;
1145 menu->current = 0;
1146 menu->top = 0;
1147 init_menu (&state, menu, title, sizeof (title), buffy, mutt_b2s (defaultsel));
1148 }
1149 else
1150#endif
1151 {
1152 if (*(mutt_b2s (buf)) != '/')
1153 {
1154 /* in case dir is relative, make it relative to LastDir,
1155 * not current working dir */
1156 mutt_buffer_concat_path (tmp, mutt_b2s (LastDir), mutt_b2s (buf));
1157 mutt_buffer_strcpy (buf, mutt_b2s (tmp));
1158 }
1159 if (stat (mutt_b2s (buf), &st) == 0)
1160 {
1161 if (S_ISDIR (st.st_mode))
1162 {
1163 destroy_state (&state);
1164 if (examine_directory (menu, &state, mutt_b2s (buf), mutt_b2s (prefix)) == 0)
1165 mutt_buffer_strcpy (LastDir, mutt_b2s (buf));
1166 else
1167 {
1168 mutt_error _("Error scanning directory.");
1169 if (examine_directory (menu, &state, mutt_b2s (LastDir), mutt_b2s (prefix)) == -1)
1170 {
1171 goto bail;
1172 }
1173 }
1174 menu->current = 0;
1175 menu->top = 0;
1176 init_menu (&state, menu, title, sizeof (title), buffy, mutt_b2s (defaultsel));
1177 }
1178 else
1179 mutt_error (_("%s is not a directory."), mutt_b2s (buf));
1180 }
1181 else
1182 mutt_perror (mutt_b2s (buf));
1183 }
1184 }
1185 break;
1186
1187 case OP_ENTER_MASK:
1188
1189 mutt_buffer_strcpy (buf, NONULL(Mask.pattern));
1190 /* buf comes from the buffer pool, so defaults to size LONG_STRING */
1191 if (mutt_buffer_get_field (_("File Mask: "), buf, 0) == 0)
1192 {
1193 regex_t *rx = (regex_t *) safe_malloc (sizeof (regex_t));
1194 const char *s = mutt_b2s (buf);
1195 int not = 0, err;
1196
1197 buffy = 0;
1198 /* assume that the user wants to see everything */
1199 if (!(mutt_buffer_len (buf)))
1200 mutt_buffer_strcpy (buf, ".");
1201 SKIPWS (s);
1202 if (*s == '!')
1203 {
1204 s++;
1205 SKIPWS (s);
1206 not = 1;
1207 }
1208
1209 if ((err = REGCOMP (rx, s, REG_NOSUB)) != 0)
1210 {
1211 regerror (err, rx, buf->data, buf->dsize);
1212 mutt_buffer_fix_dptr (buf);
1213 FREE (&rx);
1214 mutt_error ("%s", mutt_b2s (buf));
1215 }
1216 else
1217 {
1218 mutt_str_replace (&Mask.pattern, mutt_b2s (buf));
1219 regfree (Mask.rx);
1220 FREE (&Mask.rx);
1221 Mask.rx = rx;
1222 Mask.not = not;
1223
1224 destroy_state (&state);
1225#ifdef USE_IMAP
1226 if (state.imap_browse)
1227 {
1228 init_state (&state, NULL);
1229 state.imap_browse = 1;
1230 imap_browse (mutt_b2s (LastDir), &state);
1231 browser_sort (&state);
1232 menu->data = state.entry;
1233 init_menu (&state, menu, title, sizeof (title), buffy, mutt_b2s (defaultsel));
1234 }
1235 else
1236#endif
1237 if (examine_directory (menu, &state, mutt_b2s (LastDir), NULL) == 0)
1238 init_menu (&state, menu, title, sizeof (title), buffy, mutt_b2s (defaultsel));
1239 else
1240 {
1241 mutt_error _("Error scanning directory.");
1242 goto bail;
1243 }
1244 killPrefix = 0;
1245 if (!state.entrylen)
1246 {
1247 mutt_error _("No files match the file mask");
1248 break;
1249 }
1250 }
1251 }
1252 break;
1253
1254 case OP_SORT:
1255 case OP_SORT_REVERSE:
1256
1257 {
1258 int resort = 1;
1259 int reverse = (op == OP_SORT_REVERSE);
1260
1261 switch (mutt_multi_choice ((reverse) ?
1262 _("Reverse sort by (d)ate, (a)lpha, si(z)e, (c)ount, (u)nread, or do(n)'t sort? ") :
1263 _("Sort by (d)ate, (a)lpha, si(z)e, (c)ount, (u)nread, or do(n)'t sort? "),
1264 _("dazcun")))
1265 {
1266 case -1: /* abort */
1267 resort = 0;
1268 break;
1269
1270 case 1: /* (d)ate */
1271 BrowserSort = SORT_DATE;
1272 break;
1273
1274 case 2: /* (a)lpha */
1275 BrowserSort = SORT_SUBJECT;
1276 break;
1277
1278 case 3: /* si(z)e */
1279 BrowserSort = SORT_SIZE;
1280 break;
1281
1282 case 4: /* (c)ount */
1283 BrowserSort = SORT_COUNT;
1284 break;
1285
1286 case 5: /* (u)nread */
1287 BrowserSort = SORT_UNREAD;
1288 break;
1289
1290 case 6: /* do(n)'t sort */
1291 BrowserSort = SORT_ORDER;
1292 resort = 0;
1293 break;
1294 }
1295 if (resort)
1296 {
1297 BrowserSort |= reverse ? SORT_REVERSE : 0;
1298 browser_sort (&state);
1299 set_sticky_cursor (&state, menu, mutt_b2s (defaultsel));
1300 menu->redraw = REDRAW_FULL;
1301 }
1302 break;
1303 }
1304
1305 case OP_TOGGLE_MAILBOXES:
1306 buffy = 1 - buffy;
1307 menu->current = 0;
1308 /* fall through */
1309
1310 case OP_CHECK_NEW:
1311 destroy_state (&state);
1312 mutt_buffer_clear (prefix);
1313 killPrefix = 0;
1314
1315 if (buffy)
1316 {
1317 if (examine_mailboxes (menu, &state) == -1)
1318 goto bail;
1319 }
1320#ifdef USE_IMAP
1321 else if (mx_is_imap (mutt_b2s (LastDir)))
1322 {
1323 init_state (&state, NULL);
1324 state.imap_browse = 1;
1325 imap_browse (mutt_b2s (LastDir), &state);
1326 browser_sort (&state);
1327 menu->data = state.entry;
1328 }
1329#endif
1330 else if (examine_directory (menu, &state, mutt_b2s (LastDir), mutt_b2s (prefix)) == -1)
1331 goto bail;
1332 init_menu (&state, menu, title, sizeof (title), buffy, mutt_b2s (defaultsel));
1333 break;
1334
1335 case OP_BUFFY_LIST:
1336 mutt_buffy_list ();
1337 break;
1338
1339 case OP_BROWSER_NEW_FILE:
1340
1341 mutt_buffer_printf (buf, "%s/", mutt_b2s (LastDir));
1342 /* buf comes from the buffer pool, so defaults to size LONG_STRING */
1343 if (mutt_buffer_get_field (_("New file name: "), buf, MUTT_FILE) == 0)
1344 {
1345 mutt_buffer_strcpy (f, mutt_b2s (buf));
1346 destroy_state (&state);
1347 goto bail;
1348 }
1349 break;
1350
1351 case OP_BROWSER_VIEW_FILE:
1352 if (!state.entrylen)
1353 {
1354 mutt_error _("No files match the file mask");
1355 break;
1356 }
1357
1358#ifdef USE_IMAP
1359 if (state.entry[menu->current].selectable)
1360 {
1361 mutt_buffer_strcpy (f, state.entry[menu->current].full_path);
1362 destroy_state (&state);
1363 goto bail;
1364 }
1365 else
1366#endif
1367 if (S_ISDIR (state.entry[menu->current].mode) ||
1368 (S_ISLNK (state.entry[menu->current].mode) &&
1369 link_is_dir (state.entry[menu->current].full_path)))
1370 {
1371 mutt_error _("Can't view a directory");
1372 break;
1373 }
1374 else
1375 {
1376 BODY *b;
1377
1378 b = mutt_make_file_attach (state.entry[menu->current].full_path);
1379 if (b != NULL)
1380 {
1381 mutt_view_attachment (NULL, b, MUTT_REGULAR, NULL, NULL);
1382 mutt_free_body (&b);
1383 menu->redraw = REDRAW_FULL;
1384 }
1385 else
1386 mutt_error _("Error trying to view file");
1387 }
1388 }
1389 }
1390
1391bail:
1392 mutt_buffer_pool_release (&buf);
1393 mutt_buffer_pool_release (&prefix);
1394 mutt_buffer_pool_release (&tmp);
1395 mutt_buffer_pool_release (&OldLastDir);
1396 mutt_buffer_pool_release (&defaultsel);
1397
1398 if (menu)
1399 {
1400 mutt_pop_current_menu (menu);
1401 mutt_menuDestroy (&menu);
1402 }
1403
1404 if (!folder)
1405 mutt_buffer_strcpy (LastDir, mutt_b2s (LastDirBackup));
1406
1407}