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