]> git.lizzy.rs Git - plan9front.git/blob - acme/news/src/news.c
acme: fix more error messages
[plan9front.git] / acme / news / src / news.c
1 /*
2  * Acme interface to nntpfs.
3  */
4 #include <u.h>
5 #include <libc.h>
6 #include <bio.h>
7 #include <thread.h>
8 #include "win.h"
9 #include <ctype.h>
10
11 int canpost;
12 int debug;
13 int nshow = 20;
14
15 int lo; /* next message to look at in dir */
16 int hi; /* current hi message in dir */
17 char *dir = "/mnt/news";
18 char *group;
19 char *from;
20
21 typedef struct Article Article;
22 struct Article {
23         Ref;
24         Article *prev;
25         Article *next;
26         Window *w;
27         int n;
28         int dead;
29         int dirtied;
30         int sayspost;
31         int headers;
32         int ispost;
33 };
34
35 Article *mlist;
36 Window *root;
37
38 int
39 cistrncmp(char *a, char *b, int n)
40 {
41         while(n-- > 0){
42                 if(tolower(*a++) != tolower(*b++))
43                         return -1;
44         }
45         return 0;
46 }
47
48 int
49 cistrcmp(char *a, char *b)
50 {
51         for(;;){
52                 if(tolower(*a) != tolower(*b++))
53                         return -1;
54                 if(*a++ == 0)
55                         break;
56         }
57         return 0;
58 }
59
60 char*
61 skipwhite(char *p)
62 {
63         while(isspace(*p))
64                 p++;
65         return p;
66 }
67
68 int
69 gethi(void)
70 {
71         Dir *d;
72         int hi;
73
74         if((d = dirstat(dir)) == nil)
75                 return -1;
76         hi = d->qid.vers;
77         free(d);
78         return hi;
79 }
80
81 char*
82 fixfrom(char *s)
83 {
84         char *r, *w;
85
86         s = estrdup(s);
87         /* remove quotes */
88         for(r=w=s; *r; r++)
89                 if(*r != '"')
90                         *w++ = *r;
91         *w = '\0';
92         return s;
93 }
94
95 char *day[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", };
96 char *mon[] = {
97         "Jan", "Feb", "Mar", "Apr",
98         "May", "Jun", "Jul", "Aug",
99         "Sep", "Oct", "Nov", "Dec",
100 };
101
102 char*
103 fixdate(char *s)
104 {
105         char *f[10], *m, *t, *wd, tmp[40];
106         int d, i, j, nf, hh, mm;
107
108         nf = tokenize(s, f, nelem(f));
109
110         wd = nil;
111         d = 0;
112         m = nil;
113         t = nil;
114         for(i=0; i<nf; i++){
115                 for(j=0; j<7; j++)
116                         if(cistrncmp(day[j], f[i], 3)==0)
117                                 wd = day[j];
118                 for(j=0; j<12; j++)
119                         if(cistrncmp(mon[j], f[i], 3)==0)
120                                 m = mon[j];
121                 j = atoi(f[i]);
122                 if(1 <= j && j <= 31 && d != 0)
123                         d = j;
124                 if(strchr(f[i], ':'))
125                         t = f[i];
126         }
127
128         if(d==0 || wd==nil || m==nil || t==nil)
129                 return nil;
130
131         hh = strtol(t, 0, 10);
132         mm = strtol(strchr(t, ':')+1, 0, 10);
133         sprint(tmp, "%s %d %s %d:%.2d", wd, d, m, hh, mm);
134         return estrdup(tmp);
135 }
136
137 void
138 msgheadline(Biobuf *bin, int n, Biobuf *bout)
139 {
140         char *p, *q;
141         char *date;
142         char *from;
143         char *subject;
144
145         date = nil;
146         from = nil;
147         subject = nil;
148         while(p = Brdline(bin, '\n')){
149                 p[Blinelen(bin)-1] = '\0';
150                 if((q = strchr(p, ':')) == nil)
151                         continue;
152                 *q++ = '\0';
153                 if(cistrcmp(p, "from")==0)
154                         from = fixfrom(skipwhite(q));
155                 else if(cistrcmp(p, "subject")==0)
156                         subject = estrdup(skipwhite(q));
157                 else if(cistrcmp(p, "date")==0)
158                         date = fixdate(skipwhite(q));
159         }
160
161         Bprint(bout, "%d/\t%s", n, from ? from : "");
162         if(date)
163                 Bprint(bout, "\t%s", date);
164         if(subject)
165                 Bprint(bout, "\n\t%s", subject);
166         Bprint(bout, "\n");
167
168         free(date);
169         free(from);
170         free(subject);
171 }
172
173 /*
174  * Write the headers for at most nshow messages to b,
175  * starting with hi and working down to lo.
176  * Return number of first message not scanned.
177  */
178 int
179 adddir(Biobuf *body, int hi, int lo, int nshow)
180 {
181         char *p, *q, tmp[40];
182         int i, n;
183         Biobuf *b;
184
185         n = 0;
186         for(i=hi; i>=lo && n<nshow; i--){
187                 sprint(tmp, "%d", i);
188                 p = estrstrdup(dir, tmp);
189                 if(access(p, OREAD) < 0){
190                         free(p);
191                         break;
192                 }
193                 q = estrstrdup(p, "/header");
194                 free(p);
195                 b = Bopen(q, OREAD);
196                 free(q);
197                 if(b == nil)
198                         continue;
199                 msgheadline(b, i, body);
200                 Bterm(b);
201                 n++;
202         }
203         return i;
204 }
205
206 /* 
207  * Show the first nshow messages in the window.
208  * This depends on nntpfs presenting contiguously
209  * numbered directories, and on the qid version being
210  * the topmost numbered directory.
211  */
212 void
213 dirwindow(Window *w)
214 {
215         if((hi=gethi()) < 0)
216                 return;
217
218         if(w->data < 0)
219                 w->data = winopenfile(w, "data");
220
221         fprint(w->ctl, "dirty\n");
222         
223         winopenbody(w, OWRITE);
224         lo = adddir(w->body, hi, 0, nshow);
225         winclean(w);
226 }
227
228 /* return 1 if handled, 0 otherwise */
229 static int
230 iscmd(char *s, char *cmd)
231 {
232         int len;
233
234         len = strlen(cmd);
235         return strncmp(s, cmd, len)==0 && (s[len]=='\0' || s[len]==' ' || s[len]=='\t' || s[len]=='\n');
236 }
237
238 static char*
239 skip(char *s, char *cmd)
240 {
241         s += strlen(cmd);
242         while(*s==' ' || *s=='\t' || *s=='\n')
243                 s++;
244         return s;
245 }
246
247 void
248 unlink(Article *m)
249 {
250         if(mlist == m)
251                 mlist = m->next;
252
253         if(m->next)
254                 m->next->prev = m->prev;
255         if(m->prev)
256                 m->prev->next = m->next;
257         m->next = m->prev = nil;
258 }
259
260 int mesgopen(char*);
261 int fillmesgwindow(int, Article*);
262 Article *newpost(void);
263 void replywindow(Article*);
264 void mesgpost(Article*);
265
266 /*
267  * Depends on d.qid.vers being highest numbered message in dir.
268  */
269 void
270 acmetimer(Article *m, Window *w)
271 {
272         Biobuf *b;
273         Dir *d;
274
275         assert(m==nil && w==root);
276
277         if((d = dirstat(dir))==nil | hi==d->qid.vers){
278                 free(d);
279                 return;
280         }
281
282         if(w->data < 0)
283                 w->data = winopenfile(w, "data");
284         if(winsetaddr(w, "0", 0))
285                 write(w->data, "", 0);
286
287         b = emalloc(sizeof(*b));
288         Binit(b, w->data, OWRITE);
289         adddir(b, d->qid.vers, hi+1, d->qid.vers);
290         hi = d->qid.vers;
291         Bterm(b);
292         free(b);
293         free(d);
294         winselect(w, "0,.", 0);
295 }
296
297 int
298 acmeload(Article*, Window*, char *s)
299 {
300         int nopen;
301
302 //fprint(2, "load %s\n", s);
303
304         nopen = 0;
305         while(*s){
306                 /* skip directory name */
307                 if(strncmp(s, dir, strlen(dir))==0)
308                         s += strlen(dir);
309                 nopen += mesgopen(s);
310                 if((s = strchr(s, '\n')) == nil)
311                         break;
312                 s = skip(s, "");
313         }
314         return nopen;
315 }
316
317 int
318 acmecmd(Article *m, Window *w, char *s)
319 {
320         int n;
321         Biobuf *b;
322
323 //fprint(2, "cmd %s\n", s);
324
325         s = skip(s, "");
326
327         if(iscmd(s, "Del")){
328                 if(m == nil){   /* don't close dir until messages close */
329                         if(mlist != nil){
330                                 ctlprint(mlist->w->ctl, "show\n");
331                                 return 1;
332                         }
333                         if(windel(w, 0))
334                                 threadexitsall(nil);
335                         return 1;
336                 }else{
337                         if(windel(w, 0))
338                                 m->dead = 1;
339                         return 1;
340                 }
341         }
342         if(m==nil && iscmd(s, "More")){
343                 s = skip(s, "More");
344                 if(n = atoi(s))
345                         nshow = n;
346
347                 if(w->data < 0)
348                         w->data = winopenfile(w, "data");
349                 winsetaddr(w, "$", 1);
350         
351                 fprint(w->ctl, "dirty\n");
352
353                 b = emalloc(sizeof(*b));
354                 Binit(b, w->data, OWRITE);
355                 lo = adddir(b, lo, 0, nshow);
356                 Bterm(b);
357                 free(b);                
358                 winclean(w);
359                 winsetaddr(w, ".,", 0);
360         }
361         if(m!=nil && !m->ispost && iscmd(s, "Headers")){
362                 m->headers = !m->headers;
363                 fillmesgwindow(-1, m);
364                 return 1;
365         }
366         if(iscmd(s, "Newpost")){
367                 m = newpost();
368                 winopenbody(m->w, OWRITE);
369                 Bprint(m->w->body, "%s\nsubject: \n\n", group);
370                 winclean(m->w);
371                 winselect(m->w, "$", 0);
372                 return 1;
373         }
374         if(m!=nil && !m->ispost && iscmd(s, "Reply")){
375                 replywindow(m);
376                 return 1;
377         }
378 //      if(m!=nil && iscmd(s, "Replymail")){
379 //              fprint(2, "no replymail yet\n");
380 //              return 1;
381 //      }
382         if(iscmd(s, "Post")){
383                 mesgpost(m);
384                 return 1;
385         }
386         return 0;
387 }
388
389 void
390 acmeevent(Article *m, Window *w, Event *e)
391 {
392         Event *ea, *e2, *eq;
393         char *s, *t, *buf;
394         int na;
395         //int n;
396         //ulong q0, q1;
397
398         switch(e->c1){  /* origin of action */
399         default:
400         Unknown:
401                 fprint(2, "unknown message %c%c\n", e->c1, e->c2);
402                 break;
403
404         case 'T':       /* bogus timer event! */
405                 acmetimer(m, w);
406                 break;
407
408         case 'F':       /* generated by our actions; ignore */
409                 break;
410
411         case 'E':       /* write to body or tag; can't affect us */
412                 break;
413
414         case 'K':       /* type away; we don't care */
415                 if(m && (e->c2 == 'I' || e->c2 == 'D')){
416                         m->dirtied = 1;
417                         if(!m->sayspost){
418                                 wintagwrite(w, "Post ", 5);
419                                 m->sayspost = 1;
420                         }
421                 }
422                 break;
423
424         case 'M':       /* mouse event */
425                 switch(e->c2){          /* type of action */
426                 case 'x':       /* mouse: button 2 in tag */
427                 case 'X':       /* mouse: button 2 in body */
428                         ea = nil;
429                         //e2 = nil;
430                         s = e->b;
431                         if(e->flag & 2){        /* null string with non-null expansion */
432                                 e2 = recvp(w->cevent);
433                                 if(e->nb==0)
434                                         s = e2->b;
435                         }
436                         if(e->flag & 8){        /* chorded argument */
437                                 ea = recvp(w->cevent);  /* argument */
438                                 na = ea->nb;
439                                 recvp(w->cevent);               /* ignore origin */
440                         }else
441                                 na = 0;
442                         
443                         /* append chorded arguments */
444                         if(na){
445                                 t = emalloc(strlen(s)+1+na+1);
446                                 sprint(t, "%s %s", s, ea->b);
447                                 s = t;
448                         }
449                         /* if it's a known command, do it */
450                         /* if it's a long message, it can't be for us anyway */
451                 //      DPRINT(2, "exec: %s\n", s);
452                         if(!acmecmd(m, w, s))   /* send it back */
453                                 winwriteevent(w, e);
454                         if(na)
455                                 free(s);
456                         break;
457
458                 case 'l':       /* mouse: button 3 in tag */
459                 case 'L':       /* mouse: button 3 in body */
460                         //buf = nil;
461                         eq = e;
462                         if(e->flag & 2){
463                                 e2 = recvp(w->cevent);
464                                 eq = e2;
465                         }
466                         s = eq->b;
467                         if(eq->q1>eq->q0 && eq->nb==0){
468                                 buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
469                                 winread(w, eq->q0, eq->q1, buf);
470                                 s = buf;
471                         }
472                         if(!acmeload(m, w, s))
473                                 winwriteevent(w, e);
474                         break;
475
476                 case 'i':       /* mouse: text inserted in tag */
477                 case 'd':       /* mouse: text deleted from tag */
478                         break;
479
480                 case 'I':       /* mouse: text inserted in body */
481                 case 'D':       /* mouse: text deleted from body */
482                         if(m == nil)
483                                 break;
484
485                         m->dirtied = 1;
486                         if(!m->sayspost){
487                                 wintagwrite(w, "Post ", 5);
488                                 m->sayspost = 1;
489                         }
490                         break;
491
492                 default:
493                         goto Unknown;
494                 }
495         }
496 }
497
498 void
499 dirthread(void *v)
500 {
501         Event *e;
502         Window *w;
503
504         w = v;
505         while(e = recvp(w->cevent))
506                 acmeevent(nil, w, e);
507
508         threadexitsall(nil);
509 }
510
511 void
512 mesgthread(void *v)
513 {
514         Event *e;
515         Article *m;
516
517         m = v;
518         while(!m->dead && (e = recvp(m->w->cevent)))
519                 acmeevent(m, m->w, e);
520
521 //fprint(2, "msg %p exits\n", m);
522         unlink(m);
523         free(m->w);
524         free(m);
525         threadexits(nil);
526 }
527
528 /*
529 Xref: news.research.att.com comp.os.plan9:7360
530 Newsgroups: comp.os.plan9
531 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
532 From: Stephen Adam <saadam@bigpond.com>
533 Subject: Future of Plan9
534 Approved: plan9mod@bath.ac.uk
535 X-Newsreader: Microsoft Outlook Express 5.00.2014.211
536 X-Mimeole: Produced By Microsoft MimeOLE V5.00.2014.211
537 Sender: ccsdhd@bath.ac.uk (Dennis Davis)
538 Nntp-Posting-Date: Wed, 13 Dec 2000 21:28:45 EST
539 NNTP-Posting-Host: 203.54.121.233
540 Organization: Telstra BigPond Internet Services (http://www.bigpond.com)
541 X-Date: Wed, 13 Dec 2000 20:43:37 +1000
542 Lines: 12
543 Message-ID: <xbIZ5.157945$e5.114349@newsfeeds.bigpond.com>
544 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>
545 X-Msmail-Priority: Normal
546 X-Trace: newsfeeds.bigpond.com 976703325 203.54.121.233 (Wed, 13 Dec 2000 21:28:45 EST)
547 X-Priority: 3
548 Date: Wed, 13 Dec 2000 10:49:50 GMT
549 */
550
551 char *skipheader[] = 
552 {
553         "x-",
554         "path:",
555         "xref:",
556         "approved:",
557         "sender:",
558         "nntp-",
559         "organization:",
560         "lines:",
561         "message-id:",
562         "references:",
563         "reply-to:",
564         "mime-",
565         "content-",
566 };
567
568 int
569 fillmesgwindow(int fd, Article *m)
570 {
571         Biobuf *b;
572         char *p, tmp[40];
573         int i, inhdr, copy, xfd;
574         Window *w;
575
576         xfd = -1;
577         if(fd == -1){
578                 sprint(tmp, "%d/article", m->n);
579                 p = estrstrdup(dir, tmp);
580                 if((xfd = open(p, OREAD)) < 0){
581                         free(p);        
582                         return 0;
583                 }
584                 free(p);
585                 fd = xfd;
586         }
587
588         w = m->w;
589         if(w->data < 0)
590                 w->data = winopenfile(w, "data");
591         if(winsetaddr(w, ",", 0))
592                 write(w->data, "", 0);
593
594         winopenbody(m->w, OWRITE);
595         b = emalloc(sizeof(*b));
596         Binit(b, fd, OREAD);
597
598         inhdr = 1;
599         copy = 1;
600         while(p = Brdline(b, '\n')){
601                 if(Blinelen(b)==1)
602                         inhdr = 0, copy=1;
603                 if(inhdr && !isspace(p[0])){
604                         copy = 1;
605                         if(!m->headers){
606                                 if(cistrncmp(p, "from:", 5)==0){
607                                         p[Blinelen(b)-1] = '\0';
608                                         p = fixfrom(skip(p, "from:"));
609                                         Bprint(m->w->body, "From: %s\n", p);
610                                         free(p);
611                                         copy = 0;
612                                         continue;
613                                 }
614                                 for(i=0; i<nelem(skipheader); i++)
615                                         if(cistrncmp(p, skipheader[i], strlen(skipheader[i]))==0)
616                                                 copy=0;
617                         }
618                 }
619                 if(copy)
620                         Bwrite(m->w->body, p, Blinelen(b));
621         }
622         Bterm(b);
623         free(b);
624         winclean(m->w);
625         if(xfd != -1)
626                 close(xfd);
627         return 1;
628 }
629
630 Article*
631 newpost(void)
632 {
633         Article *m;
634         char *p, tmp[40];
635         static int nnew;
636
637         m = emalloc(sizeof *m);
638         sprint(tmp, "Post%d", ++nnew);
639         p = estrstrdup(dir, tmp);
640
641         m->w = newwindow();
642         proccreate(wineventproc, m->w, STACK);
643         winname(m->w, p);
644         wintagwrite(m->w, "Post ", 5);
645         m->sayspost = 1;
646         m->ispost = 1;
647         threadcreate(mesgthread, m, STACK);
648
649         if(mlist){
650                 m->next = mlist;
651                 mlist->prev = m;
652         }
653         mlist = m;
654         return m;
655 }
656
657 void
658 replywindow(Article *m)
659 {
660         Biobuf *b;
661         char *p, *ep, *q, tmp[40];
662         int fd, copy;
663         Article *reply;
664
665         sprint(tmp, "%d/article", m->n);
666         p = estrstrdup(dir, tmp);
667         if((fd = open(p, OREAD)) < 0){
668                 free(p);        
669                 return;
670         }
671         free(p);
672
673         reply = newpost();
674         winopenbody(reply->w, OWRITE);
675         b = emalloc(sizeof(*b));
676         Binit(b, fd, OREAD);
677         copy = 0;
678         while(p = Brdline(b, '\n')){
679                 if(Blinelen(b)==1)
680                         break;
681                 ep = p+Blinelen(b);
682                 if(!isspace(*p)){
683                         copy = 0;
684                         if(cistrncmp(p, "newsgroups:", 11)==0){
685                                 for(q=p+11; *q!='\n'; q++)
686                                         if(*q==',')
687                                                 *q = ' ';
688                                 copy = 1;
689                         }else if(cistrncmp(p, "subject:", 8)==0){
690                                 if(!strstr(p, " Re:") && !strstr(p, " RE:") && !strstr(p, " re:")){
691                                         p = skip(p, "subject:");
692                                         ep[-1] = '\0';
693                                         Bprint(reply->w->body, "Subject: Re: %s\n", p);
694                                 }else
695                                         copy = 1;
696                         }else if(cistrncmp(p, "message-id:", 11)==0){
697                                 Bprint(reply->w->body, "References: ");
698                                 p += 11;
699                                 copy = 1;
700                         }
701                 }
702                 if(copy)
703                         Bwrite(reply->w->body, p, ep-p);
704         }
705         Bterm(b);
706         close(fd);
707         free(b);
708         Bprint(reply->w->body, "\n");
709         winclean(reply->w);
710         winselect(reply->w, "$", 0);
711 }
712
713 char*
714 skipbl(char *s, char *e)
715 {
716         while(s < e){
717                 if(*s!=' ' && *s!='\t' && *s!=',')
718                         break;
719                 s++;
720         }
721         return s;
722 }
723
724 char*
725 findbl(char *s, char *e)
726 {
727         while(s < e){
728                 if(*s==' ' || *s=='\t' || *s==',')
729                         break;
730                 s++;
731         }
732         return s;
733 }
734
735 /*
736  * comma-separate possibly blank-separated strings in line; e points before newline
737  */
738 void
739 commas(char *s, char *e)
740 {
741         char *t;
742
743         /* may have initial blanks */
744         s = skipbl(s, e);
745         while(s < e){
746                 s = findbl(s, e);
747                 if(s == e)
748                         break;
749                 t = skipbl(s, e);
750                 if(t == e)      /* no more words */
751                         break;
752                 /* patch comma */
753                 *s++ = ',';
754                 while(s < t)
755                         *s++ = ' ';
756         }
757 }
758 void
759 mesgpost(Article *m)
760 {
761         Biobuf *b;
762         char *p, *ep;
763         int isfirst, ishdr, havegroup, havefrom;
764
765         p = estrstrdup(dir, "post");
766         if((b = Bopen(p, OWRITE)) == nil){
767                 fprint(2, "cannot open %s: %r\n", p);
768                 free(p);
769                 return;
770         }
771         free(p);
772
773         winopenbody(m->w, OREAD);
774         ishdr = 1;
775         isfirst = 1;
776         havegroup = havefrom = 0;
777         while(p = Brdline(m->w->body, '\n')){
778                 ep = p+Blinelen(m->w->body);
779                 if(ishdr && p+1==ep){
780                         if(!havegroup)
781                                 Bprint(b, "Newsgroups: %s\n", group);
782                         if(!havefrom)
783                                 Bprint(b, "From: %s\n", from);
784                         ishdr = 0;
785                 }
786                 if(ishdr){
787                         ep[-1] = '\0';
788                         if(isfirst && strchr(p, ':')==0){       /* group list */
789                                 commas(p, ep);
790                                 Bprint(b, "newsgroups: %s\n", p);
791                                 havegroup = 1;
792                                 isfirst = 0;
793                                 continue;
794                         }
795                         if(cistrncmp(p, "newsgroup:", 10)==0){
796                                 commas(skip(p, "newsgroup:"), ep);
797                                 Bprint(b, "newsgroups: %s\n", skip(p, "newsgroup:"));
798                                 havegroup = 1;
799                                 continue;
800                         }
801                         if(cistrncmp(p, "newsgroups:", 11)==0){
802                                 commas(skip(p, "newsgroups:"), ep);
803                                 Bprint(b, "newsgroups: %s\n", skip(p, "newsgroups:"));
804                                 havegroup = 1;
805                                 continue;
806                         }
807                         if(cistrncmp(p, "from:", 5)==0)
808                                 havefrom = 1;
809                         ep[-1] = '\n';
810                 }
811                 Bwrite(b, p, ep-p);
812         }
813         winclosebody(m->w);
814         Bflush(b);
815         if(write(Bfildes(b), "", 0) == 0)
816                 winclean(m->w);
817         else
818                 fprint(2, "post: %r\n");
819         Bterm(b);
820 }
821
822 int
823 mesgopen(char *s)
824 {
825         char *p, tmp[40];
826         int fd, n;
827         Article *m;
828
829         n = atoi(s);
830         if(n==0)
831                 return 0;
832
833         for(m=mlist; m; m=m->next){
834                 if(m->n == n){
835                         ctlprint(m->w->ctl, "show\n");
836                         return 1;
837                 }
838         }
839
840         sprint(tmp, "%d/article", n);
841         p = estrstrdup(dir, tmp);
842         if((fd = open(p, OREAD)) < 0){
843                 free(p);        
844                 return 0;
845         }
846
847         m = emalloc(sizeof(*m));
848         m->w = newwindow();
849         m->n = n;
850         proccreate(wineventproc, m->w, STACK);
851         p[strlen(p)-strlen("article")] = '\0';
852         winname(m->w, p);
853         if(canpost)
854                 wintagwrite(m->w, "Reply ", 6);
855         wintagwrite(m->w, "Headers ", 8);
856
857         free(p);
858         if(mlist){
859                 m->next = mlist;
860                 mlist->prev = m;
861         }
862         mlist = m;
863         threadcreate(mesgthread, m, STACK);
864
865         fillmesgwindow(fd, m);
866         close(fd);
867         windormant(m->w);
868         return 1;
869 }
870
871 void
872 usage(void)
873 {
874         fprint(2, "usage: News [-d /mnt/news] comp.os.plan9\n");
875         exits("usage");
876 }
877
878 void
879 timerproc(void *v)
880 {
881         Event e;
882         Window *w;
883
884         memset(&e, 0, sizeof e);
885         e.c1 = 'T';
886         w = v;
887
888         for(;;){
889                 sleep(60*1000);
890                 sendp(w->cevent, &e);
891         }
892 }
893
894 char*
895 findfrom(void)
896 {
897         char *p, *u;
898         Biobuf *b;
899
900         u = getuser();
901         if(u==nil)
902                 return "glenda";
903
904         p = estrstrstrdup("/usr/", u, "/lib/newsfrom");
905         b = Bopen(p, OREAD);
906         free(p);
907         if(b){
908                 p = Brdline(b, '\n');
909                 if(p){
910                         p[Blinelen(b)-1] = '\0';
911                         p = estrdup(p);
912                         Bterm(b);
913                         return p;
914                 }
915                 Bterm(b);
916         }
917
918         p = estrstrstrdup("/mail/box/", u, "/headers");
919         b = Bopen(p, OREAD);
920         free(p);
921         if(b){
922                 while(p = Brdline(b, '\n')){
923                         p[Blinelen(b)-1] = '\0';
924                         if(cistrncmp(p, "from:", 5)==0){
925                                 p = estrdup(skip(p, "from:"));
926                                 Bterm(b);
927                                 return p;
928                         }
929                 }
930                 Bterm(b);
931         }
932
933         return u;
934 }
935
936 void
937 threadmain(int argc, char **argv)
938 {
939         char *p, *q;
940         Dir *d;
941         Window *w;
942
943         ARGBEGIN{
944         case 'D':
945                 debug++;
946                 break;
947         case 'd':
948                 dir = EARGF(usage());
949                 break;
950         default:
951                 usage();
952                 break;
953         }ARGEND
954
955         if(argc != 1)
956                 usage();
957
958         from = findfrom();
959
960         group = estrdup(argv[0]);       /* someone will be cute */
961         while(q=strchr(group, '/'))
962                 *q = '.';
963
964         p = estrdup(argv[0]);
965         while(q=strchr(p, '.'))
966                 *q = '/';
967         p = estrstrstrdup(dir, "/", p);
968         cleanname(p);
969
970         if((d = dirstat(p)) == nil){    /* maybe it is a new group */
971                 if((d = dirstat(dir)) == nil){
972                         fprint(2, "dirstat(%s) fails: %r\n", dir);
973                         threadexitsall(nil);
974                 }
975                 if((d->mode&DMDIR)==0){
976                         fprint(2, "%s not a directory\n", dir);
977                         threadexitsall(nil);
978                 }
979                 free(d);
980                 if((d = dirstat(p)) == nil){
981                         fprint(2, "stat %s: %r\n", p);
982                         threadexitsall(nil);
983                 }
984         }
985         if((d->mode&DMDIR)==0){
986                 fprint(2, "%s not a directory\n", dir);
987                 threadexitsall(nil);
988         }
989         free(d);
990         dir = estrstrdup(p, "/");
991
992         q = estrstrdup(dir, "post");
993         canpost = access(q, AWRITE)==0;
994
995         w = newwindow();
996         root = w;
997         proccreate(wineventproc, w, STACK);
998         proccreate(timerproc, w, STACK);
999
1000         winname(w, dir);
1001         if(canpost)
1002                 wintagwrite(w, "Newpost ", 8);
1003         wintagwrite(w, "More ", 5);
1004         dirwindow(w);
1005         threadcreate(dirthread, w, STACK);
1006         threadexits(nil);
1007 }