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