wibble
[apps/madmutt.git] / browser.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
4  *
5  * This file is part of mutt-ng, see http://www.muttng.org/.
6  * It's licensed under the GNU General Public License,
7  * please see the file GPL in the top level source directory.
8  */
9
10 #include <lib-lib/lib-lib.h>
11
12 #include <dirent.h>
13 #include <pwd.h>
14 #include <grp.h>
15
16 #include <lib-ui/lib-ui.h>
17 #include <lib-ui/menu.h>
18 #include <lib-mx/mx.h>
19
20 #include "mutt.h"
21 #include "buffy.h"
22 #include "sort.h"
23 #include "browser.h"
24 #include "attach.h"
25
26 #include <imap/imap.h>
27
28 typedef struct folder_t {
29   struct folder_file *ff;
30   int num;
31 } FOLDER;
32
33 static char LastDir[_POSIX_PATH_MAX] = "";
34 static char LastDirBackup[_POSIX_PATH_MAX] = "";
35
36 /* Frees up the memory allocated for the local-global variables.  */
37 static void destroy_state (struct browser_state *state)
38 {
39   int c;
40
41   for (c = 0; c < state->entrylen; c++) {
42     p_delete(&state->entry[c].name);
43     p_delete(&state->entry[c].desc);
44     p_delete(&state->entry[c].st);
45   }
46   p_delete(&state->folder);
47   p_delete(&state->entry);
48 }
49
50 static int browser_compare_subject (const void *a, const void *b)
51 {
52   struct folder_file *pa = (struct folder_file *) a;
53   struct folder_file *pb = (struct folder_file *) b;
54
55   int r = strcoll(NONULL(pa->name), NONULL(pb->name));
56
57   return ((BrowserSort & SORT_REVERSE) ? -r : r);
58 }
59
60 static int browser_compare_date (const void *a, const void *b)
61 {
62   struct folder_file *pa = (struct folder_file *) a;
63   struct folder_file *pb = (struct folder_file *) b;
64
65   int r = pa->mtime - pb->mtime;
66
67   return ((BrowserSort & SORT_REVERSE) ? -r : r);
68 }
69
70 static int browser_compare_size (const void *a, const void *b)
71 {
72   struct folder_file *pa = (struct folder_file *) a;
73   struct folder_file *pb = (struct folder_file *) b;
74
75   int r = pa->size - pb->size;
76
77   return ((BrowserSort & SORT_REVERSE) ? -r : r);
78 }
79
80 static void browser_sort (struct browser_state *state)
81 {
82   int (*f) (const void *, const void *);
83
84   switch (BrowserSort & SORT_MASK) {
85   case SORT_ORDER:
86     return;
87   case SORT_DATE:
88     f = browser_compare_date;
89     break;
90   case SORT_SIZE:
91     f = browser_compare_size;
92     break;
93   case SORT_SUBJECT:
94   default:
95     f = browser_compare_subject;
96     break;
97   }
98   qsort (state->entry, state->entrylen, sizeof (struct folder_file), f);
99 }
100
101 static int link_is_dir (const char *folder, const char *path)
102 {
103   struct stat st;
104   char fullpath[_POSIX_PATH_MAX];
105
106   mutt_concat_path(fullpath, sizeof(fullpath), folder, path);
107
108   if (stat (fullpath, &st) == 0)
109     return (S_ISDIR (st.st_mode));
110   else
111     return 0;
112 }
113
114 static const char *
115 folder_format_str(char *dest, ssize_t destlen, char op,
116                   const char *src, const char *fmt,
117                   const char *ifstr, const char *elstr,
118                   anytype data, format_flag flags)
119 {
120   char fn[STRING], tmp[STRING], permission[11], date[16];
121   const char *t_fmt;
122   time_t tnow;
123   FOLDER *folder = data.ptr;
124   struct passwd *pw;
125   struct group *gr;
126   int optional = (flags & M_FORMAT_OPTIONAL);
127
128   switch (op) {
129   case 'C':
130     snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
131     snprintf (dest, destlen, tmp, folder->num + 1);
132     break;
133
134   case 'd':
135     if (folder->ff->st != NULL) {
136       tnow = time (NULL);
137       t_fmt =
138         tnow - folder->ff->st->st_mtime <
139         31536000 ? "%b %d %H:%M" : "%b %d  %Y";
140       strftime (date, sizeof (date), t_fmt,
141                 localtime (&folder->ff->st->st_mtime));
142       mutt_format_s (dest, destlen, fmt, date);
143     }
144     else
145       mutt_format_s (dest, destlen, fmt, "");
146     break;
147
148   case 'f':
149     {
150       const char *s;
151
152       if (folder->ff->imap)
153         s = NONULL(folder->ff->desc);
154       else
155         s = NONULL(folder->ff->name);
156
157       snprintf (fn, sizeof (fn), "%s%s", s,
158                 folder->ff->st ? (S_ISLNK (folder->ff->st->st_mode) ? "@" :
159                                   (S_ISDIR (folder->ff->st->st_mode) ? "/" :
160                                    ((folder->ff->st->st_mode & S_IXUSR) !=
161                                     0 ? "*" : ""))) : "");
162
163       mutt_format_s (dest, destlen, fmt, fn);
164       break;
165     }
166   case 'F':
167     if (folder->ff->st != NULL) {
168       snprintf (permission, sizeof (permission), "%c%c%c%c%c%c%c%c%c%c",
169                 S_ISDIR(folder->ff->st-> st_mode)
170                 ? 'd' : (S_ISLNK(folder->ff->st-> st_mode) ? 'l' : '-'),
171                 (folder->ff->st->st_mode & S_IRUSR) != 0 ? 'r' : '-',
172                 (folder->ff->st->st_mode & S_IWUSR) != 0 ? 'w' : '-',
173                 (folder->ff->st->st_mode & S_ISUID) !=
174                 0 ? 's' : (folder->ff->st->st_mode & S_IXUSR) !=
175                 0 ? 'x' : '-',
176                 (folder->ff->st->st_mode & S_IRGRP) != 0 ? 'r' : '-',
177                 (folder->ff->st->st_mode & S_IWGRP) != 0 ? 'w' : '-',
178                 (folder->ff->st->st_mode & S_ISGID) !=
179                 0 ? 's' : (folder->ff->st->st_mode & S_IXGRP) !=
180                 0 ? 'x' : '-',
181                 (folder->ff->st->st_mode & S_IROTH) != 0 ? 'r' : '-',
182                 (folder->ff->st->st_mode & S_IWOTH) != 0 ? 'w' : '-',
183                 (folder->ff->st->st_mode & S_ISVTX) !=
184                 0 ? 't' : (folder->ff->st->st_mode & S_IXOTH) !=
185                 0 ? 'x' : '-');
186       mutt_format_s (dest, destlen, fmt, permission);
187     }
188     else if (folder->ff->imap) {
189       /* mark folders with subfolders AND mail */
190       snprintf (permission, sizeof (permission), "IMAP %c",
191                 (folder->ff->inferiors
192                  && folder->ff->selectable) ? '+' : ' ');
193       mutt_format_s (dest, destlen, fmt, permission);
194     }
195     else
196       mutt_format_s (dest, destlen, fmt, "");
197     break;
198
199   case 'g':
200     if (folder->ff->st != NULL) {
201       if ((gr = getgrgid (folder->ff->st->st_gid)))
202         mutt_format_s (dest, destlen, fmt, gr->gr_name);
203       else {
204         snprintf (tmp, sizeof (tmp), "%%%sld", fmt);
205         snprintf (dest, destlen, tmp, folder->ff->st->st_gid);
206       }
207     }
208     else
209       mutt_format_s (dest, destlen, fmt, "");
210     break;
211
212   case 'l':
213     if (folder->ff->st != NULL) {
214       snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
215       snprintf (dest, destlen, tmp, folder->ff->st->st_nlink);
216     }
217     else
218       mutt_format_s (dest, destlen, fmt, "");
219     break;
220
221   case 'N':
222     if (imap_is_magic (folder->ff->desc, NULL) == M_IMAP) {
223       if (!optional) {
224         snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
225         snprintf (dest, destlen, tmp, folder->ff->new);
226       }
227       else if (!folder->ff->new)
228         optional = 0;
229       break;
230     }
231     snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
232     snprintf (dest, destlen, tmp, folder->ff->new ? 'N' : ' ');
233     break;
234
235   case 's':
236     if (folder->ff->st != NULL) {
237       snprintf (tmp, sizeof (tmp), "%%%sld", fmt);
238       snprintf (dest, destlen, tmp, (long) folder->ff->st->st_size);
239     }
240     else
241       mutt_format_s (dest, destlen, fmt, "");
242     break;
243
244   case 't':
245     snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
246     snprintf (dest, destlen, tmp, folder->ff->tagged ? '*' : ' ');
247     break;
248
249   case 'u':
250     if (folder->ff->st != NULL) {
251       if ((pw = getpwuid (folder->ff->st->st_uid)))
252         mutt_format_s (dest, destlen, fmt, pw->pw_name);
253       else {
254         snprintf (tmp, sizeof (tmp), "%%%sld", fmt);
255         snprintf (dest, destlen, tmp, folder->ff->st->st_uid);
256       }
257     }
258     else
259       mutt_format_s (dest, destlen, fmt, "");
260     break;
261
262   default:
263     snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
264     snprintf (dest, destlen, tmp, op);
265     break;
266   }
267
268
269   if (flags & M_FORMAT_OPTIONAL)
270     m_strformat(dest, destlen, 0, optional ? ifstr : elstr,
271                 folder_format_str, data, 0);
272
273   return src;
274 }
275
276
277 static void add_folder (MUTTMENU * m, struct browser_state *state,
278                         const char *name, const struct stat *s, int new)
279 {
280   if (state->entrylen == state->entrymax) {
281     /* need to allocate more space */
282     p_realloc(&state->entry, state->entrymax += 256);
283     p_clear(&state->entry[state->entrylen], 256);
284     if (m)
285       m->data = state->entry;
286   }
287
288   if (s != NULL) {
289     (state->entry)[state->entrylen].mode  = s->st_mode;
290     (state->entry)[state->entrylen].mtime = s->st_mtime;
291     (state->entry)[state->entrylen].size  = s->st_size;
292     (state->entry)[state->entrylen].st    = p_dup(s, 1);
293   }
294
295   (state->entry)[state->entrylen].new = new;
296   (state->entry)[state->entrylen].name = m_strdup(name);
297   (state->entry)[state->entrylen].desc = m_strdup(name);
298   (state->entry)[state->entrylen].imap = 0;
299   (state->entrylen)++;
300 }
301
302 static void init_state (struct browser_state *state, MUTTMENU * menu)
303 {
304   state->entrylen = 0;
305   state->entrymax = 256;
306   state->entry = p_new(struct folder_file, state->entrymax);
307   state->imap_browse = 0;
308   if (menu)
309     menu->data = state->entry;
310 }
311
312 /* get list of all files/newsgroups with mask */
313 static int examine_directory (MUTTMENU * menu, struct browser_state *state,
314                               char *d, const char *prefix)
315 {
316  
317   struct stat s;
318   DIR *dp;
319   struct dirent *de;
320   char buffer[_POSIX_PATH_MAX + STRING];
321   int i = -1;
322
323   while (stat (d, &s) == -1) {
324     if (errno == ENOENT) {
325       /* The last used directory is deleted, try to use the parent dir. */
326       char *c = strrchr (d, '/');
327
328       if (c && (c > d)) {
329         *c = 0;
330         continue;
331       }
332     }
333     mutt_perror (d);
334     return (-1);
335   }
336
337   if (!S_ISDIR (s.st_mode)) {
338     mutt_error (_("%s is not a directory."), d);
339     return (-1);
340   }
341
342   buffy_check (0);
343
344   if ((dp = opendir (d)) == NULL) {
345     mutt_perror (d);
346     return (-1);
347   }
348
349   init_state (state, menu);
350
351   while ((de = readdir (dp)) != NULL) {
352     if (m_strcmp(de->d_name, ".") == 0)
353       continue;               /* we don't need . */
354
355     if (m_strncmp(prefix, de->d_name, m_strlen(prefix)) != 0)
356       continue;
357     if (!((regexec (Mask.rx, de->d_name, 0, NULL, 0) == 0) ^ Mask.neg))
358       continue;
359
360     mutt_concat_path(buffer, sizeof(buffer), d, de->d_name);
361     if (lstat (buffer, &s) == -1)
362       continue;
363
364     if ((!S_ISREG (s.st_mode)) && (!S_ISDIR (s.st_mode)) &&
365         (!S_ISLNK (s.st_mode)))
366       continue;
367
368     i = buffy_lookup (buffer);
369     add_folder (menu, state, de->d_name, &s, i >= 0 ? Incoming.arr[i]->new : 0);
370   }
371   closedir (dp);
372   sidebar_draw ();
373   browser_sort (state);
374   return 0;
375 }
376
377 /* get list of mailboxes/subscribed newsgroups */
378 static int examine_mailboxes (MUTTMENU * menu, struct browser_state *state)
379 {
380   struct stat s;
381   char buffer[LONG_STRING];
382   int i = 0;
383   BUFFY* tmp;
384
385   if (!Incoming.len)
386     return (-1);
387   buffy_check (0);
388
389   init_state (state, menu);
390
391   for (i = 0; i < Incoming.len; i++) {
392     tmp = Incoming.arr[i];
393     tmp->magic = mx_get_magic (tmp->path);
394     if (tmp->magic == M_IMAP) {
395       add_folder (menu, state, tmp->path, NULL, tmp->new);
396       continue;
397     }
398     if (tmp->magic == M_POP) {
399       add_folder (menu, state, tmp->path, NULL, tmp->new);
400       continue;
401     }
402     if (lstat (tmp->path, &s) == -1)
403       continue;
404
405     if ((!S_ISREG (s.st_mode)) && (!S_ISDIR (s.st_mode)) &&
406         (!S_ISLNK (s.st_mode)))
407       continue;
408
409     m_strcpy(buffer, sizeof(buffer), NONULL(tmp->path));
410     mutt_pretty_mailbox (buffer);
411     add_folder (menu, state, buffer, &s, tmp->new);
412   }
413
414   browser_sort (state);
415   return 0;
416 }
417
418 static int select_file_search (MUTTMENU * menu, regex_t * re, int n)
419 {
420   return (regexec
421           (re, ((struct folder_file *) menu->data)[n].name, 0, NULL, 0));
422 }
423
424 static void folder_entry (char *s, ssize_t slen, MUTTMENU * menu, int num)
425 {
426   FOLDER folder;
427
428   folder.ff = &((struct folder_file *) menu->data)[num];
429   folder.num = num;
430
431   m_strformat(s, slen, getmaxx(main_w), FolderFormat, folder_format_str,
432               &folder, 0);
433 }
434
435 static void init_menu (struct browser_state *state, MUTTMENU * menu,
436                        char *title, ssize_t titlelen, int buffy)
437 {
438   char path[_POSIX_PATH_MAX];
439
440   menu->max = state->entrylen;
441
442   if (menu->current >= menu->max)
443     menu->current = menu->max - 1;
444   if (menu->current < 0)
445     menu->current = 0;
446   if (menu->top > menu->current)
447     menu->top = 0;
448
449   menu->tagged = 0;
450
451   if (buffy)
452     snprintf(title, titlelen, _("Mailboxes [%d]"), buffy_check(0));
453   else {
454     m_strcpy(path, sizeof(path), LastDir);
455     mutt_pretty_mailbox (path);
456     if (state->imap_browse && option (OPTIMAPLSUB))
457       snprintf (title, titlelen, _("Subscribed [%s], File mask: %s"),
458                 path, NONULL (Mask.pattern));
459     else
460       snprintf (title, titlelen, _("Directory [%s], File mask: %s"),
461                 path, NONULL (Mask.pattern));
462   }
463   menu->redraw = REDRAW_FULL;
464 }
465
466 static int file_tag (MUTTMENU * menu, int n, int m)
467 {
468   struct folder_file *ff = &(((struct folder_file *) menu->data)[n]);
469   int ot;
470
471   if (S_ISDIR (ff->mode)
472       || (S_ISLNK (ff->mode) && link_is_dir (LastDir, ff->name))) {
473     mutt_error _("Can't attach a directory!");
474
475     return 0;
476   }
477
478   ot = ff->tagged;
479   ff->tagged = (m >= 0 ? m : !ff->tagged);
480
481   return ff->tagged - ot;
482 }
483
484 void mutt_select_file (char *f, ssize_t flen, int flags, char ***files,
485                        int *numfiles)
486 {
487   char buf[_POSIX_PATH_MAX];
488   char prefix[_POSIX_PATH_MAX] = "";
489   char title[STRING];
490   struct browser_state state;
491   MUTTMENU *menu;
492   struct stat st;
493   int i, killPrefix = 0;
494   int multiple = (flags & M_SEL_MULTI) ? 1 : 0;
495   int folder = (flags & M_SEL_FOLDER) ? 1 : 0;
496   int buffy = (flags & M_SEL_BUFFY) ? 1 : 0;
497
498   buffy = buffy && folder;
499
500   p_clear(&state, 1);
501
502   if (!folder)
503     m_strcpy(LastDirBackup, sizeof(LastDirBackup), LastDir);
504
505   if (*f) {
506     mutt_expand_path (f, flen);
507     if (imap_is_magic (f, NULL) == M_IMAP) {
508       init_state (&state, NULL);
509       state.imap_browse = 1;
510       if (!imap_browse (f, &state))
511         m_strcpy(LastDir, sizeof(LastDir), state.folder);
512     }
513     else {
514       for (i = m_strlen(f) - 1; i > 0 && f[i] != '/'; i--);
515       if (i > 0) {
516         if (f[0] == '/') {
517           i = MIN(ssizeof(LastDir) - 1, i);
518           m_strcpy(LastDir, sizeof(LastDir), f);
519         }
520         else {
521           getcwd(LastDir, sizeof(LastDir));
522           m_strcat(LastDir, sizeof(LastDir), "/");
523           m_strncat(LastDir, sizeof(LastDir), f, i);
524         }
525       }
526       else {
527         if (f[0] == '/')
528           m_strcpy(LastDir, sizeof(LastDir), "/");
529         else
530           getcwd (LastDir, sizeof (LastDir));
531       }
532
533       if (i <= 0 && f[0] != '/')
534         m_strcpy(prefix, sizeof(prefix), f);
535       else
536         m_strcpy(prefix, sizeof(prefix), f + i + 1);
537       killPrefix = 1;
538     }
539   }
540   else {
541     if (!folder)
542       getcwd (LastDir, sizeof (LastDir));
543     else if (!LastDir[0])
544       m_strcpy(LastDir, sizeof(LastDir), NONULL(Maildir));
545
546     if (!buffy && imap_is_magic (LastDir, NULL) == M_IMAP) {
547       init_state (&state, NULL);
548       state.imap_browse = 1;
549       imap_browse (LastDir, &state);
550       browser_sort (&state);
551     }
552   }
553
554   *f = 0;
555
556   if (buffy) {
557     if (examine_mailboxes (NULL, &state) == -1)
558       goto bail;
559   }
560   else
561   if (!state.imap_browse)
562     if (examine_directory (NULL, &state, LastDir, prefix) == -1)
563       goto bail;
564
565   menu = mutt_new_menu ();
566   menu->menu = MENU_FOLDER;
567   menu->make_entry = folder_entry;
568   menu->search = select_file_search;
569   menu->title = title;
570   menu->data = state.entry;
571   if (multiple)
572     menu->tag = file_tag;
573   init_menu (&state, menu, title, sizeof (title), buffy);
574
575   for (;;) {
576     switch (i = mutt_menuLoop (menu)) {
577     case OP_GENERIC_SELECT_ENTRY:
578
579       if (!state.entrylen) {
580         mutt_error _("No files match the file mask");
581
582         break;
583       }
584
585       if (S_ISDIR (state.entry[menu->current].mode) ||
586           (S_ISLNK (state.entry[menu->current].mode) &&
587            link_is_dir (LastDir, state.entry[menu->current].name))
588           || state.entry[menu->current].inferiors
589         ) {
590         /* make sure this isn't a MH or maildir mailbox */
591         if (buffy) {
592           m_strcpy(buf, sizeof(buf), state.entry[menu->current].name);
593           mutt_expand_path (buf, sizeof (buf));
594         }
595         else if (state.imap_browse) {
596           m_strcpy(buf, sizeof(buf), state.entry[menu->current].name);
597         }
598         else
599           mutt_concat_path(buf, sizeof(buf), LastDir,
600                            state.entry[menu->current].name);
601
602         if ((mx_get_magic (buf) <= 0)
603             || state.entry[menu->current].inferiors)
604         {
605           char OldLastDir[_POSIX_PATH_MAX];
606
607           /* save the old directory */
608           m_strcpy(OldLastDir, sizeof(OldLastDir), LastDir);
609
610           if (m_strcmp(state.entry[menu->current].name, "..") == 0) {
611             if (m_strcmp("..", LastDir + m_strlen(LastDir) - 2) == 0)
612               m_strcat(LastDir, sizeof(LastDir), "/..");
613             else {
614               char *p = strrchr (LastDir + 1, '/');
615
616               if (p)
617                 *p = 0;
618               else {
619                 if (LastDir[0] == '/')
620                   LastDir[1] = 0;
621                 else
622                   m_strcat(LastDir, sizeof(LastDir), "/..");
623               }
624             }
625           }
626           else if (buffy) {
627             m_strcpy(LastDir, sizeof(LastDir),
628                      state.entry[menu->current].name);
629             mutt_expand_path (LastDir, sizeof (LastDir));
630           }
631           else if (state.imap_browse) {
632             int n;
633             ciss_url_t url;
634
635             m_strcpy(LastDir, sizeof(LastDir),
636                      state.entry[menu->current].name);
637             /* tack on delimiter here */
638             n = m_strlen(LastDir) + 1;
639
640             /* special case "" needs no delimiter */
641             url_parse_ciss (&url, state.entry[menu->current].name);
642             if (url.path &&
643                 (state.entry[menu->current].delim != '\0') &&
644                 (n < ssizeof (LastDir))) {
645               LastDir[n] = '\0';
646               LastDir[n - 1] = state.entry[menu->current].delim;
647             }
648           }
649           else {
650             char tmp[_POSIX_PATH_MAX];
651
652             mutt_concat_path(tmp, sizeof(tmp), LastDir,
653                              state.entry[menu->current].name);
654             m_strcpy(LastDir, sizeof(LastDir), tmp);
655           }
656
657           destroy_state (&state);
658           if (killPrefix) {
659             prefix[0] = 0;
660             killPrefix = 0;
661           }
662           buffy = 0;
663           if (state.imap_browse) {
664             init_state (&state, NULL);
665             state.imap_browse = 1;
666             imap_browse (LastDir, &state);
667             browser_sort (&state);
668             menu->data = state.entry;
669           }
670           else
671           if (examine_directory (menu, &state, LastDir, prefix) == -1) {
672             /* try to restore the old values */
673             m_strcpy(LastDir, sizeof(LastDir), OldLastDir);
674             if (examine_directory (menu, &state, LastDir, prefix) == -1) {
675               m_strcpy(LastDir, sizeof(LastDir), NONULL(mod_core.homedir));
676               goto bail;
677             }
678           }
679           menu->current = 0;
680           menu->top = 0;
681           init_menu (&state, menu, title, sizeof (title), buffy);
682           break;
683         }
684       }
685
686       if (buffy) {
687         m_strcpy(f, flen, state.entry[menu->current].name);
688         mutt_expand_path (f, flen);
689       }
690       else if (state.imap_browse)
691         m_strcpy(f, flen, state.entry[menu->current].name);
692       else
693         mutt_concat_path(f, flen, LastDir, state.entry[menu->current].name);
694
695       /* Fall through to OP_EXIT */
696
697     case OP_EXIT:
698
699       if (multiple) {
700         char **tfiles;
701         int j;
702         int h;
703
704         if (menu->tagged) {
705           *numfiles = menu->tagged;
706           tfiles = p_new(char *, *numfiles);
707           for (h = 0, j = 0; h < state.entrylen; i++) {
708             struct folder_file ff = state.entry[i];
709             char full[_POSIX_PATH_MAX];
710
711             if (ff.tagged) {
712               mutt_concat_path(full, sizeof(full), LastDir, ff.name);
713               mutt_expand_path (full, sizeof (full));
714               tfiles[j++] = m_strdup(full);
715             }
716           }
717           *files = tfiles;
718         }
719         else if (f[0]) {        /* no tagged entries. return selected entry */
720           *numfiles = 1;
721           tfiles = p_new(char *, *numfiles);
722           mutt_expand_path (f, flen);
723           tfiles[0] = m_strdup(f);
724           *files = tfiles;
725         }
726       }
727
728       destroy_state (&state);
729       mutt_menuDestroy (&menu);
730       goto bail;
731
732     case OP_BROWSER_TELL:
733       if (state.entrylen)
734         mutt_message ("%s", state.entry[menu->current].name);
735       break;
736
737     case OP_BROWSER_TOGGLE_LSUB:
738       if (option (OPTIMAPLSUB)) {
739         unset_option (OPTIMAPLSUB);
740       }
741       else {
742         set_option (OPTIMAPLSUB);
743       }
744       mutt_ungetch (0, OP_CHECK_NEW);
745       break;
746
747     case OP_CREATE_MAILBOX:
748       if (!state.imap_browse)
749         mutt_error (_("Create is only supported for IMAP mailboxes"));
750       else {
751         imap_mailbox_create (LastDir);
752         /* TODO: find a way to detect if the new folder would appear in
753          *   this window, and insert it without starting over. */
754         destroy_state (&state);
755         init_state (&state, NULL);
756         state.imap_browse = 1;
757         imap_browse (LastDir, &state);
758         browser_sort (&state);
759         menu->data = state.entry;
760         menu->current = 0;
761         menu->top = 0;
762         init_menu (&state, menu, title, sizeof (title), buffy);
763         MAYBE_REDRAW (menu->redraw);
764       }
765       break;
766
767     case OP_RENAME_MAILBOX:
768       if (!state.entry[menu->current].imap)
769         mutt_error (_("Rename is only supported for IMAP mailboxes"));
770       else {
771         int nentry = menu->current;
772
773         if (imap_mailbox_rename (state.entry[nentry].name) >= 0) {
774           destroy_state (&state);
775           init_state (&state, NULL);
776           state.imap_browse = 1;
777           imap_browse (LastDir, &state);
778           browser_sort (&state);
779           menu->data = state.entry;
780           menu->current = 0;
781           menu->top = 0;
782           init_menu (&state, menu, title, sizeof (title), buffy);
783           MAYBE_REDRAW (menu->redraw);
784         }
785       }
786       break;
787
788     case OP_DELETE_MAILBOX:
789       if (!state.entry[menu->current].imap)
790         mutt_error (_("Delete is only supported for IMAP mailboxes"));
791       else {
792         char msg[STRING];
793         IMAP_MBOX mx;
794         int nentry = menu->current;
795
796         imap_parse_path (state.entry[nentry].name, &mx);
797         snprintf (msg, sizeof (msg), _("Really delete mailbox \"%s\"?"),
798                   mx.mbox);
799         if (mutt_yesorno (msg, M_NO) == M_YES) {
800           if (!imap_delete_mailbox (Context, mx)) {
801             /* free the mailbox from the browser */
802             p_delete(&((state.entry)[nentry].name));
803             p_delete(&((state.entry)[nentry].desc));
804             /* and move all other entries up */
805             if (nentry + 1 < state.entrylen)
806               memmove (state.entry + nentry, state.entry + nentry + 1,
807                        sizeof (struct folder_file) * (state.entrylen -
808                                                       (nentry + 1)));
809             state.entrylen--;
810             mutt_message _("Mailbox deleted.");
811
812             init_menu (&state, menu, title, sizeof (title), buffy);
813             MAYBE_REDRAW (menu->redraw);
814           }
815         }
816         else
817           mutt_message _("Mailbox not deleted.");
818         p_delete(&mx.mbox);
819       }
820       break;
821
822     case OP_CHANGE_DIRECTORY:
823       m_strcpy(buf, sizeof(buf), LastDir);
824       if (!state.imap_browse)
825       {
826         /* add '/' at the end of the directory name if not already there */
827         ssize_t len = m_strlen(LastDir);
828
829         if (len && LastDir[len - 1] != '/' && ssizeof(buf) > len)
830           buf[len] = '/';
831       }
832
833       if (mutt_get_field (_("Chdir to: "), buf, sizeof (buf), M_FILE) == 0 &&
834           buf[0]) {
835         buffy = 0;
836         mutt_expand_path (buf, sizeof (buf));
837         if (imap_is_magic (buf, NULL) == M_IMAP) {
838           m_strcpy(LastDir, sizeof(LastDir), buf);
839           destroy_state (&state);
840           init_state (&state, NULL);
841           state.imap_browse = 1;
842           imap_browse (LastDir, &state);
843           browser_sort (&state);
844           menu->data = state.entry;
845           menu->current = 0;
846           menu->top = 0;
847           init_menu (&state, menu, title, sizeof (title), buffy);
848         }
849         else
850         if (stat (buf, &st) == 0) {
851           if (S_ISDIR (st.st_mode)) {
852             destroy_state (&state);
853             if (examine_directory (menu, &state, buf, prefix) == 0)
854               m_strcpy(LastDir, sizeof(LastDir), buf);
855             else {
856               mutt_error _("Error scanning directory.");
857
858               if (examine_directory (menu, &state, LastDir, prefix) == -1) {
859                 mutt_menuDestroy (&menu);
860                 goto bail;
861               }
862             }
863             menu->current = 0;
864             menu->top = 0;
865             init_menu (&state, menu, title, sizeof (title), buffy);
866           }
867           else
868             mutt_error (_("%s is not a directory."), buf);
869         }
870         else
871           mutt_perror (buf);
872       }
873       MAYBE_REDRAW (menu->redraw);
874       break;
875
876     case OP_ENTER_MASK:
877
878       m_strcpy(buf, sizeof(buf), NONULL(Mask.pattern));
879       if (mutt_get_field (_("File Mask: "), buf, sizeof (buf), 0) == 0) {
880         regex_t *rx = p_new(regex_t, 1);
881         char *s = buf;
882         int neg = 0, err;
883
884         buffy = 0;
885         /* assume that the user wants to see everything */
886         if (!buf[0])
887           m_strcpy(buf, sizeof(buf), ".");
888         s = vskipspaces(s);
889         if (*s == '!') {
890           s = vskipspaces(s + 1);
891           neg = 1;
892         }
893
894         if ((err = REGCOMP (rx, s, REG_NOSUB)) != 0) {
895           regerror (err, rx, buf, sizeof (buf));
896           regfree (rx);
897           p_delete(&rx);
898           mutt_error ("%s", buf);
899         }
900         else {
901           m_strreplace(&Mask.pattern, buf);
902           regfree (Mask.rx);
903           p_delete(&Mask.rx);
904           Mask.rx = rx;
905           Mask.neg = neg;
906
907           destroy_state (&state);
908           if (state.imap_browse) {
909             init_state (&state, NULL);
910             state.imap_browse = 1;
911             imap_browse (LastDir, &state);
912             browser_sort (&state);
913             menu->data = state.entry;
914             init_menu (&state, menu, title, sizeof (title), buffy);
915           }
916           else
917           if (examine_directory (menu, &state, LastDir, NULL) == 0)
918             init_menu (&state, menu, title, sizeof (title), buffy);
919           else {
920             mutt_error _("Error scanning directory.");
921
922             mutt_menuDestroy (&menu);
923             goto bail;
924           }
925           killPrefix = 0;
926           if (!state.entrylen) {
927             mutt_error _("No files match the file mask");
928
929             break;
930           }
931         }
932       }
933       MAYBE_REDRAW (menu->redraw);
934       break;
935
936     case OP_SORT:
937     case OP_SORT_REVERSE:
938
939       {
940         int resort = 1;
941         int reverse = (i == OP_SORT_REVERSE);
942
943         switch (mutt_multi_choice ((reverse) ?
944                                    _
945                                    ("Reverse sort by (d)ate, (a)lpha, si(z)e or do(n)'t sort? ")
946                                    :
947                                    _
948                                    ("Sort by (d)ate, (a)lpha, si(z)e or do(n)'t sort? "),
949                                    _("dazn"))) {
950         case -1:               /* abort */
951           resort = 0;
952           break;
953
954         case 1:                /* (d)ate */
955           BrowserSort = SORT_DATE;
956           break;
957
958         case 2:                /* (a)lpha */
959           BrowserSort = SORT_SUBJECT;
960           break;
961
962         case 3:                /* si(z)e */
963           BrowserSort = SORT_SIZE;
964           break;
965
966         case 4:                /* do(n)'t sort */
967           BrowserSort = SORT_ORDER;
968           resort = 0;
969           break;
970         }
971         if (resort) {
972           BrowserSort |= reverse ? SORT_REVERSE : 0;
973           browser_sort (&state);
974           menu->redraw = REDRAW_FULL;
975         }
976         break;
977       }
978
979     case OP_TOGGLE_MAILBOXES:
980       buffy = 1 - buffy;
981
982     case OP_CHECK_NEW:
983       destroy_state (&state);
984       prefix[0] = 0;
985       killPrefix = 0;
986
987       if (buffy) {
988         if (examine_mailboxes (menu, &state) == -1)
989           goto bail;
990       }
991       else if (imap_is_magic (LastDir, NULL) == M_IMAP) {
992         init_state (&state, NULL);
993         state.imap_browse = 1;
994         imap_browse (LastDir, &state);
995         browser_sort (&state);
996         menu->data = state.entry;
997       }
998       else if (examine_directory (menu, &state, LastDir, prefix) == -1)
999         goto bail;
1000       init_menu (&state, menu, title, sizeof (title), buffy);
1001       break;
1002
1003     case OP_BUFFY_LIST:
1004       if (option (OPTFORCEBUFFYCHECK))
1005         buffy_check (1);
1006       buffy_list ();
1007       break;
1008
1009     case OP_BROWSER_NEW_FILE:
1010
1011       snprintf (buf, sizeof (buf), "%s/", LastDir);
1012       if (mutt_get_field (_("New file name: "), buf, sizeof (buf), M_FILE) ==
1013           0) {
1014         m_strcpy(f, flen, buf);
1015         destroy_state (&state);
1016         mutt_menuDestroy (&menu);
1017         goto bail;
1018       }
1019       MAYBE_REDRAW (menu->redraw);
1020       break;
1021
1022     case OP_BROWSER_VIEW_FILE:
1023       if (!state.entrylen) {
1024         mutt_error _("No files match the file mask");
1025
1026         break;
1027       }
1028
1029       if (state.entry[menu->current].selectable) {
1030         m_strcpy(f, flen, state.entry[menu->current].name);
1031         destroy_state (&state);
1032         mutt_menuDestroy (&menu);
1033         goto bail;
1034       }
1035       else
1036       if (S_ISDIR (state.entry[menu->current].mode) ||
1037             (S_ISLNK (state.entry[menu->current].mode) &&
1038                link_is_dir (LastDir, state.entry[menu->current].name))) {
1039         mutt_error _("Can't view a directory");
1040
1041         break;
1042       }
1043       else {
1044         BODY *b;
1045         char nbuf[_POSIX_PATH_MAX];
1046
1047         mutt_concat_path(nbuf, sizeof(nbuf), LastDir,
1048                          state.entry[menu->current].name);
1049         b = mutt_make_file_attach (nbuf);
1050         if (b != NULL) {
1051           mutt_view_attachment (NULL, b, M_REGULAR, NULL, NULL, 0);
1052           body_list_wipe(&b);
1053           menu->redraw = REDRAW_FULL;
1054         }
1055         else
1056           mutt_error _("Error trying to view file");
1057       }
1058       break;
1059
1060     case OP_BROWSER_SUBSCRIBE:
1061     case OP_BROWSER_UNSUBSCRIBE:
1062       if (i == OP_BROWSER_SUBSCRIBE)
1063         imap_subscribe (state.entry[menu->current].name, 1);
1064       else
1065         imap_subscribe (state.entry[menu->current].name, 0);
1066     }
1067   }
1068
1069 bail:
1070
1071   if (!folder)
1072     m_strcpy(LastDir, sizeof(LastDir), LastDirBackup);
1073
1074 }