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(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 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 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* 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 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 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 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 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 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 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