xref: /plan9/acme/wiki/src/wiki.c (revision 90630c3ae7e1186c94d00c68ec472672552283ad)
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