jz@mulga.oz.au (Justin Zobel) (10/14/87)
Submitted by: jz@mulga.oz.au Posting-number: Volume 12, Issue 6 Archive-name: vmail/Part03 This is the third of three parts of vmail, an interactive mail handler that sits on top of MH. vmail has a number of advantages over raw MH. It is screen-based and faster (and more convenient) than the MH show-scan-rmm refile cycle. vmail makes it feasible for users to organise and keep track of moderate volumes of mail without wasting too much time, and is very simple to use. It has been in use at Melbourne University Computer Science Department for about six months without any problems, and has become the interface of choice for many users. : ---------------------------------------- cut here echo x - "load.c" 2>&1 sed "s/^X//" >"load.c" <<'!The!End!' X/* -------------------- X vmail -- load.c X X Routines to find folders, find all mail items in folders. Empty X folders are ignored. X X Copyright (C) J. Zobel, University of Melbourne, October 1987. X-------------------- */ X X#include "defs.h" X#include <ctype.h> X X X/* -------------------- X Find all folders in mail directory, set up linked list. X-------------------- */ Xvoid Xfind_folders() X{ X struct direct *dp; X struct stat statbuf; X DIR *dirp; X int i, n = 0, scmp(); X char str[LEN], **fds, **ftmp; X folder tmp, last = (folder) NULL; X X stat(mail_dir, &statbuf); X /* guess number of folders - 10 is an error bound */ X ftmp = fds = (char **) malloc(sizeof(char *) * (statbuf.st_nlink+10)); X dirp = opendir(mail_dir); X for(dp=readdir(dirp) ; dp != (struct direct *)NULL ; dp=readdir(dirp)) { X if(strcmp(dp->d_name, ".") == 0) X continue; X if(strcmp(dp->d_name, "..") == 0) X continue; X sprintf(str, "%s/%s", mail_dir, dp->d_name); X stat(str, &statbuf); X if(! (statbuf.st_mode & S_IFDIR)) X continue; X if(! (statbuf.st_mode & S_IREAD) || ! (statbuf.st_mode & S_IWRITE) X || ! (statbuf.st_mode & S_IEXEC)) { X printf("Folder %s unreadable.\n", dp->d_name); X continue; X } X *ftmp = NEWSTR(strlen(dp->d_name)+1); X strcpy(*ftmp, dp->d_name); X n++, ftmp++; X } X closedir(dirp); X if(n == 0) { X printf("No folders.\n"); X exit(1); X } X qsort(fds, n, sizeof(char *), scmp); X for(i=0, ftmp=fds ; i < n ; ftmp++, i++) { X tmp = NEW(mail_folder); X tmp->name = *ftmp; X tmp->last = tmp->mail = (item) NULL; X tmp->pages = tmp->pagenum = 1; X tmp->valid = false; X tmp->prev = last; X tmp->next = (folder) NULL; X if(last != (folder) NULL) X last->next = tmp; X else X folders = tmp; X last = tmp; X } X} X X Xint Xscmp(p, q) X char **p, **q; X{ X return(strcmp(*p, *q)); X} X X X/* -------------------- X Find all mail items in given folder, throw away folder record if no X mail items. Returns next folder in linked list, adds new folder X records to linked list if original record overflows. X-------------------- */ Xfolder Xfind_mail(flr, load_time) X folder flr; X int load_time; X{ X struct direct *dp, *readdir(); X DIR *dirp, *opendir(); X int i, n = 0, items[MAXITEMS], *itmp = items, dcmp(); X char str[LEN]; X item tmp, last = (item) NULL; X folder new_folder(); X X sprintf(str, "%s/%s", mail_dir, flr->name); X dirp = opendir(str); X if(dirp == (DIR *) NULL) { X if(load_time) X printf("%s: strange folder.\n", str); X } else { X for(dp=readdir(dirp) ; dp!=(struct direct *) NULL ; dp=readdir(dirp)) { X /* get numbers of all mail items */ X if(n >= MAXITEMS) { X no_control(); X printf("\nMore than %d items in folder.\n",MAXITEMS-1); X exit(1); X } X /* ignore anything that is not string of digits */ X if(! digits(dp->d_name)) X continue; X /* assume any file that is string of digits is a mail X item and not a directory */ X *itmp = atoi(dp->d_name); X n++, itmp++; X } X closedir(dirp); X } X if(n == 0) { /* Empty folder - delete from list */ X flr->valid = EMPTY; X if(load_time) X printf("\t%s: empty\n", flr->name); X if(flr->prev != (folder) NULL) X flr->prev->next = flr->next; X else X folders = flr->next; X if(flr->next != (folder) NULL) X flr->next->prev = flr->prev; X return(flr->next); X } else X qsort(items, n, sizeof(int), dcmp); X if(load_time) /* show status of folder */ X if(n == 1) X printf("\t%s: %d\n", flr->name, *items); X else X printf("\t%s: %d-%d (%d items)\n",flr->name,*items,*(items+n-1),n); X for(i=0, itmp=items ; i < n ; itmp++, i++) { X /* add item to list of items in folder */ X tmp = NEW(mail_item); X get_title(flr, tmp, *itmp); X if(i % lines == 0 && last != (item) NULL) { X /* add new record for folder */ X flr->last = last; X flr = new_folder(flr); X last = (item) NULL; X } X tmp->next = (item) NULL; X tmp->prev = last; X if(last != (item) NULL) X last->next = tmp; X else X flr->mail = tmp; X last = tmp; X } X flr->last = last; X return(flr->next); X} X X Xint Xdcmp(a, b) X int *a, *b; X{ X return(*a - *b); X} X X X/* -------------------- X Return true if string consists only of digits. X-------------------- */ Xint Xdigits(str) X char *str; X{ X for( ; *str != '\0' ; str++) X if(! isdigit(*str)) X return(false); X return(true); X} X X X/* -------------------- X Make header line from mail item. X-------------------- */ Xvoid Xget_title(flr, mail, num) X folder flr; X item mail; X int num; X{ X FILE *fp, *fopen(); X char date[10], str[LEN], subj[42], from[20], to[16], fill[42]; X char repl = ' ', *first(), *s, *fgets(); X int i; X X subj[0] = from[0] = to[0] = fill[0] = '\0'; X sprintf(str, "%s/%s/%d", mail_dir, flr->name, num); X fp = fopen(str, "r"); X while((s=fgets(str, LEN, fp)) != (char *) NULL && *str != '\n') { X /* while not eof and reading header */ X str[strlen(str)-1] = '\0'; X /* should also check date */ X if(! lstrncmp("date:", str, 5)) X get_date(str+5, date); X else if(! lstrncmp("subject:", str, 8)) { X s = first(str+8); X strncpy(subj, s, 41); X subj[41] = '\0'; X } else if(! lstrncmp("replied:", str, 8)) X repl = '-'; X else if(! lstrncmp("from:", str, 5)) { X s = first(str+5); X if(! isuser(s)) { X strncpy(from, s, 19); X from[19] = '\0'; X } X } else if(! lstrncmp("to:", str, 3)) { X s = first(str+3); X strncpy(to, s, 15); X to[15] = '\0'; X } else if(! lstrncmp("apparently-to:", str, 14)) { X s = first(str+14); X strncpy(to, s, 15); X to[15] = '\0'; X } X } X if(s != (char *) NULL) { X for(i=0 ; i < 50 && fgets(str, LEN, fp) != (char *) NULL ;) { X str[strlen(str)-1] = ' '; X strncpy(fill+i, str, 42-i); X i += strlen(str); X } X fill[41] = '\0'; X } X fclose(fp); X if(subj[0] == '\0') X strcpy(subj, "(none)"); X flatten(subj); X flatten(fill); X if(from[0] == '\0') X sprintf(str, " %8s %cTo: %-15s %s << %s",date,repl,to,subj,fill); X else X sprintf(str, " %8s %c%-19s %s << %s",date,repl,from,subj,fill); X str[cols-6] = '\0'; X mail->title = NEWSTR(strlen(str)+1); X mail->number = num; X strcpy(mail->title, str); X} X X X#ifdef USDATE X#define DAY1 date[3] X#define DAY2 date[4] X#define MTH1 date[0] X#define MTH2 date[1] X#else X#define DAY1 date[0] X#define DAY2 date[1] X#define MTH1 date[3] X#define MTH2 date[4] X#endif X#define YR1 date[6] X#define YR2 date[7] X X/* -------------------- X Get date from first argument; assumes format is one of XDate: Fri, 3 Apr 87 ... (a) XDate: Wed, 17 Jun 87 ... (b) XDate: 07 Sep 87 ... (c) XDate: Tue Sep 29 12:27:01 EST 1987 (d) X Dates are put into Imperial form (dd-mm-yy), or US form (mm-dd-yy) X if -DUSDATE is set. X X This routine should be more flexible re: recognizing date formats. X-------------------- */ Xvoid Xget_date(str, date) X char *str, *date; X{ X int i; X bool get_month(); X char *next_token(); X X date[2] = date[5] = '-'; X date[8] = '\0'; X for(; *str == ' ' || *str == '\t' ; str++) X ; X if(*str == '\0') X goto unknown; X if(! isdigit(*str)) /* day of month not first field */ X str = next_token(str); /* skip day of week */ X if(str == (char *) NULL + 1) X goto unknown; X X if(isdigit(*str)) { /* one of (a), (b) or (c) formats */ X if(isdigit(*(str+1))) /* two-number date */ X#ifdef USDATE X DAY1 = *str, DAY2 = *(++str); X else X DAY1 = '0', DAY2 = *str; X#else X DAY1 = (*str == '0') ? ' ' : *str, DAY2 = *(++str); X else X DAY1 = ' ', DAY2 = *str; X#endif X str = next_token(str); /* go to month */ X if(str == (char *) NULL + 1) X goto unknown; X if(! get_month(str, date)) X goto unknown; X str = next_token(str); /* go to year */ X if(str == (char *) NULL + 1) X goto unknown; X if(! isdigit(*str) || ! isdigit(*(str+1))) X goto unknown; X YR1 = *str, YR2 = *(str+1); X } else { /* format (d) */ X if(! get_month(str, date)) X goto unknown; X str = next_token(str); /* go to day */ X if(str == (char *) NULL + 1) X goto unknown; X if(! isdigit(*str) || ! isdigit(*(str+1))) X goto unknown; X#ifdef USDATE X DAY1 = *str, DAY2 = *(++str); X#else X DAY1 = (*str == '0') ? ' ' : *str, DAY2 = *(++str); X#endif X str += strlen(str) - 2; /* go to year */ X if(! isdigit(*str) || ! isdigit(*(str+1))) X goto unknown; X YR1 = *str, YR2 = *(str+1); X } X return; /* date is ok */ X Xunknown: /* erase date */ X for(i=0 ; i < 8 ; i++) X date[i] = ' '; X} X X Xstatic char *month[] = { X "jan", "feb", "mar", "apr", "may", "jun", X "jul", "aug", "sep", "oct", "nov", "dec" X}; X X/* -------------------- X Put the month from the first part of str in date as a number. X-------------------- */ Xbool Xget_month(str, date) X char *str, *date; X{ X int i; X X for(i=0 ; i < 12 && lstrncmp(month[i], str, 3) != 0 ; i++) X ; X if(i == 12) /* string doesn't match month */ X return(false); X else { X if(i < 9) /* single digit month */ X#ifdef USDATE X MTH1 = ' ', MTH2 = i + '0' + 1; X#else X MTH1 = '0', MTH2 = i + '0' + 1; X#endif X else X MTH1 = '1', MTH2 = i + '0' - 9; X return(true); X } X} X X X/* -------------------- X Replace tabs and newlines in string by spaces. X-------------------- */ Xvoid Xflatten(str) X char *str; X{ X char prev, *s = str; X X for(prev='\0' ; (*s = *str) != '\0' ; prev = *str, str++) { X if(*str == '\t') X *s = ' '; X if(prev != ' ' || *s != ' ') X s++; X } X} X X X/* -------------------- X Return pointer to first non-white character in string. X-------------------- */ Xchar * Xfirst(str) X char *str; X{ X for(; *str == ' ' || *str == '\t' || *str == '\n' ; str++) X ; X return(str); X} X X X/* -------------------- X Check given string for occurence of user's name. X-------------------- */ Xint Xisuser(str) X char *str; X{ X int len = strlen(user); X X for(; *str != '\0' ; str++) X /* assume all chars in all unames alphanumeric */ X if(*str == *user && strncmp(str, user, len) == 0 && X (*(str+len) < 'a' || *(str+len) > 'z') && X (*(str+len) < 'A' || *(str+len) > 'Z') && X (*(str+len) < '0' || *(str+len) > '9')) X return(true); X return(false); X} X X X/* -------------------- X Make a new folder record. X-------------------- */ Xfolder Xnew_folder(flr) X folder flr; X{ X folder f; X X f = NEW(mail_folder); X f->prev = flr; X f->next = flr->next; X flr->next = f; X if(f->next != (folder) NULL) X f->next->prev = f; X f->pagenum = f->pages = flr->pages + 1; X f->name = flr->name; X f->valid = true; X f->mail = f->last = (item) NULL; X flr = f; X for(f=f->prev; f != (folder) NULL && f->name == flr->name ; f=f->prev) X f->pages = flr->pages; X return(flr); X} X X X/* -------------------- X Find next free slot in directory for mail item (that is, find next X unused number) -- structures in vmail may not be up to date if user X has initiated a "send" process. X-------------------- */ Xint Xnext_vacant(flr) X folder flr; X{ X struct direct *dp, *readdir(); X DIR *dirp, *opendir(); X char str[LEN]; X int i, n = 0; X X sprintf(str, "%s/%s", mail_dir, flr->name); X dirp = opendir(str); X for(dp=readdir(dirp) ; dp != (struct direct *) NULL ; dp=readdir(dirp)) { X /* ignore anything that is not string of digits */ X if(! digits(dp->d_name)) X continue; X if((i = atoi(dp->d_name)) > n) X n = i; X } X closedir(dirp); X return(n+1); X} !The!End! echo x - "low.c" 2>&1 sed "s/^X//" >"low.c" <<'!The!End!' X/* -------------------- X vmail -- low.c X X Low level screen update and data structure update routines, other X similar stuff. X X Copyright (C) J. Zobel, University of Melbourne, October 1987. X-------------------- */ X X#include "defs.h" X X/* -------------------- X Get string from terminal (visibly). X-------------------- */ Xvoid Xget_string(mes, str) X char *mes, *str; X{ X char c; X int i; X X move(STATUS, 0); X clrtoeol(); X addstr(mes); X refresh(); X for(i=0 ; (c = getchar()) != '\n' && c != '\r' && c != ESC ; ) X if(i > 0 && c == '\b') { X i--; X mvaddch(STATUS, strlen(mes)+i, ' '); X move(STATUS, strlen(mes)+i); X refresh(); X } else if(c != '\b') { X str[i++] = c; X addch(c); X refresh(); X } X str[i] = '\0'; X move(y, 0); X} X X X/* -------------------- X Put string on status line. X-------------------- */ Xvoid Xaddstatus(str, stand) X char *str; X bool stand; X{ X move(STATUS, 0); X clrtoeol(); X if(stand) X standout(); /* doesn't do anything? */ X addstr(str); X if(stand) X standend(); X move(y, 0); X refresh(); X} X X X/* -------------------- X Fudge deleteline because curses wont delete the line immediately X after a ^Z, and because it doesnt clear the last line of the screen. X-------------------- */ Xvoid Xdeleteline() X{ X mvaddstr(y, 0, "scribble"); X deleteln(); X move(FIRST+lines-1, 0); X clrtoeol(); X move(y, 0); X} X X Xint Xputch(c) X char c; X{ X putchar(c); /* turn macro into routine */ X} X X X/* -------------------- X Put prompt on screen, wait for getchar(). X-------------------- */ Xint Xuse_prompt(win) X WINDOW *win; X{ X int c = (cols-42)/2; X X mvwaddstr(win,FIRST+lines-1,c,"-- hit space to continue, q to quit -- "); X wrefresh(win); X return(getchar()); X} X X X/* -------------------- X Put info for mail item on screen at row i. X-------------------- */ Xvoid Xshow_title(str, i, mail) X char *str; X int i; X item mail; X{ X sprintf(str, "%4d%s", mail->number, mail->title); X mvaddstr(i, 0, str); X} X X X/* -------------------- X Put header for page at top of screen. X-------------------- */ Xvoid Xadd_page_header(str) X char *str; X{ X sprintf(str, "%s (page %d of %d) ", curflr->name, curflr->pagenum, X curflr->pages); X mvaddstr(TITLE, 0, str); X} X X X/* -------------------- X Flush typeahead. X-------------------- */ Xchar Xflushin() X{ X struct sgttyb sg; X X if(do_flush) { X ioctl(0, TIOCGETP, &sg); X ioctl(0, TIOCSETP, &sg); X } X return(getchar()); X} X X X/* -------------------- X Print message, wait for getchar(); X-------------------- */ Xvoid Xhold_end() X{ X tputs(SO, 1, putch); X printf("-- any character to continue --"); X tputs(SE, 1, putch); X fflush(stdout); X getchar(); X} X X X#define ctol(c) (((c) >= 'A' && (c) <= 'Z') ? c-'A'+'a' : c) X X/* -------------------- X Case independent strncmp. X-------------------- */ Xlstrncmp(s1, s2, n) X char *s1, *s2; X int n; X{ X for( ; ctol(*s1) == ctol(*s2) && n > 0 ; s1++, s2++, n--) X ; X return((n > 0) ? *s1 - *s2 : 0); X} X X X/* -------------------- X String processing - more useful form of index. X Skip current non-white then skip white, replacing by null. X-------------------- */ Xchar * Xnext_token(t) X char *t; X{ X for( ; *t != ' ' && *t != '\t' && *t != '\0' ; t++) X ; X for( ; *t == ' ' || *t == '\t' ; t++) X *t = '\0'; X return(t); X} !The!End! echo x - "main.c" 2>&1 sed "s/^X//" >"main.c" <<'!The!End!' X/* -------------------- X vmail -- main.c X X Definitions of global variables, main switch. X X Copyright (C) J. Zobel, University of Melbourne, October 1987. X-------------------- */ X X#include "defs.h" X Xfolder folders = (folder) NULL, curflr, alternate = (folder) NULL; Xitem curmail; Xchar *user, *pager = (char *) NULL, *editor = (char *) NULL, X *mail_dir = (char *) NULL, *shell = (char *) NULL, *context; Xbool top_level = true, do_flush = true, comp_args = true, repl_args = true, X forw_args = true; Xint lines, cols, y, folder_protect = FPROT; Xjmp_buf env; X X#define HELP 37 X Xchar *help_scr[] = { X" . Re-execute last command (if one of acdDefirR)", X" (on repeat, r does not prompt for folder)", X" /<exp> Search forwards for title with <exp> (/ to repeat)", X" ?<exp> Search backwards for title with <exp> (? to repeat)", X" <space> Show current mail item", X" [n]<return> Go forward one (or n) active page(s) of mail items", X" [n]<backspace> Go back one (or n) active page(s) of mail items", X" ^L Refresh", X" ! Invoke shell", X" | Pipe current mail item into command", X" ^ Go to first active page", X" $ Go to last active page", X" a Answer current mail item (call to \"repl\")", X" c Compose new mail item (call to \"comp\")", X" C Go to folder chooser", X" [n]d Delete current (or next inclusive n) mail item(s)", X" D Delete current mail item, show next", X" e Edit current mail item", X" f Forward current mail item (call to \"forw\")", X" F List all folders", X" g,G Go to named (or alternate) folder", X" h Show this page", X" [n]H Go to top of page (or nth from top line)", X" i Incorporate new mail (call to \"inc\")", X" [n]j Move cursor down one (or n) line(s)", X" [n]k Move cursor up one (or n) line(s)", X" [n]L Go to bottom of page (or nth from bottom line)", X" M Go to middle of page", X" [n]n Go forward one (or n) folders, make new folder active", X" [n]p Go back one (or n) folders, make new folder active", X" P Print name of alternate folder", X" q Exit from vmail", X" r,R Refile item in named (or previous) folder", X" s Save current item in named file", X" u Undo most recent deletion (1 mail item only)", X" v Make current folder inactive", X" z Pack current folder" X}; X X X#define USAGE printf( \ X"Usage: vmail [-inc] [-flush] [-comp] [-forw] [-ans] [+curfolder] folders ...\n"), \ X exit(1) X Xmain(argc, argv) X int argc; X char *argv[]; X{ X char c, last = '\0', get_number(), flushin(); X int count = 1, /* accumulate count to give to command */ X pcount = 1; X bool flag = false; /* used to check whether to reset count */ X X argc--, argv++; X X process_args(argc, argv); X#ifndef VERSION X#define VERSION "(local version)" X#endif X printf("vmail %s -- reading mail headers\n", VERSION); X init(argc, argv); X setjmp(env); /* return point from interrupt */ X c = flushin(); X for(;;) { X while(c == '.') X if(last == '\0') { X beep(); X c = flushin(); X } else { X count = pcount; X c = last; X } X switch(c) { X case '|': /* pipe mail to command */ X do_pipe(); X break; X case '!': /* call shell */ X call_shell(); X break; X case '/': /* search forwards */ X search(true); X break; X case '?': /* search backwards */ X search(false); X break; X case '^': /* first active page */ X goto_first_page(); X break; X case '$': /* last active page */ X goto_last_page(); X break; X case '\n': /* next folder */ X case '\r': /* next folder */ X next_page(count, true); X break; X case ' ': /* show current item */ X show_mail(); X break; X case DEL: /* previous folder */ X case '\b': /* previous folder */ X prev_page(count, true); X break; X case 'a': /* answer - call to repl */ X repl(); X break; X case 'c': /* compose - fork of comp */ X comp(); X break; X case 'C': /* go to folder chooser */ X choose(); X break; X case 'd': /* delete item */ X delete_item(count); X break; X case 'D': /* delete item, show next */ X change_item(false); X show_mail(); X break; X case 'e': /* edit current mail item */ X edit(); X break; X case 'f': /* forward - call to forw */ X forw(); X break; X case 'F': /* list all folders */ X list_folders(); X break; X case 'g': /* go to named folder */ X goto_folder(true); X break; X case 'G': /* go to last-named folder */ X goto_folder(false); X break; X case 'h': /* help */ X help(); X break; X case 'H': /* go to top of page */ X cursor_first(count); X break; X case 'i': /* inc */ X inc(); X break; X case 'j': /* cursor down */ X cursor_down(count); X break; X case 'k': /* cursor up */ X cursor_up(count); X break; X case CTRL_L: /* redraw */ X display_page(); X break; X case 'L': /* go to bottom of page */ X cursor_last(count); X break; X case 'M': /* go to middle of page */ X cursor_middle(); X break; X case 'n': /* next folder - load if not there */ X goto_next_folder(count); X break; X case 'p': /* prev folder - load if not there */ X goto_prev_folder(count); X break; X case 'P': /* print name of last-named folder */ X show_folder(); X break; X case 'q': /* quit */ X to_normal(); X fix_mh(); X exit(0); X break; X case 'r': /* move item to named folder */ X move_item(true); X break; X case 'R': /* move item to previous folder */ X move_item(false); X break; X case 's': /* save item in named file */ X save_item(); X break; X case 'u': /* undo */ X undo(); X break; X case 'v': /* make folder inactive */ X inactive(); X break; X case 'z': /* pack current folder */ X pack_folder(); X break; X case '0': /* start of count */ X case '1': X case '2': X case '3': X case '4': X case '5': X case '6': X case '7': X case '8': X case '9': X c = get_number(c, &count); X flag = true; X break; X default: X addstatus("command unknown -- `h' for help", true); X break; X } X switch(c) { X /* repeatable commands */ X case 'r': /* refile item in named folder */ X last = 'R'; X pcount = 1; X break; X case 'a': /* reply - call to repl */ X case 'c': /* compose - fork of comp */ X case 'd': /* delete item */ X case 'D': /* delete item, show next */ X case 'e': /* edit current mail item */ X case 'f': /* forward - call to forw */ X case 'i': /* inc */ X case 'R': /* refile to previous folder */ X case 'w': /* write to file */ X last = c; X pcount = count; X break; X default: X break; X } X if(!flag) { X c = flushin(); X count = 1; X } else { X flag = false; X } X } X} X X X/* -------------------- X Read a number from terminal. X-------------------- */ Xchar Xget_number(c, count) X char c; X int *count; X{ X *count = c - '0'; X while((c = getchar()) >= '0' && c <= '9') X *count = *count * 10 + c - '0'; X return(c); X} X X XWINDOW *helpwin = (WINDOW *) NULL; X X/* -------------------- X Display help messages on help screen. X-------------------- */ Xhelp() X{ X WINDOW *newwin(); X int i, j; X X if(helpwin == (WINDOW *) NULL) X helpwin = newwin(0, 0, 0, 0); X wclear(helpwin); X for(j=i=0 ; i <= HELP ; i++, j++) { X if((i+2) % lines == 0) { X if(use_prompt(helpwin) == 'q') X break; X j=0; X wclear(helpwin); X } X if(i >= HELP) { X use_prompt(helpwin); X break; X } X mvwaddstr(helpwin, j+1, 0, help_scr[i]); X } X display_page(); X} X X X/* -------------------- X Process arguments, command-line or given in profile. X-------------------- */ Xvoid Xprocess_args(argc, argv) X int argc; X char *argv[]; X{ X for( ; argc > 0 ; argc--, argv++) X if(!strcmp(argv[0], "-inc")) { /* incorporate mail before starting */ X if(!vfork()) { X execlp(INC, INC, 0); X printf("Warning: can't execute %s\n", INC); X exit(0); X } X wait(0); X printf("\n"); X } else if(!strcmp(argv[0], "-flush")) /* don't flush typeahead */ X do_flush = false; X else if(!strcmp(argv[0], "-forw")) /* no args to forw */ X forw_args = false; X else if(!strcmp(argv[0], "-comp")) /* no args to comp */ X comp_args = false; X else if(!strcmp(argv[0], "-ans")) /* no args to repl */ X repl_args = false; X else if(*argv[0] == '-') { X printf("%s: illegal option\n", argv[0]); X USAGE; X } X} !The!End! echo x - "move.c" 2>&1 sed "s/^X//" >"move.c" <<'!The!End!' X/* -------------------- X vmail -- move.c X X Routines to delete or move a mail item from current folder. X X Copyright (C) J. Zobel, University of Melbourne, October 1987. X-------------------- */ X X#include "defs.h" X#include <errno.h> X Xstatic char prevfile[LEN] = ""; X Xextern int errno; X X/* -------------------- X Move current item to folder prevfile (get_name = false) or as read from X terminal (get_name = true). X-------------------- */ Xvoid Xmove_item(get_name) X bool get_name; X{ X char *s, str[LEN], str2[LEN]; X folder f, new_folder(), create_folder(); X item m; X bool redraw, /* true if screen to be refreshed */ X change_item(); X int fdi, fdo, i = FIRST-1, num, N; X X /* get name if required */ X if(get_name || *prevfile == '\0') { X get_string("folder? ", str); X if(*str == '\0') { X addstatus("no name given for folder", true); X return; X } X strcpy(prevfile, str); X } else X strcpy(str, prevfile); X /* find first page of named folder */ X sprintf(str2, "refiling to %s ...", str); X addstatus(str2, false); X GOTO_NAME(f, str); X if(f == (folder) NULL) { /* create folder */ X f = create_folder(str); X if(f == (folder) NULL) X return; X } else { X if(f->name == curflr->name) { X addstatus("can't move item to current folder", true); X return; X } X if(f->valid) { /* goto last mail item in folder */ X LAST_OF_NAME(f); X for(i=FIRST, m=f->mail ; m->next != (item) NULL ; i++, m=m->next) X ; X } X } X /* remember current location of mail */ X m = curmail; X num = curmail->number; X s = curflr->name; X /* delete item from current folder, update current folder & screen */ X redraw = change_item(true); X if(f->valid) /* update structures of mail items */ X if(i > lines) { X /* create new folder record */ X f = new_folder(f); X f->mail = f->last = m; X m->prev = m->next = (item) NULL; X N = f->prev->last->number; X } else { X /* insert at end of list of mail items */ X m->prev = f->last; m->next = (item) NULL; X f->last->next = m; X f->last = m; X N = m->prev->number; X } X else X N = next_vacant(f); X/* to avoid race between "send" or other process in background and vmail X foreground, compute next free slot, dont just use given value X*/ X sprintf(str2, "%s/%s/%d", mail_dir, f->name, N); X /* loop until unused file name found */ X for(errno=0 ; (fdo = open(str2, O_WRONLY|O_CREAT|O_EXCL, 0644)) < 0 X && errno == EEXIST ; errno=0) { X N = N + 1; X sprintf(str2, "%s/%s/%d", mail_dir, f->name, N); X } X if(f->valid) X m->number = N; X sprintf(str, "%s/%s/%d", mail_dir, s, num); X fdi = open(str, O_RDONLY); X while((i = read(fdi, str2, LEN)) > 0) /* copy original to new */ X write(fdo, str2, i); X close(fdi); X close(fdo); X unlink(str); /* remove original */ X if(redraw) X display_page(); X else { X add_page_header(str); X move(y, 0); X refresh(); X } X addstatus("refiled", true); X if(curflr == (folder) NULL) { X to_normal(); X exit(1); X } X} X X X/* -------------------- X Delete count items. X-------------------- */ Xvoid Xdelete_item(count) X int count; X{ X bool redraw = false, change_item(); X item m; X char str[LEN]; X X for( ; count > 0 ; count--) { X m = curmail->next; X redraw = change_item(false); X if(redraw || m != curmail) /* on new page, or last item deleted */ X break; X } X if(redraw) X display_page(); X else { X add_page_header(str); X move(y, 0); X refresh(); X } X} X X X/* -------------------- X Structure for deleted item. X-------------------- */ Xstruct { X folder flr; X int number; X} deleted = {(folder) NULL, 0}; X X X/* -------------------- X Either delete (do_move = false) or prepare to move (do_move = true) item. X Delete item from current folder, update screen, find new current folder X if current folder has become empty. X-------------------- */ Xbool Xchange_item(do_move) X bool do_move; X{ X item tmp, m = curmail; X folder F, p, f = curflr, pval, nval; X char s1[LEN], s2[LEN]; X bool redraw, doexit = false; X X if(curmail->next == (item) NULL && curmail->prev == (item) NULL) { X /* have last item in page */ X redraw = true; X pval = curflr->prev; PREV_VALID(pval); X nval = curflr->next; NEXT_VALID(nval); X if(pval == (folder) NULL && nval == (folder) NULL) { X /* no more active pages */ X if(! do_move) X addstatus("Deleting last active mail item -- bye", true); X doexit = true; X } else { X /* update pages, pagenum */ X for(p=curflr->prev ; p != (folder) NULL && p->name == curflr->name X ; p=p->prev) X p->pages -= 1; X for(p=curflr->next ; p != (folder) NULL && p->name == curflr->name X ; p=p->next) X p->pages -= 1, p->pagenum -= 1; X /* find next active page, drop current page from list */ X if(curflr->prev != (folder) NULL) X curflr->prev->next = curflr->next; X else X folders = curflr->next; X if(curflr->next != (folder) NULL) X curflr->next->prev = curflr->prev; X curflr = (nval == (folder) NULL) ? pval : nval; X curmail = curflr->mail; X } X } else { X redraw = false; X deleteline(); X /* move first item from next to current */ X if(curflr->next != (folder) NULL && curflr->name == curflr->next->name) X show_title(s1, FIRST+lines-1, curflr->next->mail); X for(p=curflr->next ; p != (folder) NULL && p->name == curflr->name ; X p=p->next) { X tmp = p->mail; X p->mail = tmp->next; X if(p->mail == (item) NULL) { /* remove folder from list */ X p->prev->next = p->next; X if(p->next != (folder) NULL) X p->next->prev = p->prev; X /* update page counts */ X for(F=p->prev ; F != (folder) NULL && F->name == p->name X ; F=F->prev) X F->pages -= 1; X } else X p->mail->prev = (item) NULL; X p->prev->last->next = tmp; X tmp->prev = p->prev->last; X tmp->next = (item) NULL; X p->prev->last = tmp; X } X /* delete item from linked list of items */ X if(m->prev == (item) NULL) X curflr->mail = m->next; X else X m->prev->next = m->next; X if(m->next == (item) NULL) { X curflr->last = m->prev; X curmail = m->prev; X y--; X } else { X m->next->prev = m->prev; X curmail = m->next; X } X move(y, 0); X } X if(! do_move) { X deleted.flr = f; X deleted.number = m->number; X sprintf(s1, "%s/%s/%d", mail_dir, f->name, m->number); X sprintf(s2, "%s/%s/#%d", mail_dir, f->name, m->number); X rename(s1, s2); X if(doexit) { X to_normal(); X exit(0); X } X } X return(redraw); X} X X X/* -------------------- X Create folder of given name if user agrees, creating directory and X entry in linked list of folders. X-------------------- */ Xfolder Xcreate_folder(str) X char *str; X{ X struct stat statbuf; X char str2[LEN], c; X folder fnew, p, f; X X squash(str); X sprintf(str2, "%s does not exist (or is empty) - create? ", str); X mvaddstr(STATUS, 0, str2); refresh(); X c = getchar(); X move(STATUS, 0); clrtoeol(); move(y, 0); refresh(); X if(c != 'y') X return((folder) NULL); X for(p=(folder) NULL, f=folders ; f != (folder) NULL && X strcmp(str, f->name) > 0 ; p=f, f=f->next) X ; X /* create physical folder */ X sprintf(str2, "%s/%s", mail_dir, str); X if(stat(str2, &statbuf)) { /* doesn't exist */ X if(mkdir(str2, folder_protect)) { X addstatus("Cannot make folder", true); X return((folder) NULL); X } X } else X if(!(statbuf.st_mode & S_IREAD) || !(statbuf.st_mode & S_IWRITE) X || !(statbuf.st_mode & S_IEXEC)) { X addstatus("Cannot write in folder", true); X return((folder) NULL); X } X /* make a new folder record, insert it */ X fnew = NEW(mail_folder); X fnew->name = NEWSTR(strlen(str)+1); X strcpy(fnew->name, str); X fnew->mail = fnew->last = (item) NULL; X fnew->next = fnew->prev = (folder) NULL; X fnew->pages = fnew->pagenum = 1; X fnew->valid = false; X if(p == (folder) NULL) { X fnew->next = folders; X folders->prev = fnew; X folders = fnew; X } else { X p->next = fnew; X if(f != (folder) NULL) X f->prev = fnew; X fnew->prev = p; X fnew->next = f; X } X return(fnew); X} X X X/* -------------------- X Crude undo. Sophisticated undo rather too painful to code. X-------------------- */ Xvoid Xundo() X{ X folder f = deleted.flr, p; X char s1[LEN], s2[LEN]; X X if(f == (folder) NULL) { X addstatus("nothing to undo", true); X return; X } else { X addstatus("undoing ...", true); X deleted.flr = (folder) NULL; X } X sprintf(s1, "%s/%s/#%d", mail_dir, f->name, deleted.number); X sprintf(s2, "%s/%s/%d", mail_dir, f->name, deleted.number); X rename(s1, s2); X /* find first page of folder */ X p = f; FRST_OF_NAME(p); X /* find last page of folder */ X LAST_OF_NAME(f); X curflr = p; X p->next = f->next; X/* should free old folder/mail records */ X/* p->valid = false; */ X p->mail = p->last = (item) NULL; X p->pagenum = p->pages = 1; X if(p->next != (folder) NULL) X p->next->prev = p; X find_mail(curflr, false); X curmail = curflr->mail; X y = FIRST; X display_page(); X} X X X/* -------------------- X Pack current folder. Unsets "deleted" if last removed record was X on current folder. X-------------------- */ Xvoid Xpack_folder() X{ X folder f = curflr; X item m; X char path[LEN], s1[LEN], s2[LEN]; X bool found; X int newnum = 1; X X addstatus("Packing folder ...", false); X FRST_OF_NAME(f); X /* unset undo if packing that folder */ X if(deleted.flr != (folder) NULL && deleted.flr->name == curflr->name) X deleted.flr = (folder) NULL; X sprintf(path, "%s/%s/", mail_dir, curflr->name); X for( ; f != (folder) NULL && f->name == curflr->name ; f=f->next) X for(m=f->mail ; m != (item) NULL ; m=m->next) { X for(found=false ; !found && newnum < m->number ; ) { X sprintf(s1, "%s%d", path, newnum); X if(! access(s1, R_OK)) X newnum++; X else X found = true; X } X if(found) { X sprintf(s2, "%s%d", path, m->number); X rename(s2, s1); X m->number = newnum; X } X } X display_page(); X} !The!End! echo x - "page.c" 2>&1 sed "s/^X//" >"page.c" <<'!The!End!' X/* -------------------- X vmail -- page.c X X Routines for paging between folders. X X Copyright (C) J. Zobel, University of Melbourne, October 1987. X-------------------- */ X X#include "defs.h" X X/* -------------------- X Clear screen, show current page of mail items. X-------------------- */ Xvoid Xdisplay_page() X{ X int i; X item mail; X char str[LEN]; X X clear(); X add_page_header(str); X for(i=FIRST, mail=curflr->mail ; mail != (item) NULL ; i++, mail=mail->next) X show_title(str, i, mail); X move(y, 0); X refresh(); X} X X X/* -------------------- X Find previous valid page of mail items (times count). X-------------------- */ Xvoid Xprev_page(count, display) X int count; X bool display; X{ X bool show = false; X folder f, p = curflr; X X for( ; count > 0 ; count--) { X f = curflr->prev; PREV_VALID(f); X if(f == (folder) NULL) X break; X else { X show = true; X y = FIRST; X curflr = f; X curmail = curflr->mail; X } X } X if(p != curflr) X alternate = p; X if(display) { X if(show) X display_page(); X if(f == (folder) NULL) X addstatus("first active page", true); X } X} X X X/* -------------------- X Find next valid page of mail items (times count). X-------------------- */ Xvoid Xnext_page(count, display) X int count; X bool display; X{ X bool show = false; X folder f, p = curflr; X X for( ; count > 0 ; count--) { X f = curflr->next; NEXT_VALID(f); X if(f == (folder) NULL) X break; X else { X show = true; X y = FIRST; X curflr = f; X curmail = curflr->mail; X } X } X if(p != curflr) X alternate = p; X if(display) { X if(show) X display_page(); X if(f == (folder) NULL) X addstatus("last active page", true); X } X} X X X/* -------------------- X Move cursor up count lines. X-------------------- */ Xvoid Xcursor_up(count) X int count; X{ X bool redraw = false; X X addstatus("", false); /* clear status line */ X for( ; count > 0 ; count--) X if(curmail->prev == (item) NULL) { X if(curflr->prev == (folder) NULL || X curflr->name != curflr->prev->name) { X addstatus("first page of folder", true); X break; X } else { X redraw = true; X prev_page(1, false); X /* find last mail item on page */ X for( ; curmail->next != (item) NULL ; y++) X curmail=curmail->next; X } X } else { X y--; X curmail = curmail->prev; X } X if(redraw) X display_page(); X else { X move(y, 0); X refresh(); X } X} X X X/* -------------------- X Move cursor down count lines. X-------------------- */ Xvoid Xcursor_down(count) X int count; X{ X bool redraw = false; X X addstatus("", false); /* clear status line */ X for( ; count > 0 ; count--) X if(curmail->next == (item) NULL) { X if(curflr->next == (folder) NULL || X curflr->name != curflr->next->name) { X addstatus("last page of folder", true); X break; X } else { X redraw = true; X next_page(1, false); X } X } else { X y++; X curmail = curmail->next; X } X if(redraw) X display_page(); X else { X move(y, 0); X refresh(); X } X} X X X/* -------------------- X Go to next folder, make active if not so already. X-------------------- */ Xvoid Xgoto_next_folder(count) X int count; X{ X bool changed = false; X folder f, p = curflr; X X for( ; count > 0 ; count--) { X /* skip other pages of current folder */ X for(f=p ; p->next->name == f->name ; p=p->next) X ; X if(p->next == (folder) NULL) { X addstatus("last folder", true); X break; X } else { X changed = true; X p = p->next; X } X } X if(changed) { X do { X if(!p->valid) { X p->valid = true; X addstatus("reading mail headers ...", true); X find_mail(p, false); X } X if(p->valid == EMPTY) X p = p->next; X } while(p != (folder) NULL && p->valid != true); X if(p == (folder) NULL) X addstatus("can't go forward that many folders", true); X else { X alternate = curflr; X curflr = p; X curmail = p->mail; X y = FIRST; X display_page(); X } X } X} X X X/* -------------------- X Goto previous folder, make active if not so already. X-------------------- */ Xvoid Xgoto_prev_folder(count) X int count; X{ X bool changed = false; X folder f, p = curflr; X X for( ; count > 0 ; count--) { X /* skip other pages of current folder */ X for(f=p ; p->prev->name == f->name ; p=p->prev) X ; X if(curflr->prev == (folder) NULL) { X addstatus("first folder", true); X break; X } else { X changed = true; X p = p->prev; X } X } X if(changed) { X do { X if(!p->valid) { X p->valid = true; X addstatus("reading mail headers ...", true); X find_mail(p, false); X } X if(p->valid == EMPTY) X p = p->prev; X } while(p != (folder) NULL && p->valid != true); X if(p == (folder) NULL) X addstatus("can't go back that many folders", true); X else { X /* skip other pages of current folder */ X for(f=p ; p->prev->name == f->name ; p=p->prev) X ; X alternate = curflr; X curflr = p; X curmail = p->mail; X y = FIRST; X display_page(); X } X } X} X X X/* -------------------- X Make new folder current as specified - read from terminal (get_name = X true) or use value in alternate (get_name = false). X-------------------- */ Xvoid Xgoto_folder(get_name) X bool get_name; X{ X char buf[LEN], *str = buf; X folder f; X int i; X X if(get_name || alternate == (folder) NULL) { X /* no alternate folder */ X get_string("folder? ", str); X for(i=0 ; *str == ' ' ; i++, str++) X ; X for(; i < LEN && buf[i] != ' ' && buf[i] != '\0' ; i++) X ; X if(i < LEN) X buf[i] = '\0'; X GOTO_NAME(f, str); X if(f == (folder) NULL) { X addstatus("no such folder", true); X return; X } X if(!f->valid) { X f->valid = true; X addstatus("reading mail headers ...", true); X find_mail(f, false); X if(f->valid == EMPTY) { X sprintf(str, "%s -- folder empty", f->name); X addstatus(str, true); X return; X } X } X } else X f = alternate; X if(f == curflr) X addstatus("already in that folder", true); X else { X alternate = curflr; X curflr = f; X curmail = f->mail; X y = FIRST; X display_page(); X } X} X X X/* -------------------- X Go to first line of page, as offset by count. X-------------------- */ Xvoid Xcursor_first(count) X int count; X{ X item m = curflr->mail; X X addstatus("", false); /* clear status line */ X for(y=FIRST ; m != (item) NULL && count > 1 ; count--, y++, m=m->next) X ; X if(m == (item) NULL) X beep(); X else { X curmail = m; X move(y, 0); X refresh(); X } X} X X X/* -------------------- X Go to middle line of page. X-------------------- */ Xvoid Xcursor_middle() X{ X item m, p; X bool tog = false; X X addstatus("", false); /* clear status line */ X /* find middle of page - p traverses page at half speed */ X for(y=FIRST, m=p=curflr->mail ; m != (item) NULL ; m=m->next, tog= !tog) X if(tog) { X y++; X p = p->next; X } X curmail = p; X move(y, 0); X refresh(); X} X X X/* -------------------- X Go to last line of page, as offset by count. X-------------------- */ Xvoid Xcursor_last(count) X int count; X{ X item m = curflr->mail; X X addstatus("", false); /* clear status line */ X /* find last item, including y-value */ X for(y=FIRST ; m->next != (item) NULL ; y++, m=m->next) X ; X for(; m != (item) NULL && count > 1 ; count--, y--, m=m->prev) X ; X if(m == (item) NULL) X beep(); X else { X curmail = m; X move(y, 0); X refresh(); X } X} X X X/* -------------------- X Go to first page. ***should use count as offset*** X-------------------- */ Xvoid Xgoto_first_page() X{ X folder f; X X f = folders; NEXT_VALID(f); X if(f == (folder) NULL) { X /* no valid folders - bleah?! */ X addstatus("no valid folders, exiting", true); X to_normal(); X exit(1); X } else if(f != curflr) { X alternate = curflr; X curflr = f; X curmail = f->mail; X y = FIRST; X display_page(); X } X} X X X/* -------------------- X Go to last page. ***should use count as offset*** X-------------------- */ Xvoid Xgoto_last_page() X{ X folder f, p; X X for(f=folders ; f != (folder) NULL ; f=f->next) X if(f->valid) X p = f; X if(p == (folder) NULL) { X /* no valid folders - bleah?! */ X addstatus("no valid folders, exiting", true); X to_normal(); X exit(1); X } else if(p != curflr) { X alternate = curflr; X curflr = p; X curmail = p->mail; X y = FIRST; X display_page(); X } X} !The!End! exit