1008 lines
18 KiB
C
1008 lines
18 KiB
C
![]() |
/*
|
||
|
* 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);
|
||
|
}
|