xref: /plan9/acme/wiki/src/wiki.c (revision 4d44ba9b9ee4246ddbd96c7fcaf0918ab92ab35a)
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, 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
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
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*
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
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
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(w->win->dirtied){
253 			w->win->dirtied = 0;
254 			fprint(2, "%s/%s modified\n", dir, w->arg);
255 		}else
256 			wikiget(w);
257 		return 1;
258 	}
259 	if(iscmd(s, "Put")){
260 		if((p=strchr(w->arg, '/')) && p[1]!='\0')
261 			fprint(2, "%s/%s is read-only\n", dir, w->arg);
262 		else
263 			wikiput(w);
264 		return 1;
265 	}
266 	return 0;
267 }
268 
269 /* need to expand selection more than default word */
270 static long
271 eval(Window *w, char *s, ...)
272 {
273 	char buf[64];
274 	va_list arg;
275 
276 	va_start(arg, s);
277 	vsnprint(buf, sizeof buf, s, arg);
278 	va_end(arg);
279 
280 	if(winsetaddr(w, buf, 1)==0)
281 		return -1;
282 
283 	if(pread(w->addr, buf, 24, 0) != 24)
284 		return -1;
285 	return strtol(buf, 0, 10);
286 }
287 
288 static int
289 getdot(Window *w, long *q0, long *q1)
290 {
291 	char buf[24];
292 
293 	ctlprint(w->ctl, "addr=dot\n");
294 	if(pread(w->addr, buf, 24, 0) != 24)
295 		return -1;
296 	*q0 = atoi(buf);
297 	*q1 = atoi(buf+12);
298 	return 0;
299 }
300 
301 static Event*
302 expand(Window *w, Event *e, Event *eacme)
303 {
304 	long q0, q1, x;
305 
306 	if(getdot(w, &q0, &q1)==0 && q0 <= e->q0 && e->q0 <= q1){
307 		e->q0 = q0;
308 		e->q1 = q1;
309 		return e;
310 	}
311 
312 	q0 = eval(w, "#%lud-/\\[/", e->q0);
313 	if(q0 < 0)
314 		return eacme;
315 	if(eval(w, "#%lud+/\\]/", q0) < e->q0)	/* [ closes before us */
316 		return eacme;
317 	q1 = eval(w, "#%lud+/\\]/", e->q1);
318 	if(q1 < 0)
319 		return eacme;
320 	if((x=eval(w, "#%lud-/\\[/", q1))==-1 || x > e->q1)	/* ] opens after us */
321 		return eacme;
322 	e->q0 = q0+1;
323 	e->q1 = q1;
324 	return e;
325 }
326 
327 void
328 acmeevent(Wiki *wiki, Event *e)
329 {
330 	Event *ea, *e2, *eq;
331 	Window *w;
332 	char *s, *t, *buf;
333 	int na;
334 
335 	w = wiki->win;
336 	switch(e->c1){	/* origin of action */
337 	default:
338 	Unknown:
339 		fprint(2, "unknown message %c%c\n", e->c1, e->c2);
340 		break;
341 
342 	case 'F':	/* generated by our actions; ignore */
343 		break;
344 
345 	case 'E':	/* write to body or tag; can't affect us */
346 	case 'K':	/* type away; we don't care */
347 		if(e->c2 == 'I' || e->c2 == 'D')
348 			w->dirtied = 1;
349 		break;
350 
351 	case 'M':	/* mouse event */
352 		switch(e->c2){		/* type of action */
353 		case 'x':	/* mouse: button 2 in tag */
354 		case 'X':	/* mouse: button 2 in body */
355 			ea = nil;
356 			//e2 = nil;
357 			s = e->b;
358 			if(e->flag & 2){	/* null string with non-null expansion */
359 				e2 = recvp(w->cevent);
360 				if(e->nb==0)
361 					s = e2->b;
362 			}
363 			if(e->flag & 8){	/* chorded argument */
364 				ea = recvp(w->cevent);	/* argument */
365 				na = ea->nb;
366 				recvp(w->cevent);		/* ignore origin */
367 			}else
368 				na = 0;
369 
370 			/* append chorded arguments */
371 			if(na){
372 				t = emalloc(strlen(s)+1+na+1);
373 				sprint(t, "%s %s", s, ea->b);
374 				s = t;
375 			}
376 			/* if it's a known command, do it */
377 			/* if it's a long message, it can't be for us anyway */
378 		//	DPRINT(2, "exec: %s\n", s);
379 			if(!wikicmd(wiki, s))	/* send it back */
380 				winwriteevent(w, e);
381 			if(na)
382 				free(s);
383 			break;
384 
385 		case 'l':	/* mouse: button 3 in tag */
386 		case 'L':	/* mouse: button 3 in body */
387 			//buf = nil;
388 			eq = e;
389 			if(e->flag & 2){	/* we do our own expansion for loads */
390 				e2 = recvp(w->cevent);
391 				eq = expand(w, eq, e2);
392 			}
393 			s = eq->b;
394 			if(eq->q1>eq->q0 && eq->nb==0){
395 				buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
396 				winread(w, eq->q0, eq->q1, buf);
397 				s = buf;
398 			}
399 			if(!wikiload(wiki, s))
400 				winwriteevent(w, e);
401 			break;
402 
403 		case 'i':	/* mouse: text inserted in tag */
404 		case 'd':	/* mouse: text deleted from tag */
405 			break;
406 
407 		case 'I':	/* mouse: text inserted in body */
408 		case 'D':	/* mouse: text deleted from body */
409 			w->dirtied = 1;
410 			break;
411 
412 		default:
413 			goto Unknown;
414 		}
415 	}
416 }
417 
418 void
419 wikithread(void *v)
420 {
421 	char tmp[40];
422 	Event *e;
423 	Wiki *w;
424 
425 	w = v;
426 
427 	if(w->isnew){
428 		sprint(tmp, "+new+%d", w->isnew);
429 		wikiname(w->win, tmp);
430 		if(w->arg){
431 			winopenbody(w->win, OWRITE);
432 			Bprint(w->win->body, "%s\n\n", w->arg);
433 		}
434 		winclean(w->win);
435 	}else if(!w->special){
436 		wikiget(w);
437 		wikiname(w->win, w->arg);
438 		if(w->addr)
439 			winselect(w->win, w->addr, 1);
440 	}
441 	wintagwrite(w->win, "Get Put History Diff New", 4+4+8+4+4);
442 
443 	while(!w->dead && (e = recvp(w->win->cevent)))
444 		acmeevent(w, e);
445 
446 	windormant(w->win);
447 	unlink(w);
448 	free(w->win);
449 	free(w->arg);
450 	free(w);
451 	threadexits(nil);
452 }
453 
454 int
455 wikiopen(char *arg, char *addr)
456 {
457 	Dir *d;
458 	char *p;
459 	Wiki *w;
460 
461 /*
462 	if(arg==nil){
463 		if(write(mapfd, title, strlen(title)) < 0
464 		|| seek(mapfd, 0, 0) < 0 || (n=read(mapfd, tmp, sizeof(tmp)-2)) < 0){
465 			fprint(2, "Wiki: no page '%s' found: %r\n", title);
466 			return -1;
467 		}
468 		if(tmp[n-1] == '\n')
469 			tmp[--n] = '\0';
470 		tmp[n++] = '/';
471 		tmp[n] = '\0';
472 		arg = tmp;
473 	}
474 */
475 
476 	/* replace embedded '\n' in links by ' ' */
477 	for(p=arg; *p; p++)
478 		if(*p=='\n')
479 			*p = ' ';
480 
481 	if(strncmp(arg, dir, strlen(dir))==0 && arg[strlen(dir)]=='/' && arg[strlen(dir)+1])
482 		arg += strlen(dir)+1;
483 	else if(arg[0] == '/')
484 		return -1;
485 
486 	if((d = dirstat(arg)) == nil)
487 		return -1;
488 
489 	if((d->mode&DMDIR) && arg[strlen(arg)-1] != '/'){
490 		p = emalloc(strlen(arg)+2);
491 		strcpy(p, arg);
492 		strcat(p, "/");
493 		arg = p;
494 	}else if(!(d->mode&DMDIR) && arg[strlen(arg)-1]=='/'){
495 		arg = estrdup(arg);
496 		arg[strlen(arg)-1] = '\0';
497 	}else
498 		arg = estrdup(arg);
499 	free(d);
500 
501 	/* rewrite /current into / */
502 	if(strlen(arg) > 8 && strcmp(arg+strlen(arg)-8, "/current")==0)
503 		arg[strlen(arg)-8+1] = '\0';
504 
505 	/* look for window already open */
506 	for(w=wlist; w; w=w->next){
507 		if(strcmp(w->arg, arg)==0){
508 			ctlprint(w->win->ctl, "show\n");
509 			return 0;
510 		}
511 	}
512 
513 	w = emalloc(sizeof *w);
514 	w->arg = arg;
515 	w->addr = addr;
516 	w->win = newwindow();
517 	link(w);
518 
519 	proccreate(wineventproc, w->win, STACK);
520 	threadcreate(wikithread, w, STACK);
521 	return 0;
522 }
523 
524 void
525 wikinew(char *arg)
526 {
527 	static int n;
528 	Wiki *w;
529 
530 	w = emalloc(sizeof *w);
531 	if(arg)
532 		arg = estrdup(arg);
533 	w->arg = arg;
534 	w->win = newwindow();
535 	w->isnew = ++n;
536 	proccreate(wineventproc, w->win, STACK);
537 	threadcreate(wikithread, w, STACK);
538 }
539 
540 typedef struct Diffarg Diffarg;
541 struct Diffarg {
542 	Wiki *w;
543 	char *dir;
544 };
545 
546 void
547 execdiff(void *v)
548 {
549 	char buf[64];
550 	Diffarg *a;
551 
552 	a = v;
553 
554 	rfork(RFFDG);
555 	close(0);
556 	open("/dev/null", OREAD);
557 	sprint(buf, "/mnt/wsys/%d/body", a->w->win->id);
558 	close(1);
559 	open(buf, OWRITE);
560 	close(2);
561 	open(buf, OWRITE);
562 	sprint(buf, "/mnt/wsys/%d", a->w->win->id);
563 	bind(buf, "/dev", MBEFORE);
564 
565 	procexecl(nil, "/acme/wiki/wiki.diff", "wiki.diff", a->dir, nil);
566 }
567 
568 int
569 wikidiff(Wiki *w)
570 {
571 	Diffarg *d;
572 	char *p, *q, *r;
573 	Wiki *nw;
574 
575 	p = emalloc(strlen(w->arg)+10);
576 	strcpy(p, w->arg);
577 	if(q = strchr(p, '/'))
578 		*q = '\0';
579 	r = estrdup(p);
580 	strcat(p, "/+Diff");
581 
582 	nw = emalloc(sizeof *w);
583 	nw->arg = p;
584 	nw->win = newwindow();
585 	nw->special = 1;
586 
587 	d = emalloc(sizeof(*d));
588 	d->w = nw;
589 	d->dir = r;
590 	wikiname(nw->win, p);
591 	proccreate(wineventproc, nw->win, STACK);
592 	proccreate(execdiff, d, STACK);
593 	threadcreate(wikithread, nw, STACK);
594 	return 1;
595 }
596 
597