summaryrefslogtreecommitdiff
path: root/acme/news/src/news.c
diff options
context:
space:
mode:
authorcinap_lenrek <cinap_lenrek@localhost>2011-04-14 17:27:24 +0000
committercinap_lenrek <cinap_lenrek@localhost>2011-04-14 17:27:24 +0000
commita150899221a5badff9740703b754f901b4f52762 (patch)
tree3d6911874b0ee763e40490d04f56fc09d89ccfa6 /acme/news/src/news.c
parent71cfa9c637386ebe00fc6d1bf6215db6657559f4 (diff)
downloadplan9front-a150899221a5badff9740703b754f901b4f52762.tar.xz
fill /acme
Diffstat (limited to 'acme/news/src/news.c')
-rw-r--r--acme/news/src/news.c1007
1 files changed, 1007 insertions, 0 deletions
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);
+}