diff options
Diffstat (limited to 'acme/news')
-rw-r--r-- | acme/news/guide | 2 | ||||
-rw-r--r-- | acme/news/src/mkfile | 18 | ||||
-rw-r--r-- | acme/news/src/news.c | 1007 | ||||
-rw-r--r-- | acme/news/src/util.c | 106 | ||||
-rw-r--r-- | acme/news/src/win.c | 324 | ||||
-rw-r--r-- | acme/news/src/win.h | 75 |
6 files changed, 1532 insertions, 0 deletions
diff --git a/acme/news/guide b/acme/news/guide new file mode 100644 index 000000000..845b0ae1e --- /dev/null +++ b/acme/news/guide @@ -0,0 +1,2 @@ +Local nntpfs +News comp.os.plan9 diff --git a/acme/news/src/mkfile b/acme/news/src/mkfile new file mode 100644 index 000000000..34979b25e --- /dev/null +++ b/acme/news/src/mkfile @@ -0,0 +1,18 @@ +</$objtype/mkfile + +TARG=News +OFILES=\ + news.$O\ + util.$O\ + win.$O\ + +HFILES= + +BIN=/acme/bin/$objtype +UPDATE=\ + mkfile\ + $HFILES\ + ${OFILES:%.$O=%.c}\ + ${TARG:%=/acme/bin/386/%}\ + +</sys/src/cmd/mkone diff --git a/acme/news/src/news.c b/acme/news/src/news.c new file mode 100644 index 000000000..90cb3c754 --- /dev/null +++ b/acme/news/src/news.c @@ -0,0 +1,1007 @@ +/* + * Acme interface to nntpfs. + */ +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <thread.h> +#include "win.h" +#include <ctype.h> + +int canpost; +int debug; +int nshow = 20; + +int lo; /* next message to look at in dir */ +int hi; /* current hi message in dir */ +char *dir = "/mnt/news"; +char *group; +char *from; + +typedef struct Article Article; +struct Article { + Ref; + Article *prev; + Article *next; + Window *w; + int n; + int dead; + int dirtied; + int sayspost; + int headers; + int ispost; +}; + +Article *mlist; +Window *root; + +int +cistrncmp(char *a, char *b, int n) +{ + while(n-- > 0){ + if(tolower(*a++) != tolower(*b++)) + return -1; + } + return 0; +} + +int +cistrcmp(char *a, char *b) +{ + for(;;){ + if(tolower(*a) != tolower(*b++)) + return -1; + if(*a++ == 0) + break; + } + return 0; +} + +char* +skipwhite(char *p) +{ + while(isspace(*p)) + p++; + return p; +} + +int +gethi(void) +{ + Dir *d; + int hi; + + if((d = dirstat(dir)) == nil) + return -1; + hi = d->qid.vers; + free(d); + return hi; +} + +char* +fixfrom(char *s) +{ + char *r, *w; + + s = estrdup(s); + /* remove quotes */ + for(r=w=s; *r; r++) + if(*r != '"') + *w++ = *r; + *w = '\0'; + return s; +} + +char *day[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", }; +char *mon[] = { + "Jan", "Feb", "Mar", "Apr", + "May", "Jun", "Jul", "Aug", + "Sep", "Oct", "Nov", "Dec", +}; + +char* +fixdate(char *s) +{ + char *f[10], *m, *t, *wd, tmp[40]; + int d, i, j, nf, hh, mm; + + nf = tokenize(s, f, nelem(f)); + + wd = nil; + d = 0; + m = nil; + t = nil; + for(i=0; i<nf; i++){ + for(j=0; j<7; j++) + if(cistrncmp(day[j], f[i], 3)==0) + wd = day[j]; + for(j=0; j<12; j++) + if(cistrncmp(mon[j], f[i], 3)==0) + m = mon[j]; + j = atoi(f[i]); + if(1 <= j && j <= 31 && d != 0) + d = j; + if(strchr(f[i], ':')) + t = f[i]; + } + + if(d==0 || wd==nil || m==nil || t==nil) + return nil; + + hh = strtol(t, 0, 10); + mm = strtol(strchr(t, ':')+1, 0, 10); + sprint(tmp, "%s %d %s %d:%.2d", wd, d, m, hh, mm); + return estrdup(tmp); +} + +void +msgheadline(Biobuf *bin, int n, Biobuf *bout) +{ + char *p, *q; + char *date; + char *from; + char *subject; + + date = nil; + from = nil; + subject = nil; + while(p = Brdline(bin, '\n')){ + p[Blinelen(bin)-1] = '\0'; + if((q = strchr(p, ':')) == nil) + continue; + *q++ = '\0'; + if(cistrcmp(p, "from")==0) + from = fixfrom(skipwhite(q)); + else if(cistrcmp(p, "subject")==0) + subject = estrdup(skipwhite(q)); + else if(cistrcmp(p, "date")==0) + date = fixdate(skipwhite(q)); + } + + Bprint(bout, "%d/\t%s", n, from ? from : ""); + if(date) + Bprint(bout, "\t%s", date); + if(subject) + Bprint(bout, "\n\t%s", subject); + Bprint(bout, "\n"); + + free(date); + free(from); + free(subject); +} + +/* + * Write the headers for at most nshow messages to b, + * starting with hi and working down to lo. + * Return number of first message not scanned. + */ +int +adddir(Biobuf *body, int hi, int lo, int nshow) +{ + char *p, *q, tmp[40]; + int i, n; + Biobuf *b; + + n = 0; + for(i=hi; i>=lo && n<nshow; i--){ + sprint(tmp, "%d", i); + p = estrstrdup(dir, tmp); + if(access(p, OREAD) < 0){ + free(p); + break; + } + q = estrstrdup(p, "/header"); + free(p); + b = Bopen(q, OREAD); + free(q); + if(b == nil) + continue; + msgheadline(b, i, body); + Bterm(b); + n++; + } + return i; +} + +/* + * Show the first nshow messages in the window. + * This depends on nntpfs presenting contiguously + * numbered directories, and on the qid version being + * the topmost numbered directory. + */ +void +dirwindow(Window *w) +{ + if((hi=gethi()) < 0) + return; + + if(w->data < 0) + w->data = winopenfile(w, "data"); + + fprint(w->ctl, "dirty\n"); + + winopenbody(w, OWRITE); + lo = adddir(w->body, hi, 0, nshow); + winclean(w); +} + +/* return 1 if handled, 0 otherwise */ +static int +iscmd(char *s, char *cmd) +{ + int len; + + len = strlen(cmd); + return strncmp(s, cmd, len)==0 && (s[len]=='\0' || s[len]==' ' || s[len]=='\t' || s[len]=='\n'); +} + +static char* +skip(char *s, char *cmd) +{ + s += strlen(cmd); + while(*s==' ' || *s=='\t' || *s=='\n') + s++; + return s; +} + +void +unlink(Article *m) +{ + if(mlist == m) + mlist = m->next; + + if(m->next) + m->next->prev = m->prev; + if(m->prev) + m->prev->next = m->next; + m->next = m->prev = nil; +} + +int mesgopen(char*); +int fillmesgwindow(int, Article*); +Article *newpost(void); +void replywindow(Article*); +void mesgpost(Article*); + +/* + * Depends on d.qid.vers being highest numbered message in dir. + */ +void +acmetimer(Article *m, Window *w) +{ + Biobuf *b; + Dir *d; + + assert(m==nil && w==root); + + if((d = dirstat(dir))==nil | hi==d->qid.vers){ + free(d); + return; + } + + if(w->data < 0) + w->data = winopenfile(w, "data"); + if(winsetaddr(w, "0", 0)) + write(w->data, "", 0); + + b = emalloc(sizeof(*b)); + Binit(b, w->data, OWRITE); + adddir(b, d->qid.vers, hi+1, d->qid.vers); + hi = d->qid.vers; + Bterm(b); + free(b); + free(d); + winselect(w, "0,.", 0); +} + +int +acmeload(Article*, Window*, char *s) +{ + int nopen; + +//fprint(2, "load %s\n", s); + + nopen = 0; + while(*s){ + /* skip directory name */ + if(strncmp(s, dir, strlen(dir))==0) + s += strlen(dir); + nopen += mesgopen(s); + if((s = strchr(s, '\n')) == nil) + break; + s = skip(s, ""); + } + return nopen; +} + +int +acmecmd(Article *m, Window *w, char *s) +{ + int n; + Biobuf *b; + +//fprint(2, "cmd %s\n", s); + + s = skip(s, ""); + + if(iscmd(s, "Del")){ + if(m == nil){ /* don't close dir until messages close */ + if(mlist != nil){ + ctlprint(mlist->w->ctl, "show\n"); + return 1; + } + if(windel(w, 0)) + threadexitsall(nil); + return 1; + }else{ + if(windel(w, 0)) + m->dead = 1; + return 1; + } + } + if(m==nil && iscmd(s, "More")){ + s = skip(s, "More"); + if(n = atoi(s)) + nshow = n; + + if(w->data < 0) + w->data = winopenfile(w, "data"); + winsetaddr(w, "$", 1); + + fprint(w->ctl, "dirty\n"); + + b = emalloc(sizeof(*b)); + Binit(b, w->data, OWRITE); + lo = adddir(b, lo, 0, nshow); + Bterm(b); + free(b); + winclean(w); + winsetaddr(w, ".,", 0); + } + if(m!=nil && !m->ispost && iscmd(s, "Headers")){ + m->headers = !m->headers; + fillmesgwindow(-1, m); + return 1; + } + if(iscmd(s, "Newpost")){ + m = newpost(); + winopenbody(m->w, OWRITE); + Bprint(m->w->body, "%s\nsubject: \n\n", group); + winclean(m->w); + winselect(m->w, "$", 0); + return 1; + } + if(m!=nil && !m->ispost && iscmd(s, "Reply")){ + replywindow(m); + return 1; + } +// if(m!=nil && iscmd(s, "Replymail")){ +// fprint(2, "no replymail yet\n"); +// return 1; +// } + if(iscmd(s, "Post")){ + mesgpost(m); + return 1; + } + return 0; +} + +void +acmeevent(Article *m, Window *w, Event *e) +{ + Event *ea, *e2, *eq; + char *s, *t, *buf; + int na; + //int n; + //ulong q0, q1; + + switch(e->c1){ /* origin of action */ + default: + Unknown: + fprint(2, "unknown message %c%c\n", e->c1, e->c2); + break; + + case 'T': /* bogus timer event! */ + acmetimer(m, w); + break; + + case 'F': /* generated by our actions; ignore */ + break; + + case 'E': /* write to body or tag; can't affect us */ + break; + + case 'K': /* type away; we don't care */ + if(m && (e->c2 == 'I' || e->c2 == 'D')){ + m->dirtied = 1; + if(!m->sayspost){ + wintagwrite(w, "Post ", 5); + m->sayspost = 1; + } + } + break; + + case 'M': /* mouse event */ + switch(e->c2){ /* type of action */ + case 'x': /* mouse: button 2 in tag */ + case 'X': /* mouse: button 2 in body */ + ea = nil; + //e2 = nil; + s = e->b; + if(e->flag & 2){ /* null string with non-null expansion */ + e2 = recvp(w->cevent); + if(e->nb==0) + s = e2->b; + } + if(e->flag & 8){ /* chorded argument */ + ea = recvp(w->cevent); /* argument */ + na = ea->nb; + recvp(w->cevent); /* ignore origin */ + }else + na = 0; + + /* append chorded arguments */ + if(na){ + t = emalloc(strlen(s)+1+na+1); + sprint(t, "%s %s", s, ea->b); + s = t; + } + /* if it's a known command, do it */ + /* if it's a long message, it can't be for us anyway */ + // DPRINT(2, "exec: %s\n", s); + if(!acmecmd(m, w, s)) /* send it back */ + winwriteevent(w, e); + if(na) + free(s); + break; + + case 'l': /* mouse: button 3 in tag */ + case 'L': /* mouse: button 3 in body */ + //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; + } + if(!acmeload(m, w, s)) + winwriteevent(w, e); + break; + + case 'i': /* mouse: text inserted in tag */ + case 'd': /* mouse: text deleted from tag */ + break; + + case 'I': /* mouse: text inserted in body */ + case 'D': /* mouse: text deleted from body */ + if(m == nil) + break; + + m->dirtied = 1; + if(!m->sayspost){ + wintagwrite(w, "Post ", 5); + m->sayspost = 1; + } + break; + + default: + goto Unknown; + } + } +} + +void +dirthread(void *v) +{ + Event *e; + Window *w; + + w = v; + while(e = recvp(w->cevent)) + acmeevent(nil, w, e); + + threadexitsall(nil); +} + +void +mesgthread(void *v) +{ + Event *e; + Article *m; + + m = v; + while(!m->dead && (e = recvp(m->w->cevent))) + acmeevent(m, m->w, e); + +//fprint(2, "msg %p exits\n", m); + unlink(m); + free(m->w); + free(m); + threadexits(nil); +} + +/* +Xref: news.research.att.com comp.os.plan9:7360 +Newsgroups: comp.os.plan9 +Path: news.research.att.com!batch0!uunet!ffx.uu.net!finch!news.mindspring.net!newsfeed.mathworks.com!fu-berlin.de!server1.netnews.ja.net!hgmp.mrc.ac.uk!pegasus.csx.cam.ac.uk!bath.ac.uk!ccsdhd +From: Stephen Adam <saadam@bigpond.com> +Subject: Future of Plan9 +Approved: plan9mod@bath.ac.uk +X-Newsreader: Microsoft Outlook Express 5.00.2014.211 +X-Mimeole: Produced By Microsoft MimeOLE V5.00.2014.211 +Sender: ccsdhd@bath.ac.uk (Dennis Davis) +Nntp-Posting-Date: Wed, 13 Dec 2000 21:28:45 EST +NNTP-Posting-Host: 203.54.121.233 +Organization: Telstra BigPond Internet Services (http://www.bigpond.com) +X-Date: Wed, 13 Dec 2000 20:43:37 +1000 +Lines: 12 +Message-ID: <xbIZ5.157945$e5.114349@newsfeeds.bigpond.com> +References: <95pghu$3lf$1@news.fas.harvard.edu> <95ph36$3m9$1@news.fas.harvard.edu> <slrn980iic.u5q.mperrin@hcs.harvard.edu> <95png6$4ln$1@news.fas.harvard.edu> <95poqg$4rq$1@news.fas.harvard.edu> <slrn980vh8.2gb.myLastName@is07.fas.harvard.edu> <95q40h$66c$2@news.fas.harvard.edu> <95qjhu$8ke$1@news.fas.harvard.edu> <95riue$bu2$1@news.fas.harvard.edu> <95rnar$cbu$1@news.fas.harvard.edu> +X-Msmail-Priority: Normal +X-Trace: newsfeeds.bigpond.com 976703325 203.54.121.233 (Wed, 13 Dec 2000 21:28:45 EST) +X-Priority: 3 +Date: Wed, 13 Dec 2000 10:49:50 GMT +*/ + +char *skipheader[] = +{ + "x-", + "path:", + "xref:", + "approved:", + "sender:", + "nntp-", + "organization:", + "lines:", + "message-id:", + "references:", + "reply-to:", + "mime-", + "content-", +}; + +int +fillmesgwindow(int fd, Article *m) +{ + Biobuf *b; + char *p, tmp[40]; + int i, inhdr, copy, xfd; + Window *w; + + xfd = -1; + if(fd == -1){ + sprint(tmp, "%d/article", m->n); + p = estrstrdup(dir, tmp); + if((xfd = open(p, OREAD)) < 0){ + free(p); + return 0; + } + free(p); + fd = xfd; + } + + w = m->w; + if(w->data < 0) + w->data = winopenfile(w, "data"); + if(winsetaddr(w, ",", 0)) + write(w->data, "", 0); + + winopenbody(m->w, OWRITE); + b = emalloc(sizeof(*b)); + Binit(b, fd, OREAD); + + inhdr = 1; + copy = 1; + while(p = Brdline(b, '\n')){ + if(Blinelen(b)==1) + inhdr = 0, copy=1; + if(inhdr && !isspace(p[0])){ + copy = 1; + if(!m->headers){ + if(cistrncmp(p, "from:", 5)==0){ + p[Blinelen(b)-1] = '\0'; + p = fixfrom(skip(p, "from:")); + Bprint(m->w->body, "From: %s\n", p); + free(p); + copy = 0; + continue; + } + for(i=0; i<nelem(skipheader); i++) + if(cistrncmp(p, skipheader[i], strlen(skipheader[i]))==0) + copy=0; + } + } + if(copy) + Bwrite(m->w->body, p, Blinelen(b)); + } + Bterm(b); + free(b); + winclean(m->w); + if(xfd != -1) + close(xfd); + return 1; +} + +Article* +newpost(void) +{ + Article *m; + char *p, tmp[40]; + static int nnew; + + m = emalloc(sizeof *m); + sprint(tmp, "Post%d", ++nnew); + p = estrstrdup(dir, tmp); + + m->w = newwindow(); + proccreate(wineventproc, m->w, STACK); + winname(m->w, p); + wintagwrite(m->w, "Post ", 5); + m->sayspost = 1; + m->ispost = 1; + threadcreate(mesgthread, m, STACK); + + if(mlist){ + m->next = mlist; + mlist->prev = m; + } + mlist = m; + return m; +} + +void +replywindow(Article *m) +{ + Biobuf *b; + char *p, *ep, *q, tmp[40]; + int fd, copy; + Article *reply; + + sprint(tmp, "%d/article", m->n); + p = estrstrdup(dir, tmp); + if((fd = open(p, OREAD)) < 0){ + free(p); + return; + } + free(p); + + reply = newpost(); + winopenbody(reply->w, OWRITE); + b = emalloc(sizeof(*b)); + Binit(b, fd, OREAD); + copy = 0; + while(p = Brdline(b, '\n')){ + if(Blinelen(b)==1) + break; + ep = p+Blinelen(b); + if(!isspace(*p)){ + copy = 0; + if(cistrncmp(p, "newsgroups:", 11)==0){ + for(q=p+11; *q!='\n'; q++) + if(*q==',') + *q = ' '; + copy = 1; + }else if(cistrncmp(p, "subject:", 8)==0){ + if(!strstr(p, " Re:") && !strstr(p, " RE:") && !strstr(p, " re:")){ + p = skip(p, "subject:"); + ep[-1] = '\0'; + Bprint(reply->w->body, "Subject: Re: %s\n", p); + }else + copy = 1; + }else if(cistrncmp(p, "message-id:", 11)==0){ + Bprint(reply->w->body, "References: "); + p += 11; + copy = 1; + } + } + if(copy) + Bwrite(reply->w->body, p, ep-p); + } + Bterm(b); + close(fd); + free(b); + Bprint(reply->w->body, "\n"); + winclean(reply->w); + winselect(reply->w, "$", 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++ = ' '; + } +} +void +mesgpost(Article *m) +{ + Biobuf *b; + char *p, *ep; + int isfirst, ishdr, havegroup, havefrom; + + p = estrstrdup(dir, "post"); + if((b = Bopen(p, OWRITE)) == nil){ + fprint(2, "cannot open %s: %r\n", p); + free(p); + return; + } + free(p); + + winopenbody(m->w, OREAD); + ishdr = 1; + isfirst = 1; + havegroup = havefrom = 0; + while(p = Brdline(m->w->body, '\n')){ + ep = p+Blinelen(m->w->body); + if(ishdr && p+1==ep){ + if(!havegroup) + Bprint(b, "Newsgroups: %s\n", group); + if(!havefrom) + Bprint(b, "From: %s\n", from); + ishdr = 0; + } + if(ishdr){ + ep[-1] = '\0'; + if(isfirst && strchr(p, ':')==0){ /* group list */ + commas(p, ep); + Bprint(b, "newsgroups: %s\n", p); + havegroup = 1; + isfirst = 0; + continue; + } + if(cistrncmp(p, "newsgroup:", 10)==0){ + commas(skip(p, "newsgroup:"), ep); + Bprint(b, "newsgroups: %s\n", skip(p, "newsgroup:")); + havegroup = 1; + continue; + } + if(cistrncmp(p, "newsgroups:", 11)==0){ + commas(skip(p, "newsgroups:"), ep); + Bprint(b, "newsgroups: %s\n", skip(p, "newsgroups:")); + havegroup = 1; + continue; + } + if(cistrncmp(p, "from:", 5)==0) + havefrom = 1; + ep[-1] = '\n'; + } + Bwrite(b, p, ep-p); + } + winclosebody(m->w); + Bflush(b); + if(write(Bfildes(b), "", 0) == 0) + winclean(m->w); + else + fprint(2, "post: %r\n"); + Bterm(b); +} + +int +mesgopen(char *s) +{ + char *p, tmp[40]; + int fd, n; + Article *m; + + n = atoi(s); + if(n==0) + return 0; + + for(m=mlist; m; m=m->next){ + if(m->n == n){ + ctlprint(m->w->ctl, "show\n"); + return 1; + } + } + + sprint(tmp, "%d/article", n); + p = estrstrdup(dir, tmp); + if((fd = open(p, OREAD)) < 0){ + free(p); + return 0; + } + + m = emalloc(sizeof(*m)); + m->w = newwindow(); + m->n = n; + proccreate(wineventproc, m->w, STACK); + p[strlen(p)-strlen("article")] = '\0'; + winname(m->w, p); + if(canpost) + wintagwrite(m->w, "Reply ", 6); + wintagwrite(m->w, "Headers ", 8); + + free(p); + if(mlist){ + m->next = mlist; + mlist->prev = m; + } + mlist = m; + threadcreate(mesgthread, m, STACK); + + fillmesgwindow(fd, m); + close(fd); + windormant(m->w); + return 1; +} + +void +usage(void) +{ + fprint(2, "usage: News [-d /mnt/news] comp.os.plan9\n"); + exits("usage"); +} + +void +timerproc(void *v) +{ + Event e; + Window *w; + + memset(&e, 0, sizeof e); + e.c1 = 'T'; + w = v; + + for(;;){ + sleep(60*1000); + sendp(w->cevent, &e); + } +} + +char* +findfrom(void) +{ + char *p, *u; + Biobuf *b; + + u = getuser(); + if(u==nil) + return "glenda"; + + p = estrstrstrdup("/usr/", u, "/lib/newsfrom"); + b = Bopen(p, OREAD); + free(p); + if(b){ + p = Brdline(b, '\n'); + if(p){ + p[Blinelen(b)-1] = '\0'; + p = estrdup(p); + Bterm(b); + return p; + } + Bterm(b); + } + + p = estrstrstrdup("/mail/box/", u, "/headers"); + b = Bopen(p, OREAD); + free(p); + if(b){ + while(p = Brdline(b, '\n')){ + p[Blinelen(b)-1] = '\0'; + if(cistrncmp(p, "from:", 5)==0){ + p = estrdup(skip(p, "from:")); + Bterm(b); + return p; + } + } + Bterm(b); + } + + return u; +} + +void +threadmain(int argc, char **argv) +{ + char *p, *q; + Dir *d; + Window *w; + + ARGBEGIN{ + case 'D': + debug++; + break; + case 'd': + dir = EARGF(usage()); + break; + default: + usage(); + break; + }ARGEND + + if(argc != 1) + usage(); + + from = findfrom(); + + group = estrdup(argv[0]); /* someone will be cute */ + while(q=strchr(group, '/')) + *q = '.'; + + p = estrdup(argv[0]); + while(q=strchr(p, '.')) + *q = '/'; + p = estrstrstrdup(dir, "/", p); + cleanname(p); + + if((d = dirstat(p)) == nil){ /* maybe it is a new group */ + if((d = dirstat(dir)) == nil){ + fprint(2, "dirstat(%s) fails: %r\n", dir); + threadexitsall(nil); + } + if((d->mode&DMDIR)==0){ + fprint(2, "%s not a directory\n", dir); + threadexitsall(nil); + } + free(d); + if((d = dirstat(p)) == nil){ + fprint(2, "stat %s: %r\n", p); + threadexitsall(nil); + } + } + if((d->mode&DMDIR)==0){ + fprint(2, "%s not a directory\n", dir); + threadexitsall(nil); + } + free(d); + dir = estrstrdup(p, "/"); + + q = estrstrdup(dir, "post"); + canpost = access(q, AWRITE)==0; + + w = newwindow(); + root = w; + proccreate(wineventproc, w, STACK); + proccreate(timerproc, w, STACK); + + winname(w, dir); + if(canpost) + wintagwrite(w, "Newpost ", 8); + wintagwrite(w, "More ", 5); + dirwindow(w); + threadcreate(dirthread, w, STACK); + threadexits(nil); +} diff --git a/acme/news/src/util.c b/acme/news/src/util.c new file mode 100644 index 000000000..d3e6fdadc --- /dev/null +++ b/acme/news/src/util.c @@ -0,0 +1,106 @@ +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <thread.h> +#include "win.h" + +void* +emalloc(uint n) +{ + void *p; + + p = malloc(n); + if(p == nil) + error("can't malloc: %r"); + memset(p, 0, 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* +estrstrstrdup(char *r, char *s, char *t) +{ + char *u; + + u = emalloc(strlen(r)+strlen(s)+strlen(t)+1); + strcpy(u, r); + strcat(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, ...) +{ + va_list arg; + char buf[256]; + Fmt f; + + fmtfdinit(&f, 2, buf, sizeof buf); + fmtprint(&f, "%s: ", argv0); + va_start(arg, fmt); + fmtprint(&f, fmt, arg); + va_end(arg); + fmtprint(&f, "\n"); + fmtfdflush(&f); + threadexitsall(fmt); +} + +void +ctlprint(int fd, char *fmt, ...) +{ + int n; + va_list arg; + char buf[256]; + + va_start(arg, fmt); + n = vfprint(fd, fmt, arg); + va_end(arg); + if(n < 0) + error("control file write(%s) error: %r", buf); +} diff --git a/acme/news/src/win.c b/acme/news/src/win.c new file mode 100644 index 000000000..eebebd4e4 --- /dev/null +++ b/acme/news/src/win.c @@ -0,0 +1,324 @@ +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <thread.h> +#include "win.h" + +Window* +newwindow(void) +{ + char buf[12]; + Window *w; + + w = emalloc(sizeof(Window)); + 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"); + ctlprint(w->ctl, "noscroll\n"); + w->id = atoi(buf); + w->event = winopenfile(w, "event"); + w->addr = -1; /* will be opened when needed */ + w->body = nil; + w->data = -1; + w->cevent = chancreate(sizeof(Event*), 0); + if(w->cevent == nil) + error("cevent is nil: %r"); + 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; + + threadsetname("wineventproc"); + w = v; + for(i=0; ; i++){ + if(i >= NEVENT) + i = 0; + wingetevent(w, &w->e[i]); + sendp(w->cevent, &w->e[i]); + } +} + +int +winopenfile(Window *w, char *f) +{ + char buf[64]; + int fd; + + sprint(buf, "/mnt/wsys/%d/%s", w->id, f); + fd = open(buf, ORDWR|OCEXEC); + if(fd < 0) + error("can't open window file %s: %r", f); + return fd; +} + +void +wintagwrite(Window *w, char *s, int n) +{ + int fd; + + fd = winopenfile(w, "tag"); + if(write(fd, s, n) != n) + error("tag write: %r"); + close(fd); +} + +void +winname(Window *w, char *s) +{ + ctlprint(w->ctl, "name %s\n", s); +} + +void +winopenbody(Window *w, int mode) +{ + char buf[256]; + + sprint(buf, "/mnt/wsys/%d/body", w->id); + w->body = Bopen(buf, mode|OCEXEC); + if(w->body == nil) + error("can't open window body file: %r"); +} + +void +winclosebody(Window *w) +{ + if(w->body != nil){ + Bterm(w->body); + w->body = nil; + } +} + +void +winwritebody(Window *w, char *s, int n) +{ + if(w->body == nil) + winopenbody(w, OWRITE); + if(Bwrite(w->body, s, n) != n) + error("write error to window: %r"); +} + +int +wingetec(Window *w) +{ + if(w->nbuf == 0){ + w->nbuf = read(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) +{ + fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1); +} + +static int +nrunes(char *s, int nb) +{ + int i, n; + Rune r; + + n = 0; + for(i=0; i<nb; n++) + i += chartorune(&r, s+i); + return n; +} + +void +winread(Window *w, uint q0, uint q1, char *data) +{ + int m, n, nr; + char buf[256]; + + if(w->addr < 0) + w->addr = winopenfile(w, "addr"); + if(w->data < 0) + w->data = winopenfile(w, "data"); + m = q0; + while(m < q1){ + n = sprint(buf, "#%d", m); + if(write(w->addr, buf, n) != n) + error("error writing addr: %r"); + n = read(w->data, buf, sizeof buf); + if(n <= 0) + error("reading data: %r"); + nr = nrunes(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 >= 0){ + close(w->addr); + w->addr = -1; + } + if(w->body != nil){ + Bterm(w->body); + w->body = nil; + } + if(w->data >= 0){ + close(w->data); + w->data = -1; + } +} + + +int +windel(Window *w, int sure) +{ + if(sure) + write(w->ctl, "delete\n", 7); + else if(write(w->ctl, "del\n", 4) != 4) + return 0; + /* event proc will die due to read error from event file */ + windormant(w); + close(w->ctl); + w->ctl = -1; + close(w->event); + w->event = -1; + return 1; +} + +void +winclean(Window *w) +{ + if(w->body) + Bflush(w->body); + ctlprint(w->ctl, "clean\n"); +} + +int +winsetaddr(Window *w, char *addr, int errok) +{ + if(w->addr < 0) + w->addr = winopenfile(w, "addr"); + if(write(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); + } + m = Bread(w->body, s+n, na-n); + if(m <= 0) + break; + n += m; + } + s[n] = 0; + winclosebody(w); + *np = n; + return s; +} diff --git a/acme/news/src/win.h b/acme/news/src/win.h new file mode 100644 index 000000000..414e6e31b --- /dev/null +++ b/acme/news/src/win.h @@ -0,0 +1,75 @@ +/* acme */ +typedef struct Event Event; +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 */ + int ctl; + int event; + int addr; + int data; + Biobuf *body; + + /* event input */ + char buf[512]; + char *bufp; + int nbuf; + Event e[NEVENT]; + + int dirtied; + int id; + int open; + Channel *cevent; /* chan(Event*) */ +}; + +extern Window* newwindow(void); +extern int winopenfile(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 int winsetaddr(Window*, char*, int); +extern char* winreadbody(Window*, int*); +extern void windormant(Window*); +extern void winsetdump(Window*, char*, char*); + +extern char* readfile(char*, char*, int*); +extern void ctlprint(int, char*, ...); +extern void* emalloc(uint); +extern char* estrdup(char*); +extern char* estrstrdup(char*, char*); +extern char* estrstrstrdup(char*, 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*); + |