1 #include "awiki.h"
2
3 Wiki *wlist;
4
5 void
link(Wiki * w)6 link(Wiki *w)
7 {
8 if(w->linked)
9 return;
10 w->linked = 1;
11 w->prev = nil;
12 w->next = wlist;
13 if(wlist)
14 wlist->prev = w;
15 wlist = w;
16 }
17
18 void
unlink(Wiki * w)19 unlink(Wiki *w)
20 {
21 if(!w->linked)
22 return;
23 w->linked = 0;
24
25 if(w->next)
26 w->next->prev = w->prev;
27 if(w->prev)
28 w->prev->next = w->next;
29 else
30 wlist = w->next;
31
32 w->next = nil;
33 w->prev = nil;
34 }
35
36 void
wikiname(Window * w,char * name)37 wikiname(Window *w, char *name)
38 {
39 char *p, *q;
40
41 p = emalloc(strlen(dir)+1+strlen(name)+1+1);
42 strcpy(p, dir);
43 strcat(p, "/");
44 strcat(p, name);
45 for(q=p; *q; q++)
46 if(*q==' ')
47 *q = '_';
48 winname(w, p);
49 free(p);
50 }
51
52 int
wikiput(Wiki * w)53 wikiput(Wiki *w)
54 {
55 int fd, n;
56 char buf[1024], *p;
57 Biobuf *b;
58
59 if((fd = open("new", ORDWR)) < 0){
60 fprint(2, "Wiki: cannot open raw: %r\n");
61 return -1;
62 }
63
64 winopenbody(w->win, OREAD);
65 b = w->win->body;
66 if((p = Brdline(b, '\n'))==nil){
67 Short:
68 winclosebody(w->win);
69 fprint(2, "Wiki: no data\n");
70 close(fd);
71 return -1;
72 }
73 write(fd, p, Blinelen(b));
74
75 snprint(buf, sizeof buf, "D%lud\n", w->time);
76 if(email)
77 snprint(buf+strlen(buf), sizeof(buf)-strlen(buf), "A%s\n", email);
78
79 if(Bgetc(b) == '#'){
80 p = Brdline(b, '\n');
81 if(p == nil)
82 goto Short;
83 snprint(buf+strlen(buf), sizeof(buf)-strlen(buf), "C%s\n", p);
84 }
85 write(fd, buf, strlen(buf));
86 write(fd, "\n\n", 2);
87
88 while((n = Bread(b, buf, sizeof buf)) > 0)
89 write(fd, buf, n);
90 winclosebody(w->win);
91
92 werrstr("");
93 if((n=write(fd, "", 0)) != 0){
94 fprint(2, "Wiki commit %lud %d %d: %r\n", w->time, fd, n);
95 close(fd);
96 return -1;
97 }
98 seek(fd, 0, 0);
99 if((n = read(fd, buf, 300)) < 0){
100 fprint(2, "Wiki readback: %r\n");
101 close(fd);
102 return -1;
103 }
104 close(fd);
105 buf[n] = '\0';
106 sprint(buf, "%s/", buf);
107 free(w->arg);
108 w->arg = estrdup(buf);
109 w->isnew = 0;
110 wikiget(w);
111 wikiname(w->win, w->arg);
112 return n;
113 }
114
115 void
wikiget(Wiki * w)116 wikiget(Wiki *w)
117 {
118 char *p;
119 int fd, normal;
120 Biobuf *bin;
121
122 fprint(w->win->ctl, "dirty\n");
123
124 p = emalloc(strlen(w->arg)+8+1);
125 strcpy(p, w->arg);
126 normal = 1;
127 if(p[strlen(p)-1] == '/'){
128 normal = 0;
129 strcat(p, "current");
130 }else if(strlen(p)>8 && strcmp(p+strlen(p)-8, "/current")==0){
131 normal = 0;
132 w->arg[strlen(w->arg)-7] = '\0';
133 }
134
135 if((fd = open(p, OREAD)) < 0){
136 fprint(2, "Wiki: cannot read %s: %r\n", p);
137 winclean(w->win);
138 return;
139 }
140 free(p);
141
142 winopenbody(w->win, OWRITE);
143 bin = emalloc(sizeof(*bin));
144 Binit(bin, fd, OREAD);
145
146 p = nil;
147 if(!normal){
148 if((p = Brdline(bin, '\n')) == nil){
149 fprint(2, "Wiki: cannot read title: %r\n");
150 winclean(w->win);
151 close(fd);
152 free(bin);
153 return;
154 }
155 p[Blinelen(bin)-1] = '\0';
156 }
157 /* clear window */
158 if(w->win->data < 0)
159 w->win->data = winopenfile(w->win, "data");
160 if(winsetaddr(w->win, ",", 0))
161 write(w->win->data, "", 0);
162
163 if(!normal)
164 Bprint(w->win->body, "%s\n\n", p);
165
166 while(p = Brdline(bin, '\n')){
167 p[Blinelen(bin)-1] = '\0';
168 if(normal)
169 Bprint(w->win->body, "%s\n", p);
170 else{
171 if(p[0]=='D')
172 w->time = strtoul(p+1, 0, 10);
173 else if(p[0]=='#')
174 Bprint(w->win->body, "%s\n", p+1);
175 }
176 }
177 winclean(w->win);
178 free(bin);
179 close(fd);
180 }
181
182 static int
iscmd(char * s,char * cmd)183 iscmd(char *s, char *cmd)
184 {
185 int len;
186
187 len = strlen(cmd);
188 return strncmp(s, cmd, len)==0 && (s[len]=='\0' || s[len]==' ' || s[len]=='\t' || s[len]=='\n');
189 }
190
191 static char*
skip(char * s,char * cmd)192 skip(char *s, char *cmd)
193 {
194 s += strlen(cmd);
195 while(*s==' ' || *s=='\t' || *s=='\n')
196 s++;
197 return s;
198 }
199
200 int
wikiload(Wiki * w,char * arg)201 wikiload(Wiki *w, char *arg)
202 {
203 char *p, *q, *path, *addr;
204 int rv;
205
206 p = nil;
207 if(arg[0] == '/')
208 path = arg;
209 else{
210 p = emalloc(strlen(w->arg)+1+strlen(arg)+1);
211 strcpy(p, w->arg);
212 if(q = strrchr(p, '/')){
213 ++q;
214 *q = '\0';
215 }else
216 *p = '\0';
217 strcat(p, arg);
218 cleanname(p);
219 path = p;
220 }
221 if(addr=strchr(path, ':'))
222 *addr++ = '\0';
223
224 rv = wikiopen(path, addr)==0;
225 free(p);
226 if(rv)
227 return 1;
228 return wikiopen(arg, 0)==0;
229 }
230
231 /* return 1 if handled, 0 otherwise */
232 int
wikicmd(Wiki * w,char * s)233 wikicmd(Wiki *w, char *s)
234 {
235 char *p;
236 s = skip(s, "");
237
238 if(iscmd(s, "Del")){
239 if(windel(w->win, 0))
240 w->dead = 1;
241 return 1;
242 }
243 if(iscmd(s, "New")){
244 wikinew(skip(s, "New"));
245 return 1;
246 }
247 if(iscmd(s, "History"))
248 return wikiload(w, "history.txt");
249 if(iscmd(s, "Diff"))
250 return wikidiff(w);
251 if(iscmd(s, "Get")){
252 if(winisdirty(w->win) && !w->win->warned){
253 w->win->warned = 1;
254 fprint(2, "%s/%s modified\n", dir, w->arg);
255 }else{
256 w->win->warned = 0;
257 wikiget(w);
258 }
259 return 1;
260 }
261 if(iscmd(s, "Put")){
262 if((p=strchr(w->arg, '/')) && p[1]!='\0')
263 fprint(2, "%s/%s is read-only\n", dir, w->arg);
264 else
265 wikiput(w);
266 return 1;
267 }
268 return 0;
269 }
270
271 /* need to expand selection more than default word */
272 static long
eval(Window * w,char * s,...)273 eval(Window *w, char *s, ...)
274 {
275 char buf[64];
276 va_list arg;
277
278 va_start(arg, s);
279 vsnprint(buf, sizeof buf, s, arg);
280 va_end(arg);
281
282 if(winsetaddr(w, buf, 1)==0)
283 return -1;
284
285 if(pread(w->addr, buf, 24, 0) != 24)
286 return -1;
287 return strtol(buf, 0, 10);
288 }
289
290 static int
getdot(Window * w,long * q0,long * q1)291 getdot(Window *w, long *q0, long *q1)
292 {
293 char buf[24];
294
295 ctlprint(w->ctl, "addr=dot\n");
296 if(pread(w->addr, buf, 24, 0) != 24)
297 return -1;
298 *q0 = atoi(buf);
299 *q1 = atoi(buf+12);
300 return 0;
301 }
302
303 static Event*
expand(Window * w,Event * e,Event * eacme)304 expand(Window *w, Event *e, Event *eacme)
305 {
306 long q0, q1, x;
307
308 if(getdot(w, &q0, &q1)==0 && q0 <= e->q0 && e->q0 <= q1){
309 e->q0 = q0;
310 e->q1 = q1;
311 return e;
312 }
313
314 q0 = eval(w, "#%lud-/\\[/", e->q0);
315 if(q0 < 0)
316 return eacme;
317 if(eval(w, "#%lud+/\\]/", q0) < e->q0) /* [ closes before us */
318 return eacme;
319 q1 = eval(w, "#%lud+/\\]/", e->q1);
320 if(q1 < 0)
321 return eacme;
322 if((x=eval(w, "#%lud-/\\[/", q1))==-1 || x > e->q1) /* ] opens after us */
323 return eacme;
324 e->q0 = q0+1;
325 e->q1 = q1;
326 return e;
327 }
328
329 void
acmeevent(Wiki * wiki,Event * e)330 acmeevent(Wiki *wiki, Event *e)
331 {
332 Event *ea, *e2, *eq;
333 Window *w;
334 char *s, *t, *buf;
335 int na;
336
337 w = wiki->win;
338 switch(e->c1){ /* origin of action */
339 default:
340 Unknown:
341 fprint(2, "unknown message %c%c\n", e->c1, e->c2);
342 break;
343
344 case 'F': /* generated by our actions; ignore */
345 break;
346
347 case 'E': /* write to body or tag; can't affect us */
348 break;
349
350 case 'K': /* type away; we don't care */
351 if(e->c2 == 'I' || e->c2 == 'D')
352 w->warned = 0;
353 break;
354
355 case 'M': /* mouse event */
356 switch(e->c2){ /* type of action */
357 case 'x': /* mouse: button 2 in tag */
358 case 'X': /* mouse: button 2 in body */
359 ea = nil;
360 //e2 = nil;
361 s = e->b;
362 if(e->flag & 2){ /* null string with non-null expansion */
363 e2 = recvp(w->cevent);
364 if(e->nb==0)
365 s = e2->b;
366 }
367 if(e->flag & 8){ /* chorded argument */
368 ea = recvp(w->cevent); /* argument */
369 na = ea->nb;
370 recvp(w->cevent); /* ignore origin */
371 }else
372 na = 0;
373
374 /* append chorded arguments */
375 if(na){
376 t = emalloc(strlen(s)+1+na+1);
377 sprint(t, "%s %s", s, ea->b);
378 s = t;
379 }
380 /* if it's a known command, do it */
381 /* if it's a long message, it can't be for us anyway */
382 // DPRINT(2, "exec: %s\n", s);
383 if(!wikicmd(wiki, s)) /* send it back */
384 winwriteevent(w, e);
385 if(na)
386 free(s);
387 break;
388
389 case 'l': /* mouse: button 3 in tag */
390 case 'L': /* mouse: button 3 in body */
391 //buf = nil;
392 eq = e;
393 if(e->flag & 2){ /* we do our own expansion for loads */
394 e2 = recvp(w->cevent);
395 eq = expand(w, eq, e2);
396 }
397 s = eq->b;
398 if(eq->q1>eq->q0 && eq->nb==0){
399 buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
400 winread(w, eq->q0, eq->q1, buf);
401 s = buf;
402 }
403 if(!wikiload(wiki, s))
404 winwriteevent(w, e);
405 break;
406
407 case 'i': /* mouse: text inserted in tag */
408 case 'd': /* mouse: text deleted from tag */
409 break;
410
411 case 'I': /* mouse: text inserted in body */
412 case 'D': /* mouse: text deleted from body */
413 w->warned = 0;
414 break;
415
416 default:
417 goto Unknown;
418 }
419 }
420 }
421
422 void
wikithread(void * v)423 wikithread(void *v)
424 {
425 char tmp[40];
426 Event *e;
427 Wiki *w;
428
429 w = v;
430
431 if(w->isnew){
432 sprint(tmp, "+new+%d", w->isnew);
433 wikiname(w->win, tmp);
434 if(w->arg){
435 winopenbody(w->win, OWRITE);
436 Bprint(w->win->body, "%s\n\n", w->arg);
437 }
438 winclean(w->win);
439 }else if(!w->special){
440 wikiget(w);
441 wikiname(w->win, w->arg);
442 if(w->addr)
443 winselect(w->win, w->addr, 1);
444 }
445 fprint(w->win->ctl, "menu\n");
446 wintagwrite(w->win, "Get History Diff New", 4+8+4+4);
447 winclean(w->win);
448
449 while(!w->dead && (e = recvp(w->win->cevent)))
450 acmeevent(w, e);
451
452 windormant(w->win);
453 unlink(w);
454 free(w->win);
455 free(w->arg);
456 free(w);
457 threadexits(nil);
458 }
459
460 int
wikiopen(char * arg,char * addr)461 wikiopen(char *arg, char *addr)
462 {
463 Dir *d;
464 char *p;
465 Wiki *w;
466
467 /*
468 if(arg==nil){
469 if(write(mapfd, title, strlen(title)) < 0
470 || seek(mapfd, 0, 0) < 0 || (n=read(mapfd, tmp, sizeof(tmp)-2)) < 0){
471 fprint(2, "Wiki: no page '%s' found: %r\n", title);
472 return -1;
473 }
474 if(tmp[n-1] == '\n')
475 tmp[--n] = '\0';
476 tmp[n++] = '/';
477 tmp[n] = '\0';
478 arg = tmp;
479 }
480 */
481
482 /* replace embedded '\n' in links by ' ' */
483 for(p=arg; *p; p++)
484 if(*p=='\n')
485 *p = ' ';
486
487 if(strncmp(arg, dir, strlen(dir))==0 && arg[strlen(dir)]=='/' && arg[strlen(dir)+1])
488 arg += strlen(dir)+1;
489 else if(arg[0] == '/')
490 return -1;
491
492 if((d = dirstat(arg)) == nil)
493 return -1;
494
495 if((d->mode&DMDIR) && arg[strlen(arg)-1] != '/'){
496 p = emalloc(strlen(arg)+2);
497 strcpy(p, arg);
498 strcat(p, "/");
499 arg = p;
500 }else if(!(d->mode&DMDIR) && arg[strlen(arg)-1]=='/'){
501 arg = estrdup(arg);
502 arg[strlen(arg)-1] = '\0';
503 }else
504 arg = estrdup(arg);
505 free(d);
506
507 /* rewrite /current into / */
508 if(strlen(arg) > 8 && strcmp(arg+strlen(arg)-8, "/current")==0)
509 arg[strlen(arg)-8+1] = '\0';
510
511 /* look for window already open */
512 for(w=wlist; w; w=w->next){
513 if(strcmp(w->arg, arg)==0){
514 ctlprint(w->win->ctl, "show\n");
515 return 0;
516 }
517 }
518
519 w = emalloc(sizeof *w);
520 w->arg = arg;
521 w->addr = addr;
522 w->win = newwindow();
523 link(w);
524
525 proccreate(wineventproc, w->win, STACK);
526 threadcreate(wikithread, w, STACK);
527 return 0;
528 }
529
530 void
wikinew(char * arg)531 wikinew(char *arg)
532 {
533 static int n;
534 Wiki *w;
535
536 w = emalloc(sizeof *w);
537 if(arg)
538 arg = estrdup(arg);
539 w->arg = arg;
540 w->win = newwindow();
541 w->isnew = ++n;
542 proccreate(wineventproc, w->win, STACK);
543 threadcreate(wikithread, w, STACK);
544 }
545
546 typedef struct Diffarg Diffarg;
547 struct Diffarg {
548 Wiki *w;
549 char *dir;
550 };
551
552 void
execdiff(void * v)553 execdiff(void *v)
554 {
555 char buf[64];
556 Diffarg *a;
557
558 a = v;
559
560 rfork(RFFDG);
561 close(0);
562 open("/dev/null", OREAD);
563 sprint(buf, "/mnt/wsys/%d/body", a->w->win->id);
564 close(1);
565 open(buf, OWRITE);
566 close(2);
567 open(buf, OWRITE);
568 sprint(buf, "/mnt/wsys/%d", a->w->win->id);
569 bind(buf, "/dev", MBEFORE);
570
571 procexecl(nil, "/acme/wiki/wiki.diff", "wiki.diff", a->dir, nil);
572 }
573
574 int
wikidiff(Wiki * w)575 wikidiff(Wiki *w)
576 {
577 Diffarg *d;
578 char *p, *q, *r;
579 Wiki *nw;
580
581 p = emalloc(strlen(w->arg)+10);
582 strcpy(p, w->arg);
583 if(q = strchr(p, '/'))
584 *q = '\0';
585 r = estrdup(p);
586 strcat(p, "/+Diff");
587
588 nw = emalloc(sizeof *w);
589 nw->arg = p;
590 nw->win = newwindow();
591 nw->special = 1;
592
593 d = emalloc(sizeof(*d));
594 d->w = nw;
595 d->dir = r;
596 wikiname(nw->win, p);
597 proccreate(wineventproc, nw->win, STACK);
598 proccreate(execdiff, d, STACK);
599 threadcreate(wikithread, nw, STACK);
600 return 1;
601 }
602
603