Add mail (John Cummings)

This commit is contained in:
rsc 2005-10-29 16:21:34 +00:00
parent a078ffc8ab
commit 9f1fdc1287
11 changed files with 3588 additions and 0 deletions

172
src/cmd/acme/mail/dat.h Normal file
View file

@ -0,0 +1,172 @@
typedef struct Event Event;
typedef struct Exec Exec;
typedef struct Message Message;
typedef struct Window Window;
enum
{
STACK = 8192,
EVENTSIZE = 256,
NEVENT = 5,
};
struct Event
{
int c1;
int c2;
int q0;
int q1;
int flag;
int nb;
int nr;
char b[EVENTSIZE*UTFmax+1];
Rune r[EVENTSIZE+1];
};
struct Window
{
/* file descriptors */
/* jpc int ctl;
int event;
int addr;
int data;
Biobuf *body; jpc */
CFid* ctl;
CFid* event;
CFid* addr;
CFid* data;
CFid* body;
/* event input */
char buf[512];
char *bufp;
int nbuf;
Event e[NEVENT];
int id;
int open;
Channel *cevent;
};
struct Message
{
Window *w;
int ctlfd;
char *name;
char *replyname;
uchar opened;
uchar dirty;
uchar isreply;
uchar deleted;
uchar writebackdel;
uchar tagposted;
uchar recursed;
uchar level;
/* header info */
char *fromcolon; /* from header file; all rest are from info file */
char *from;
char *to;
char *cc;
char *replyto;
char *date;
char *subject;
char *type;
char *disposition;
char *filename;
char *digest;
Message *next; /* next in this mailbox */
Message *prev; /* prev in this mailbox */
Message *head; /* first subpart */
Message *tail; /* last subpart */
};
enum
{
NARGS = 100,
NARGCHAR = 8*1024,
EXECSTACK = STACK+(NARGS+1)*sizeof(char*)+NARGCHAR
};
struct Exec
{
char *prog;
char **argv;
int p[2]; /* p[1] is write to program; p[0] set to prog fd 0*/
int q[2]; /* q[0] is read from program; q[1] set to prog fd 1 */
Channel *sync;
};
extern Window* newwindow(void);
extern int winopenfile(Window*, char*);
extern CFid* winopenfid(Window*, char*);
extern void winopenbody(Window*, int);
extern void winclosebody(Window*);
extern void wintagwrite(Window*, char*, int);
extern void winname(Window*, char*);
extern void winwriteevent(Window*, Event*);
extern void winread(Window*, uint, uint, char*);
extern int windel(Window*, int);
extern void wingetevent(Window*, Event*);
extern void wineventproc(void*);
extern void winwritebody(Window*, char*, int);
extern void winclean(Window*);
extern int winselect(Window*, char*, int);
extern char* winselection(Window*);
extern int winsetaddr(Window*, char*, int);
extern char* winreadbody(Window*, int*);
extern void windormant(Window*);
extern void winsetdump(Window*, char*, char*);
extern void readmbox(Message*, char*, char*);
extern void rewritembox(Window*, Message*);
extern void mkreply(Message*, char*, char*, Plumbattr*, char*);
extern void delreply(Message*);
extern int mesgadd(Message*, char*, Dir*, char*);
extern void mesgmenu(Window*, Message*);
extern void mesgmenunew(Window*, Message*);
extern int mesgopen(Message*, char*, char*, Message*, int, char*);
extern void mesgctl(void*);
extern void mesgsend(Message*);
extern void mesgdel(Message*, Message*);
extern void mesgmenudel(Window*, Message*, Message*);
extern void mesgmenumark(Window*, char*, char*);
extern void mesgmenumarkdel(Window*, Message*, Message*, int);
extern Message* mesglookup(Message*, char*, char*);
extern Message* mesglookupfile(Message*, char*, char*);
extern void mesgfreeparts(Message*);
extern char* readfile(char*, char*, int*);
extern char* readbody(char*, char*, int*);
// jpc extern void ctlprint(int, char*, ...);
extern void ctlprint(CFid*, char*, ...);
extern void* emalloc(uint);
extern void* erealloc(void*, uint);
extern char* estrdup(char*);
extern char* estrstrdup(char*, char*);
extern char* egrow(char*, char*, char*);
extern char* eappend(char*, char*, char*);
extern void error(char*, ...);
extern int tokenizec(char*, char**, int, char*);
extern void execproc(void*);
extern int fsprint(CFid*, char*, ...);
#pragma varargck argpos error 1
#pragma varargck argpos ctlprint 2
extern Window *wbox;
extern Message mbox;
extern Message replies;
extern char *fsname;
extern int plumbsendfd;
extern int plumbseemailfd;
extern char *home;
extern char *outgoing;
extern char *mailboxdir;
extern char *user;
extern char deleted[];
extern int wctlfd;
extern int shortmenu;

4
src/cmd/acme/mail/guide Normal file
View file

@ -0,0 +1,4 @@
Mail stored
plumb /mail/box/$user/names
mail -'x' someaddress
mkbox /mail/box/$user/new_box

76
src/cmd/acme/mail/html.c Normal file
View file

@ -0,0 +1,76 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <ctype.h>
#include <plumb.h>
#include <9pclient.h>
#include "dat.h"
char*
formathtml(char *body, int *np)
{
int i, j, p[2], q[2];
Exec *e;
char buf[1024];
Channel *sync;
e = emalloc(sizeof(struct Exec));
if(pipe(p) < 0 || pipe(q) < 0)
error("can't create pipe: %r");
e->p[0] = p[0];
e->p[1] = p[1];
e->q[0] = q[0];
e->q[1] = q[1];
e->argv = emalloc(3*sizeof(char*));
e->argv[0] = estrdup("htmlfmt");
e->argv[1] = estrdup("-cutf-8");
e->argv[2] = nil;
e->prog = unsharp("#9/bin/htmlfmt");
sync = chancreate(sizeof(int), 0);
e->sync = sync;
proccreate(execproc, e, EXECSTACK);
recvul(sync);
// close(p[0]);
close(q[1]);
if((i=write(p[1], body, *np)) != *np){
fprint(2, "Mail: warning: htmlfmt failed: wrote %d of %d: %r\n", i, *np);
close(p[1]);
close(q[0]);
return body;
}
close(p[1]);
free(body);
body = nil;
i = 0;
for(;;){
j = read(q[0], buf, sizeof buf);
if(j <= 0)
break;
body = realloc(body, i+j+1);
if(body == nil)
error("realloc failed: %r");
memmove(body+i, buf, j);
i += j;
body[i] = '\0';
}
close(q[0]);
*np = i;
return body;
}
char*
readbody(char *type, char *dir, int *np)
{
char *body;
body = readfile(dir, "body", np);
if(body != nil && strcmp(type, "text/html") == 0)
return formathtml(body, np);
return body;
}

571
src/cmd/acme/mail/mail.c Normal file
View file

@ -0,0 +1,571 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <plumb.h>
#include <ctype.h>
#include <9pclient.h> /* jpc */
#include "dat.h"
char *maildir = "/mail/fs/"; /* mountpoint of mail file system */
char *mailtermdir = "/mnt/term/mail/fs/"; /* alternate mountpoint */
char *mboxname = "mbox"; /* mailboxdir/mboxname is mail spool file */
char *mailboxdir = nil; /* nil == /mail/box/$user */
char *fsname; /* filesystem for mailboxdir/mboxname is at maildir/fsname */
char *user;
char *outgoing;
Window *wbox;
Message mbox;
Message replies;
char *home;
int plumbsendfd;
int plumbseemailfd;
int plumbshowmailfd;
int plumbsendmailfd;
Channel *cplumb;
Channel *cplumbshow;
Channel *cplumbsend;
int wctlfd;
void mainctl(void*);
void plumbproc(void*);
void plumbshowproc(void*);
void plumbsendproc(void*);
void plumbthread(void);
void plumbshowthread(void*);
void plumbsendthread(void*);
int shortmenu;
CFsys *upasfs; /*jpc */
CFsys *acmefs; /*jpc */
void
usage(void)
{
fprint(2, "usage: Mail [-sS] [-o outgoing] [mailboxname [directoryname]]\n");
threadexitsall("usage");
}
void
removeupasfs(void)
{
char buf[256];
if(strcmp(mboxname, "mbox") == 0)
return;
snprint(buf, sizeof buf, "close %s", mboxname);
write(mbox.ctlfd, buf, strlen(buf));
}
int
ismaildir(char *s)
{
char buf[256];
Dir *d;
int ret;
snprint(buf, sizeof buf, "%s%s", maildir, s);
d = dirstat(buf);
if(d == nil)
return 0;
ret = d->qid.type & QTDIR;
free(d);
return ret;
}
void
threadmain(int argc, char *argv[])
{
char *s, *name;
char err[ERRMAX], *cmd;
int i, newdir;
Fmt fmt;
doquote = needsrcquote;
quotefmtinstall();
/* open these early so we won't miss notification of new mail messages while we read mbox */
plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
plumbseemailfd = plumbopen("seemail", OREAD|OCEXEC);
plumbshowmailfd = plumbopen("showmail", OREAD|OCEXEC);
/* jpc */
acmefs = nsmount("acme",nil);
upasfs = nsmount("upasfs", nil);
/* jpc end */
shortmenu = 0;
ARGBEGIN{
case 's':
shortmenu = 1;
break;
case 'S':
shortmenu = 2;
break;
case 'o':
outgoing = EARGF(usage());
break;
case 'm':
smprint(maildir, "%s/", EARGF(usage()));
break;
default:
usage();
}ARGEND
name = "mbox";
/* bind the terminal /mail/fs directory over the local one */
if(access(maildir, 0)<0 && access(mailtermdir, 0)==0) {
/* jpc - bind(mailtermdir, maildir, MAFTER); */
fprint(2,"jpc: trying to bind(mailtermdir, maildir, MAFTER)\n");
}
newdir = 1;
if(argc > 0){
i = strlen(argv[0]);
if(argc>2 || i==0)
usage();
/* see if the name is that of an existing /mail/fs directory */
if(argc==1 && strchr(argv[0], '/')==0 && ismaildir(argv[0])){
name = argv[0];
mboxname = eappend(estrdup(maildir), "", name);
newdir = 0;
}else{
if(argv[0][i-1] == '/')
argv[0][i-1] = '\0';
s = strrchr(argv[0], '/');
if(s == nil)
mboxname = estrdup(argv[0]);
else{
*s++ = '\0';
if(*s == '\0')
usage();
mailboxdir = argv[0];
mboxname = estrdup(s);
}
if(argc > 1)
name = argv[1];
else
name = mboxname;
}
}
user = getenv("user");
if(user == nil)
user = "none";
if(mailboxdir == nil)
mailboxdir = estrstrdup(unsharp("#9/mail/box/"), user);
if(outgoing == nil)
outgoing = estrstrdup(mailboxdir, "/outgoing");
// s = estrstrdup(maildir, "ctl");
mbox.ctlfd = fsopenfd(upasfs,"ctl", ORDWR|OCEXEC);
if(mbox.ctlfd < 0)
error("can't open %s: %r\n", s);
fsname = estrdup(name);
if(newdir && argc > 0){
s = emalloc(5+strlen(mailboxdir)+strlen(mboxname)+strlen(name)+10+1);
for(i=0; i<10; i++){
sprint(s, "open %s/%s %s", mailboxdir, mboxname, fsname);
if(write(mbox.ctlfd, s, strlen(s)) >= 0)
break;
err[0] = '\0';
errstr(err, sizeof err);
if(strstr(err, "mbox name in use") == nil)
error("can't create directory %s for mail: %s\n", name, err);
free(fsname);
fsname = emalloc(strlen(name)+10);
sprint(fsname, "%s-%d", name, i);
}
if(i == 10)
error("can't open %s/%s: %r", mailboxdir, mboxname);
free(s);
}
s = estrstrdup(fsname, "/");
mbox.name = estrstrdup(maildir, s);
// mbox.name = "/mail/fs/mbox/";
mbox.level= 0;
readmbox(&mbox, maildir, s);
home = getenv("home");
if(home == nil)
home = "/";
wbox = newwindow();
winname(wbox, mbox.name);
wintagwrite(wbox, "Put Mail Delmesg ", 3+1+4+1+7+1);
threadcreate(mainctl, wbox, STACK);
fmtstrinit(&fmt);
fmtprint(&fmt, "Mail");
if(shortmenu)
fmtprint(&fmt, " -%c", "sS"[shortmenu-1]);
if(outgoing)
fmtprint(&fmt, " -o %s", outgoing);
fmtprint(&fmt, " %s", name);
cmd = fmtstrflush(&fmt);
if(cmd == nil)
sysfatal("out of memory");
winsetdump(wbox, "/acme/mail", cmd);
mbox.w = wbox;
mesgmenu(wbox, &mbox);
// sleep(100);
winclean(wbox);
wctlfd = open("/dev/wctl", OWRITE|OCEXEC); /* for acme window */
cplumb = chancreate(sizeof(Plumbmsg*), 0);
cplumbshow = chancreate(sizeof(Plumbmsg*), 0);
if(strcmp(name, "mbox") == 0){
/*
* Avoid creating multiple windows to send mail by only accepting
* sendmail plumb messages if we're reading the main mailbox.
*/
plumbsendmailfd = plumbopen("sendmail", OREAD|OCEXEC);
cplumbsend = chancreate(sizeof(Plumbmsg*), 0);
proccreate(plumbsendproc, nil, STACK);
threadcreate(plumbsendthread, nil, STACK);
}
/* start plumb reader as separate proc ... */
proccreate(plumbproc, nil, STACK);
proccreate(plumbshowproc, nil, STACK);
threadcreate(plumbshowthread, nil, STACK);
/* ... and use this thread to read the messages */
plumbthread();
}
void
plumbproc(void* v)
{
Plumbmsg *m;
threadsetname("plumbproc");
for(;;){
m = plumbrecv(plumbseemailfd);
sendp(cplumb, m);
if(m == nil)
threadexits(nil);
}
}
void
plumbshowproc(void* v)
{
Plumbmsg *m;
threadsetname("plumbshowproc");
for(;;){
m = plumbrecv(plumbshowmailfd);
sendp(cplumbshow, m);
if(m == nil)
threadexits(nil);
}
}
void
plumbsendproc(void* v)
{
Plumbmsg *m;
threadsetname("plumbsendproc");
for(;;){
m = plumbrecv(plumbsendmailfd);
sendp(cplumbsend, m);
if(m == nil)
threadexits(nil);
}
}
void
newmesg(char *name, char *digest)
{
Dir *d;
char* tmp;
if(strncmp(name, mbox.name, strlen(mbox.name)) != 0) {
return; /* message is about another mailbox */
}
if(mesglookupfile(&mbox, name, digest) != nil) {
return;
}
if (strncmp(name,"/mail/fs/",strlen("/mail/fs/"))==0) {
tmp = name+strlen("/mail/fs/");
}
d = fsdirstat(upasfs,tmp);
if(d == nil) {
return;
}
if(mesgadd(&mbox, mbox.name, d, digest)) {
mesgmenunew(wbox, &mbox);
}
free(d);
}
void
showmesg(char *name, char *digest)
{
char *n;
if(strncmp(name, mbox.name, strlen(mbox.name)) != 0)
return; /* message is about another mailbox */
n = estrdup(name+strlen(mbox.name));
if(n[strlen(n)-1] != '/')
n = egrow(n, "/", nil);
mesgopen(&mbox, mbox.name, name+strlen(mbox.name), nil, 1, digest);
free(n);
}
void
delmesg(char *name, char *digest, int dodel)
{
Message *m;
m = mesglookupfile(&mbox, name, digest);
if(m != nil){
mesgmenumarkdel(wbox, &mbox, m, 0);
if(dodel)
m->writebackdel = 1;
}
}
void
plumbthread(void)
{
Plumbmsg *m;
Plumbattr *a;
char *type, *digest;
threadsetname("plumbthread");
while((m = recvp(cplumb)) != nil){
a = m->attr;
digest = plumblookup(a, "digest");
type = plumblookup(a, "mailtype");
if(type == nil)
fprint(2, "Mail: plumb message with no mailtype attribute\n");
else if(strcmp(type, "new") == 0)
newmesg(m->data, digest);
else if(strcmp(type, "delete") == 0)
delmesg(m->data, digest, 0);
else
fprint(2, "Mail: unknown plumb attribute %s\n", type);
plumbfree(m);
}
threadexits(nil);
}
void
plumbshowthread(void* v)
{
Plumbmsg *m;
threadsetname("plumbshowthread");
while((m = recvp(cplumbshow)) != nil){
showmesg(m->data, plumblookup(m->attr, "digest"));
plumbfree(m);
}
threadexits(nil);
}
void
plumbsendthread(void* v)
{
Plumbmsg *m;
threadsetname("plumbsendthread");
while((m = recvp(cplumbsend)) != nil){
mkreply(nil, "Mail", m->data, m->attr, nil);
plumbfree(m);
}
threadexits(nil);
}
int
mboxcommand(Window *w, char *s)
{
char *args[10], **targs;
Message *m, *next;
int ok, nargs, i, j;
char buf[128];
nargs = tokenize(s, args, nelem(args));
if(nargs == 0)
return 0;
if(strcmp(args[0], "Mail") == 0){
if(nargs == 1)
mkreply(nil, "Mail", "", nil, nil);
else
mkreply(nil, "Mail", args[1], nil, nil);
return 1;
}
if(strcmp(s, "Del") == 0){
if(mbox.dirty){
mbox.dirty = 0;
fprint(2, "mail: mailbox not written\n");
return 1;
}
ok = 1;
for(m=mbox.head; m!=nil; m=next){
next = m->next;
if(m->w){
if(windel(m->w, 0))
m->w = nil;
else
ok = 0;
}
}
for(m=replies.head; m!=nil; m=next){
next = m->next;
if(m->w){
if(windel(m->w, 0))
m->w = nil;
else
ok = 0;
}
}
if(ok){
windel(w, 1);
removeupasfs();
threadexitsall(nil);
}
return 1;
}
if(strcmp(s, "Put") == 0){
rewritembox(wbox, &mbox);
return 1;
}
if(strcmp(s, "Delmesg") == 0){
if(nargs > 1){
for(i=1; i<nargs; i++){
snprint(buf, sizeof buf, "%s%s", mbox.name, args[i]);
delmesg(buf, nil, 1);
}
}
s = winselection(w);
if(s == nil)
return 1;
nargs = 1;
for(i=0; s[i]; i++)
if(s[i] == '\n')
nargs++;
targs = emalloc(nargs*sizeof(char*)); /* could be too many for a local array */
nargs = getfields(s, targs, nargs, 1, "\n");
for(i=0; i<nargs; i++){
if(!isdigit(targs[i][0]))
continue;
j = atoi(targs[i]); /* easy way to parse the number! */
if(j == 0)
continue;
snprint(buf, sizeof buf, "%s%d", mbox.name, j);
delmesg(buf, nil, 1);
}
free(s);
free(targs);
return 1;
}
return 0;
}
void
mainctl(void *v)
{
Window *w;
Event *e, *e2, *eq, *ea;
int na, nopen;
char *s, *t, *buf;
w = v;
proccreate(wineventproc, w, STACK);
for(;;){
e = recvp(w->cevent);
switch(e->c1){
default:
Unknown:
print("unknown message %c%c\n", e->c1, e->c2);
break;
case 'E': /* write to body; can't affect us */
break;
case 'F': /* generated by our actions; ignore */
break;
case 'K': /* type away; we don't care */
break;
case 'M':
switch(e->c2){
case 'x':
case 'X':
ea = nil;
e2 = nil;
if(e->flag & 2)
e2 = recvp(w->cevent);
if(e->flag & 8){
ea = recvp(w->cevent);
na = ea->nb;
recvp(w->cevent);
}else
na = 0;
s = e->b;
/* if it's a known command, do it */
if((e->flag&2) && e->nb==0)
s = e2->b;
if(na){
t = emalloc(strlen(s)+1+na+1);
sprint(t, "%s %s", s, ea->b);
s = t;
}
/* if it's a long message, it can't be for us anyway */
if(!mboxcommand(w, s)) /* send it back */
winwriteevent(w, e);
if(na)
free(s);
break;
case 'l':
case 'L':
buf = nil;
eq = e;
if(e->flag & 2){
e2 = recvp(w->cevent);
eq = e2;
}
s = eq->b;
if(eq->q1>eq->q0 && eq->nb==0){
buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
winread(w, eq->q0, eq->q1, buf);
s = buf;
}
nopen = 0;
do{
/* skip 'deleted' string if present' */
if(strncmp(s, deleted, strlen(deleted)) == 0)
s += strlen(deleted);
/* skip mail box name if present */
if(strncmp(s, mbox.name, strlen(mbox.name)) == 0)
s += strlen(mbox.name);
nopen += mesgopen(&mbox, mbox.name, s, nil, 0, nil);
while(*s!='\0' && *s++!='\n')
;
}while(*s);
if(nopen == 0) /* send it back */
winwriteevent(w, e);
free(buf);
break;
case 'I': /* modify away; we don't care */
case 'D':
case 'd':
case 'i':
break;
default:
goto Unknown;
}
}
}
}

1444
src/cmd/acme/mail/mesg.c Normal file

File diff suppressed because it is too large Load diff

11
src/cmd/acme/mail/mkbox Normal file
View file

@ -0,0 +1,11 @@
#!/bin/rc
for(i){
if(! test -f $i){
if(cp /dev/null $i){
chmod 600 $i
chmod +al $i
}
}
if not echo $i already exists
}

32
src/cmd/acme/mail/mkfile Normal file
View file

@ -0,0 +1,32 @@
<$PLAN9/src/mkhdr
CC=9c
TARG=Mail
OFILES=\
html.$O\
mail.$O\
mesg.$O\
reply.$O\
util.$O\
win.$O
HFILES=dat.h
LIB=
BIN=/acme/bin/$objtype
UPDATE=\
mkfile\
$HFILES\
${OFILES:%.$O=%.c}\
<$PLAN9/src/mkone
$O.out: $OFILES
$LD -o $target $LDFLAGS $OFILES
syms:V:
8c -a mail.c >syms
8c -aa mesg.c reply.c util.c win.c >>syms

57
src/cmd/acme/mail/readme Normal file
View file

@ -0,0 +1,57 @@
The Acme Mail program uses upas/fs to parse the mail box, and then
presents a file-browser-like user interface to reading and sending
messages. The Mail window presents each numbered message like the
contents of a directory presented one per line. If a message has a
Subject: line, that is shown indented on the following line.
Multipart MIME-encoded messages are presented in the obvious
hierarchical format.
Mail uses upas/fs to access the mail box. By default it reads "mbox",
the standard user mail box. If Mail is given an argument, it is
passed to upas/fs as the name of the mail box (or upas/fs directory)
to open.
Although Mail works if the plumber is not running, it's designed to be
run with plumbing enabled and many of its features work best if it is.
The mailbox window has a few commands: Put writes back the mailbox;
Mail creates a new window in which to compose a message; and Delmesg
deletes messages by number. The number may be given as argument or
indicated by selecting the header line in the mailbox window.
(Delmesg does not expand null selections, in the interest of safety.)
Clicking the right button on a message number opens it; clicking on
any of the subparts of a message opens that (and also opens the
message itself). Each message window has a few commands in the tag
with obvious names: Reply, Delmsg, etc. "Reply" replies to the single
sender of the message, "Reply all" or "Replyall" replies to everyone
in the From:, To:, and CC: lines.
Message parts with recognized MIME types such as image/jpeg are sent
to the plumber for further dispatch. Acme Mail also listens to
messages on the seemail and showmail plumbing ports, to report the
arrival of new messages (highlighting the entry; right-click on the
entry to open the message) and open them if you right-click on the
face in the faces window.
When composing a mail message or replying to a message, the first line
of the text is a list of recipients of the message. To:, and CC:, and BCC:
lines are interpreted in the usual way. Two other header lines are
special to Acme Mail:
Include: file places a copy of file in the message as an
inline MIME attachment.
Attach: file places a copy of file in the message as a regular
MIME attachment.
Acme Mail uses these conventions when replying to messages,
constructing headers for the default behavior. You may edit these to
change behavior. Most important, when replying to a message Mail will
always Include: the original message; delete that line if you don't
want to include it.
If the mailbox
/mail/box/$user/outgoing
exists, Acme Mail will save your a copy of your outgoing messages
there. Attachments are described in the copy but not included.
The -m mntpoint flag specifies a different mount point for /upas/fs.

654
src/cmd/acme/mail/reply.c Normal file
View file

@ -0,0 +1,654 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <ctype.h>
#include <plumb.h>
#include <9pclient.h>
#include "dat.h"
static int replyid;
int
quote(Message *m, CFid *fid, char *dir, char *quotetext)
{
char *body, *type;
int i, n, nlines;
char **lines;
if(quotetext){
body = quotetext;
n = strlen(body);
type = nil;
}else{
/* look for first textual component to quote */
type = readfile(dir, "type", &n);
if(type == nil){
print("no type in %s\n", dir);
return 0;
}
if(strncmp(type, "multipart/", 10)==0 || strncmp(type, "message/", 8)==0){
dir = estrstrdup(dir, "1/");
if(quote(m, fid, dir, nil)){
free(type);
free(dir);
return 1;
}
free(dir);
}
if(strncmp(type, "text", 4) != 0){
free(type);
return 0;
}
body = readbody(m->type, dir, &n);
if(body == nil)
return 0;
}
nlines = 0;
for(i=0; i<n; i++)
if(body[i] == '\n')
nlines++;
nlines++;
lines = emalloc(nlines*sizeof(char*));
nlines = getfields(body, lines, nlines, 0, "\n");
/* delete leading and trailing blank lines */
i = 0;
while(i<nlines && lines[i][0]=='\0')
i++;
while(i<nlines && lines[nlines-1][0]=='\0')
nlines--;
while(i < nlines){
fsprint(fid, ">%s%s\n", lines[i][0]=='>'? "" : " ", lines[i]);
i++;
}
free(lines);
free(body); /* will free quotetext if non-nil */
free(type);
return 1;
}
#if 0 /* jpc */
int
quote(Message *m, Biobuf *b, char *dir, char *quotetext)
{
char *body, *type;
int i, n, nlines;
char **lines;
if(quotetext){
body = quotetext;
n = strlen(body);
type = nil;
}else{
/* look for first textual component to quote */
type = readfile(dir, "type", &n);
if(type == nil){
print("no type in %s\n", dir);
return 0;
}
if(strncmp(type, "multipart/", 10)==0 || strncmp(type, "message/", 8)==0){
dir = estrstrdup(dir, "1/");
if(quote(m, b, dir, nil)){
free(type);
free(dir);
return 1;
}
free(dir);
}
if(strncmp(type, "text", 4) != 0){
free(type);
return 0;
}
body = readbody(m->type, dir, &n);
if(body == nil)
return 0;
}
nlines = 0;
for(i=0; i<n; i++)
if(body[i] == '\n')
nlines++;
nlines++;
lines = emalloc(nlines*sizeof(char*));
nlines = getfields(body, lines, nlines, 0, "\n");
/* delete leading and trailing blank lines */
i = 0;
while(i<nlines && lines[i][0]=='\0')
i++;
while(i<nlines && lines[nlines-1][0]=='\0')
nlines--;
while(i < nlines){
Bprint(b, ">%s%s\n", lines[i][0]=='>'? "" : " ", lines[i]);
i++;
}
free(lines);
free(body); /* will free quotetext if non-nil */
free(type);
return 1;
}
#endif
void
mkreply(Message *m, char *label, char *to, Plumbattr *attr, char *quotetext)
{
Message *r;
char *dir, *t;
int quotereply;
Plumbattr *a;
quotereply = (label[0] == 'Q');
r = emalloc(sizeof(Message));
r->isreply = 1;
if(m != nil)
r->replyname = estrdup(m->name);
r->next = replies.head;
r->prev = nil;
if(replies.head != nil)
replies.head->prev = r;
replies.head = r;
if(replies.tail == nil)
replies.tail = r;
r->name = emalloc(strlen(mbox.name)+strlen(label)+10);
sprint(r->name, "%s%s%d", mbox.name, label, ++replyid);
r->w = newwindow();
winname(r->w, r->name);
ctlprint(r->w->ctl, "cleartag");
wintagwrite(r->w, "fmt Look Post Undo", 4+5+5+4);
r->tagposted = 1;
threadcreate(mesgctl, r, STACK);
winopenbody(r->w, OWRITE);
if(to!=nil && to[0]!='\0') {
// Bprint(r->w->body, "%s\n", to);
fsprint(r->w->body, "%s\n", to);
}
for(a=attr; a; a=a->next) {
// Bprint(r->w->body, "%s: %s\n", a->name, a->value);
fsprint(r->w->body, "%s: %s\n", a->name, a->value);
}
dir = nil;
if(m != nil){
dir = estrstrdup(mbox.name, m->name);
if(to == nil && attr == nil){
/* Reply goes to replyto; Reply all goes to From and To and CC */
if(strstr(label, "all") == nil) {
// jpc Bprint(r->w->body, "To: %s\n", m->replyto);
fsprint(r->w->body, "To: %s\n", m->replyto);
}
else{ /* Replyall */
if(strlen(m->from) > 0) {
// Bprint(r->w->body, "To: %s\n", m->from);
fsprint(r->w->body, "To: %s\n", m->from);
}
if(strlen(m->to) > 0) {
// Bprint(r->w->body, "To: %s\n", m->to);
fsprint(r->w->body, "To: %s\n", m->to);
}
if(strlen(m->cc) > 0) {
// Bprint(r->w->body, "CC: %s\n", m->cc);
fsprint(r->w->body, "CC: %s\n", m->cc);
}
}
}
if(strlen(m->subject) > 0){
t = "Subject: Re: ";
if(strlen(m->subject) >= 3)
if(tolower(m->subject[0])=='r' && tolower(m->subject[1])=='e' && m->subject[2]==':')
t = "Subject: ";
// Bprint(r->w->body, "%s%s\n", t, m->subject);
fsprint(r->w->body, "%s%s\n", t, m->subject);
}
if(!quotereply){
// Bprint(r->w->body, "Include: %sraw\n", dir);
fsprint(r->w->body, "Include: %sraw\n", dir);
free(dir);
}
}
// Bprint(r->w->body, "\n");
fsprint(r->w->body, "\n");
if(m == nil) {
// Bprint(r->w->body, "\n");
fsprint(r->w->body, "\n");
}
else if(quotereply){
quote(m, r->w->body, dir, quotetext);
free(dir);
}
winclosebody(r->w);
if(m==nil && (to==nil || to[0]=='\0'))
winselect(r->w, "0", 0);
else
winselect(r->w, "$", 0);
winclean(r->w);
windormant(r->w);
}
void
delreply(Message *m)
{
if(m->next == nil)
replies.tail = m->prev;
else
m->next->prev = m->prev;
if(m->prev == nil)
replies.head = m->next;
else
m->prev->next = m->next;
mesgfreeparts(m);
free(m);
}
/* copy argv to stack and free the incoming strings, so we don't leak argument vectors */
void
buildargv(char **inargv, char *argv[NARGS+1], char args[NARGCHAR])
{
int i, n;
char *s, *a;
s = args;
for(i=0; i<NARGS; i++){
a = inargv[i];
if(a == nil)
break;
n = strlen(a)+1;
if((s-args)+n >= NARGCHAR) /* too many characters */
break;
argv[i] = s;
memmove(s, a, n);
s += n;
free(a);
}
argv[i] = nil;
}
void
execproc(void *v)
{
struct Exec *e;
int p[2], q[2];
char *prog;
char *argv[NARGS+1], args[NARGCHAR];
int fd[3];
e = v;
p[0] = e->p[0];
p[1] = e->p[1];
q[0] = e->q[0];
q[1] = e->q[1];
prog = e->prog; /* known not to be malloc'ed */
rfork(RFFDG);
sendul(e->sync, 1);
buildargv(e->argv, argv, args);
free(e->argv);
chanfree(e->sync);
free(e);
dup(p[0], 0);
close(p[0]);
close(p[1]);
if(q[0]){
dup(q[1], 1);
close(q[0]);
close(q[1]);
}
// jpc - start
fd[0] = dup(0, -1);
fd[1] = dup(1, -1);
threadexec(nil, fd, prog, argv);
close(fd[0]);
close(fd[1]);
/* jpc - procexec(nil, prog, argv); */
// jpc end
//fprint(2, "exec: %s", e->prog);
//{int i;
//for(i=0; argv[i]; i++) print(" '%s'", argv[i]);
//print("\n");
//}
//argv[0] = "cat";
//argv[1] = nil;
//procexec(nil, "/bin/cat", argv);
fprint(2, "Mail: can't exec %s: %r\n", prog);
threadexits("can't exec");
}
enum{
ATTACH,
BCC,
CC,
FROM,
INCLUDE,
TO,
};
char *headers[] = {
"attach:",
"bcc:",
"cc:",
"from:",
"include:",
"to:",
nil,
};
int
whichheader(char *h)
{
int i;
for(i=0; headers[i]!=nil; i++)
if(cistrcmp(h, headers[i]) == 0)
return i;
return -1;
}
char *tolist[200];
char *cclist[200];
char *bcclist[200];
int ncc, nbcc, nto;
char *attlist[200];
char included[200];
int
addressed(char *name)
{
int i;
for(i=0; i<nto; i++)
if(strcmp(name, tolist[i]) == 0)
return 1;
for(i=0; i<ncc; i++)
if(strcmp(name, cclist[i]) == 0)
return 1;
for(i=0; i<nbcc; i++)
if(strcmp(name, bcclist[i]) == 0)
return 1;
return 0;
}
char*
skipbl(char *s, char *e)
{
while(s < e){
if(*s!=' ' && *s!='\t' && *s!=',')
break;
s++;
}
return s;
}
char*
findbl(char *s, char *e)
{
while(s < e){
if(*s==' ' || *s=='\t' || *s==',')
break;
s++;
}
return s;
}
/*
* comma-separate possibly blank-separated strings in line; e points before newline
*/
void
commas(char *s, char *e)
{
char *t;
/* may have initial blanks */
s = skipbl(s, e);
while(s < e){
s = findbl(s, e);
if(s == e)
break;
t = skipbl(s, e);
if(t == e) /* no more words */
break;
/* patch comma */
*s++ = ',';
while(s < t)
*s++ = ' ';
}
}
int
print2(int fd, int ofd, char *fmt, ...)
{
int m, n;
char *s;
va_list arg;
va_start(arg, fmt);
s = vsmprint(fmt, arg);
va_end(arg);
if(s == nil)
return -1;
m = strlen(s);
n = write(fd, s, m);
if(ofd > 0)
write(ofd, s, m);
return n;
}
void
write2(int fd, int ofd, char *buf, int n, int nofrom)
{
char *from, *p;
int m;
write(fd, buf, n);
if(ofd <= 0)
return;
if(nofrom == 0){
write(ofd, buf, n);
return;
}
/* need to escape leading From lines to avoid corrupting 'outgoing' mailbox */
for(p=buf; *p; p+=m){
from = cistrstr(p, "from");
if(from == nil)
m = n;
else
m = from - p;
if(m > 0)
write(ofd, p, m);
if(from){
if(p==buf || from[-1]=='\n')
write(ofd, " ", 1); /* escape with space if From is at start of line */
write(ofd, from, 4);
m += 4;
}
n -= m;
}
}
void
mesgsend(Message *m)
{
char *s, *body, *to;
int i, j, h, n, natt, p[2];
struct Exec *e;
Channel *sync;
int first, nfld, delit, ofd;
char *copy, *fld[100], *now;
body = winreadbody(m->w, &n);
/* assemble to: list from first line, to: line, and cc: line */
nto = 0;
natt = 0;
ncc = 0;
nbcc = 0;
first = 1;
to = body;
for(;;){
for(s=to; *s!='\n'; s++)
if(*s == '\0'){
free(body);
return;
}
if(s++ == to) /* blank line */
break;
/* make copy of line to tokenize */
copy = emalloc(s-to);
memmove(copy, to, s-to);
copy[s-to-1] = '\0';
nfld = tokenizec(copy, fld, nelem(fld), ", \t");
if(nfld == 0){
free(copy);
break;
}
n -= s-to;
switch(h = whichheader(fld[0])){
case TO:
case FROM:
delit = 1;
commas(to+strlen(fld[0]), s-1);
for(i=1; i<nfld && nto<nelem(tolist); i++)
if(!addressed(fld[i]))
tolist[nto++] = estrdup(fld[i]);
break;
case BCC:
delit = 1;
commas(to+strlen(fld[0]), s-1);
for(i=1; i<nfld && nbcc<nelem(bcclist); i++)
if(!addressed(fld[i]))
bcclist[nbcc++] = estrdup(fld[i]);
break;
case CC:
delit = 1;
commas(to+strlen(fld[0]), s-1);
for(i=1; i<nfld && ncc<nelem(cclist); i++)
if(!addressed(fld[i]))
cclist[ncc++] = estrdup(fld[i]);
break;
case ATTACH:
case INCLUDE:
delit = 1;
for(i=1; i<nfld && natt<nelem(attlist); i++){
attlist[natt] = estrdup(fld[i]);
included[natt++] = (h == INCLUDE);
}
break;
default:
if(first){
delit = 1;
for(i=0; i<nfld && nto<nelem(tolist); i++)
tolist[nto++] = estrdup(fld[i]);
}else /* ignore it */
delit = 0;
break;
}
if(delit){
/* delete line from body */
memmove(to, s, n+1);
}else
to = s;
free(copy);
first = 0;
}
ofd = open(outgoing, OWRITE|OCEXEC); /* no error check necessary */
if(ofd > 0){
/* From dhog Fri Aug 24 22:13:00 EDT 2001 */
now = ctime(time(0));
fprint(ofd, "From %s %s", user, now);
fprint(ofd, "From: %s\n", user);
fprint(ofd, "Date: %s", now);
for(i=0; i<natt; i++)
if(included[i])
fprint(ofd, "Include: %s\n", attlist[i]);
else
fprint(ofd, "Attach: %s\n", attlist[i]);
/* needed because mail is by default Latin-1 */
fprint(ofd, "Content-Type: text/plain; charset=\"UTF-8\"\n");
fprint(ofd, "Content-Transfer-Encoding: 8bit\n");
}
e = emalloc(sizeof(struct Exec));
if(pipe(p) < 0)
error("can't create pipe: %r");
e->p[0] = p[0];
e->p[1] = p[1];
e->prog = unsharp("#9/bin/upas/marshal");
e->argv = emalloc((1+1+2+4*natt+1)*sizeof(char*));
e->argv[0] = estrdup("marshal");
e->argv[1] = estrdup("-8");
j = 2;
if(m->replyname){
e->argv[j++] = estrdup("-R");
e->argv[j++] = estrstrdup(mbox.name, m->replyname);
}
for(i=0; i<natt; i++){
if(included[i])
e->argv[j++] = estrdup("-A");
else
e->argv[j++] = estrdup("-a");
e->argv[j++] = estrdup(attlist[i]);
}
sync = chancreate(sizeof(int), 0);
e->sync = sync;
proccreate(execproc, e, EXECSTACK);
recvul(sync);
// close(p[0]);
/* using marshal -8, so generate rfc822 headers */
if(nto > 0){
print2(p[1], ofd, "To: ");
for(i=0; i<nto-1; i++)
print2(p[1], ofd, "%s, ", tolist[i]);
print2(p[1], ofd, "%s\n", tolist[i]);
}
if(ncc > 0){
print2(p[1], ofd, "CC: ");
for(i=0; i<ncc-1; i++)
print2(p[1], ofd, "%s, ", cclist[i]);
print2(p[1], ofd, "%s\n", cclist[i]);
}
if(nbcc > 0){
print2(p[1], ofd, "BCC: ");
for(i=0; i<nbcc-1; i++)
print2(p[1], ofd, "%s, ", bcclist[i]);
print2(p[1], ofd, "%s\n", bcclist[i]);
}
i = strlen(body);
if(i > 0)
write2(p[1], ofd, body, i, 1);
/* guarantee a blank line, to ensure attachments are separated from body */
if(i==0 || body[i-1]!='\n')
write2(p[1], ofd, "\n\n", 2, 0);
else if(i>1 && body[i-2]!='\n')
write2(p[1], ofd, "\n", 1, 0);
/* these look like pseudo-attachments in the "outgoing" box */
if(ofd>0 && natt>0){
for(i=0; i<natt; i++)
if(included[i])
fprint(ofd, "=====> Include: %s\n", attlist[i]);
else
fprint(ofd, "=====> Attach: %s\n", attlist[i]);
}
if(ofd > 0)
write(ofd, "\n", 1);
for(i=0; i<natt; i++)
free(attlist[i]);
close(ofd);
close(p[1]);
free(body);
if(m->replyname != nil)
mesgmenumark(mbox.w, m->replyname, "\t[replied]");
if(m->name[0] == '/')
s = estrdup(m->name);
else
s = estrstrdup(mbox.name, m->name);
s = egrow(s, "-R", nil);
winname(m->w, s);
free(s);
winclean(m->w);
/* mark message unopened because it's no longer the original message */
m->opened = 0;
}

170
src/cmd/acme/mail/util.c Normal file
View file

@ -0,0 +1,170 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <plumb.h>
#include <9pclient.h>
#include "dat.h"
void*
emalloc(uint n)
{
void *p;
p = malloc(n);
if(p == nil)
error("can't malloc: %r");
memset(p, 0, n);
setmalloctag(p, getcallerpc(&n));
return p;
}
void*
erealloc(void *p, uint n)
{
p = realloc(p, n);
if(p == nil)
error("can't realloc: %r");
setmalloctag(p, getcallerpc(&n));
return p;
}
char*
estrdup(char *s)
{
char *t;
t = emalloc(strlen(s)+1);
strcpy(t, s);
return t;
}
char*
estrstrdup(char *s, char *t)
{
char *u;
u = emalloc(strlen(s)+strlen(t)+1);
strcpy(u, s);
strcat(u, t);
return u;
}
char*
eappend(char *s, char *sep, char *t)
{
char *u;
if(t == nil)
u = estrstrdup(s, sep);
else{
u = emalloc(strlen(s)+strlen(sep)+strlen(t)+1);
strcpy(u, s);
strcat(u, sep);
strcat(u, t);
}
free(s);
return u;
}
char*
egrow(char *s, char *sep, char *t)
{
s = eappend(s, sep, t);
free(t);
return s;
}
void
error(char *fmt, ...)
{
Fmt f;
char buf[64];
va_list arg;
fmtfdinit(&f, 2, buf, sizeof buf);
fmtprint(&f, "Mail: ");
va_start(arg, fmt);
fmtvprint(&f, fmt, arg);
va_end(arg);
fmtprint(&f, "\n");
fmtfdflush(&f);
threadexitsall(fmt);
}
#if 0 /* jpc */
void
ctlprint(int fd, char *fmt, ...)
{
int n;
va_list arg;
va_start(arg, fmt);
n = vfprint(fd, fmt, arg);
va_end(arg);
fsync(fd);
if(n <= 0)
error("control file write error: %r");
}
#endif
void
ctlprint(CFid* fd, char *fmt, ...)
{
int n;
va_list arg;
char tmp[250];
va_start(arg, fmt);
n = vsnprint(tmp, 250, fmt, arg);
va_end(arg);
n = fswrite(fd, tmp, strlen(tmp));
if(n <= 0)
error("control file write error: %r");
}
int fsprint(CFid *fid, char* fmt, ...) {
// example call this replaces: Bprint(b, ">%s%s\n", lines[i][0]=='>'? "" : " ", lines[i]);
char *tmp;
va_list arg;
int n, tlen;
tmp = emalloc( tlen=(strlen(fmt)+250) ); // leave room for interpolated text
va_start(arg, fmt);
n = vsnprint(tmp, tlen, fmt, arg);
va_end(arg);
if(n == tlen)
error("fsprint formatting error");
n = fswrite(fid, tmp, strlen(tmp));
if(n <= 0)
error("fsprint write error: %r");
free(tmp);
return n;
}
#if 0 /* jpc */
/*
here's a read construct (from winselection) that may be useful in fsprint - think about it.
*/
int m, n;
char *buf;
char tmp[256];
CFid* fid;
fid = winopenfid1(w, "rdsel", OREAD);
if(fid == nil)
error("can't open rdsel: %r");
n = 0;
buf = nil;
for(;;){
m = fsread(fid, tmp, sizeof tmp);
if(m <= 0)
break;
buf = erealloc(buf, n+m+1);
memmove(buf+n, tmp, m);
n += m;
buf[n] = '\0';
}
#endif

397
src/cmd/acme/mail/win.c Normal file
View file

@ -0,0 +1,397 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <plumb.h>
#include <9pclient.h> /* jpc */
#include "dat.h"
extern CFsys *acmefs; /* jpc */
Window*
newwindow(void)
{
char buf[12];
Window *w;
int n = 0;
w = emalloc(sizeof(Window));
/* jpc
w->ctl = open("/mnt/wsys/new/ctl", ORDWR|OCEXEC);
if(w->ctl<0 || read(w->ctl, buf, 12)!=12)
error("can't open window ctl file: %r");
*/
/* w->ctl = fsopenfd(acmefs, "new/ctl", ORDWR|OCEXEC);
if(w->ctl<0 || (n = read(w->ctl, buf, 12))!=12) {
fprint(2,"%d bytes read from %d\n",n,w->ctl);
error("can't open window ctl file: %r");
}
jpc end */
w->ctl = fsopen(acmefs, "new/ctl", ORDWR|OCEXEC);
if(w->ctl == nil || (n = fsread(w->ctl, buf, 12))!=12) {
fprint(2,"%d bytes read from %d\n",n,w->ctl);
error("can't open window ctl file: %r");
}
ctlprint(w->ctl, "noscroll\n");
w->id = atoi(buf);
w->event = winopenfid(w, "event");
w->addr = nil; /* will be opened when needed */
w->body = nil;
w->data = nil;
w->cevent = chancreate(sizeof(Event*), 0);
return w;
}
void
winsetdump(Window *w, char *dir, char *cmd)
{
if(dir != nil)
ctlprint(w->ctl, "dumpdir %s\n", dir);
if(cmd != nil)
ctlprint(w->ctl, "dump %s\n", cmd);
}
void
wineventproc(void *v)
{
Window *w;
int i;
w = v;
for(i=0; ; i++){
if(i >= NEVENT)
i = 0;
wingetevent(w, &w->e[i]);
sendp(w->cevent, &w->e[i]);
}
}
static CFid*
winopenfid1(Window *w, char *f, int m)
{
char buf[64];
CFid* fd;
sprint(buf, "%d/%s", w->id, f);
fd = fsopen(acmefs, buf, m|OCEXEC);
if(fd == nil)
error("can't open window file %s: %r", f);
return fd;
}
static int
winopenfile1(Window *w, char *f, int m)
{
char buf[64];
int fd;
/* jpc
sprint(buf, "/mnt/wsys/%d/%s", w->id, f);
fd = open(buf, m|OCEXEC);
*/
sprint(buf, "%d/%s", w->id, f);
fd = fsopenfd(acmefs, buf, m|OCEXEC);
if(fd < 0)
error("can't open window file %s: %r", f);
return fd;
}
CFid*
winopenfid(Window *w, char *f)
{
return winopenfid1(w, f, ORDWR);
}
int
winopenfile(Window *w, char *f)
{
return winopenfile1(w, f, ORDWR);
}
void
wintagwrite(Window *w, char *s, int n)
{
CFid* fid;
fid = winopenfid(w, "tag");
if(fswrite(fid, s, n) != n)
error("tag write: %r");
fsclose(fid);
}
void
winname(Window *w, char *s)
{
ctlprint(w->ctl, "name %s\n", s);
}
void
winopenbody(Window *w, int mode)
{
char buf[256];
CFid* fid;
/* jpc
sprint(buf, "/mnt/wsys/%d/body", w->id);
w->body = Bopen(buf, mode|OCEXEC);
*/
sprint(buf, "%d/body", w->id);
fid = fsopen(acmefs,buf, mode|OCEXEC);
w->body = fid; // jpcBfdopen(id, mode|OCEXEC);
if(w->body == nil)
error("can't open window body file: %r");
}
void
winclosebody(Window *w)
{
if(w->body != nil){
// jpc Bterm(w->body);
fsclose(w->body);
w->body = nil;
}
}
void
winwritebody(Window *w, char *s, int n)
{
if(w->body == nil)
winopenbody(w, OWRITE);
// jpc if(Bwrite(w->body, s, n) != n)
if(fswrite(w->body, s, n) != n)
error("write error to window: %r");
}
int
wingetec(Window *w)
{
if(w->nbuf == 0){
w->nbuf = fsread(w->event, w->buf, sizeof w->buf);
if(w->nbuf <= 0){
/* probably because window has exited, and only called by wineventproc, so just shut down */
threadexits(nil);
}
w->bufp = w->buf;
}
w->nbuf--;
return *w->bufp++;
}
int
wingeten(Window *w)
{
int n, c;
n = 0;
while('0'<=(c=wingetec(w)) && c<='9')
n = n*10+(c-'0');
if(c != ' ')
error("event number syntax");
return n;
}
int
wingeter(Window *w, char *buf, int *nb)
{
Rune r;
int n;
r = wingetec(w);
buf[0] = r;
n = 1;
if(r >= Runeself) {
while(!fullrune(buf, n))
buf[n++] = wingetec(w);
chartorune(&r, buf);
}
*nb = n;
return r;
}
void
wingetevent(Window *w, Event *e)
{
int i, nb;
e->c1 = wingetec(w);
e->c2 = wingetec(w);
e->q0 = wingeten(w);
e->q1 = wingeten(w);
e->flag = wingeten(w);
e->nr = wingeten(w);
if(e->nr > EVENTSIZE)
error("event string too long");
e->nb = 0;
for(i=0; i<e->nr; i++){
e->r[i] = wingeter(w, e->b+e->nb, &nb);
e->nb += nb;
}
e->r[e->nr] = 0;
e->b[e->nb] = 0;
if(wingetec(w) != '\n')
error("event syntax error");
}
void
winwriteevent(Window *w, Event *e)
{
fsprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
}
void
winread(Window *w, uint q0, uint q1, char *data)
{
int m, n, nr;
char buf[256];
if(w->addr == nil)
w->addr = winopenfid(w, "addr");
if(w->data == nil)
w->data = winopenfid(w, "data");
m = q0;
while(m < q1){
n = sprint(buf, "#%d", m);
if(fswrite(w->addr, buf, n) != n)
error("error writing addr: %r");
n = fsread(w->data, buf, sizeof buf);
if(n <= 0)
error("reading data: %r");
nr = utfnlen(buf, n);
while(m+nr >q1){
do; while(n>0 && (buf[--n]&0xC0)==0x80);
--nr;
}
if(n == 0)
break;
memmove(data, buf, n);
data += n;
*data = 0;
m += nr;
}
}
void
windormant(Window *w)
{
if(w->addr != nil){
fsclose(w->addr);
w->addr = nil;
}
if(w->body != nil){
fsclose(w->body);
w->body = nil;
}
if(w->data != nil){
fsclose(w->data);
w->data = nil;
}
}
int
windel(Window *w, int sure)
{
if(sure) {
fswrite(w->ctl, "delete\n", 7);
// fsync(w->ctl);
}
else if(fswrite(w->ctl, "del\n", 4) != 4) {
// fsync(w->ctl);
return 0;
}
/* event proc will die due to read error from event file */
windormant(w);
fsclose(w->ctl);
w->ctl = nil;
fsclose(w->event);
w->event = nil;
return 1;
}
void
winclean(Window *w)
{
// int fd;
// if(w->body)
// Bflush(w->body);
ctlprint(w->ctl, "clean\n");
}
int
winsetaddr(Window *w, char *addr, int errok)
{
if(w->addr == nil)
w->addr = winopenfid(w, "addr");
if(fswrite(w->addr, addr, strlen(addr)) < 0){
if(!errok)
error("error writing addr(%s): %r", addr);
return 0;
}
return 1;
}
int
winselect(Window *w, char *addr, int errok)
{
if(winsetaddr(w, addr, errok)){
ctlprint(w->ctl, "dot=addr\n");
return 1;
}
return 0;
}
char*
winreadbody(Window *w, int *np) /* can't use readfile because acme doesn't report the length */
{
char *s;
int m, na, n;
if(w->body != nil)
winclosebody(w);
winopenbody(w, OREAD);
s = nil;
na = 0;
n = 0;
for(;;){
if(na < n+512){
na += 1024;
s = realloc(s, na+1);
}
// jpc m = Bread(w->body, s+n, na-n);
m = fsread(w->body, s+n, na-n);
if(m <= 0)
break;
n += m;
}
s[n] = 0;
winclosebody(w);
*np = n;
return s;
}
char*
winselection(Window *w)
{
int m, n;
char *buf;
char tmp[256];
CFid* fid;
fid = winopenfid1(w, "rdsel", OREAD);
if(fid == nil)
error("can't open rdsel: %r");
n = 0;
buf = nil;
for(;;){
m = fsread(fid, tmp, sizeof tmp);
if(m <= 0)
break;
buf = erealloc(buf, n+m+1);
memmove(buf+n, tmp, m);
n += m;
buf[n] = '\0';
}
fsclose(fid);
return buf;
}