mutt stable branch with some hacks
at jcs 1407 lines 37 kB view raw
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}