wiki.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. /*
  2. * This file is part of the UCB release of Plan 9. It is subject to the license
  3. * terms in the LICENSE file found in the top-level directory of this
  4. * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
  5. * part of the UCB release of Plan 9, including this file, may be copied,
  6. * modified, propagated, or distributed except according to the terms contained
  7. * in the LICENSE file.
  8. */
  9. #include "awiki.h"
  10. Wiki *wlist;
  11. void
  12. link(Wiki *w)
  13. {
  14. if(w->linked)
  15. return;
  16. w->linked = 1;
  17. w->prev = nil;
  18. w->next = wlist;
  19. if(wlist)
  20. wlist->prev = w;
  21. wlist = w;
  22. }
  23. void
  24. unlink(Wiki *w)
  25. {
  26. if(!w->linked)
  27. return;
  28. w->linked = 0;
  29. if(w->next)
  30. w->next->prev = w->prev;
  31. if(w->prev)
  32. w->prev->next = w->next;
  33. else
  34. wlist = w->next;
  35. w->next = nil;
  36. w->prev = nil;
  37. }
  38. void
  39. wikiname(Window *w, char *name)
  40. {
  41. char *p, *q;
  42. p = emalloc(strlen(dir)+1+strlen(name)+1+1);
  43. strcpy(p, dir);
  44. strcat(p, "/");
  45. strcat(p, name);
  46. for(q=p; *q; q++)
  47. if(*q==' ')
  48. *q = '_';
  49. winname(w, p);
  50. free(p);
  51. }
  52. int
  53. wikiput(Wiki *w)
  54. {
  55. int fd, n;
  56. char buf[1024], *p;
  57. Biobuf *b;
  58. if((fd = open("new", ORDWR)) < 0){
  59. fprint(2, "Wiki: cannot open raw: %r\n");
  60. return -1;
  61. }
  62. winopenbody(w->win, OREAD);
  63. b = w->win->body;
  64. if((p = Brdline(b, '\n'))==nil){
  65. Short:
  66. winclosebody(w->win);
  67. fprint(2, "Wiki: no data\n");
  68. close(fd);
  69. return -1;
  70. }
  71. write(fd, p, Blinelen(b));
  72. snprint(buf, sizeof buf, "D%lud\n", w->time);
  73. if(email)
  74. snprint(buf+strlen(buf), sizeof(buf)-strlen(buf), "A%s\n", email);
  75. if(Bgetc(b) == '#'){
  76. p = Brdline(b, '\n');
  77. if(p == nil)
  78. goto Short;
  79. snprint(buf+strlen(buf), sizeof(buf)-strlen(buf), "C%s\n", p);
  80. }
  81. write(fd, buf, strlen(buf));
  82. write(fd, "\n\n", 2);
  83. while((n = Bread(b, buf, sizeof buf)) > 0)
  84. write(fd, buf, n);
  85. winclosebody(w->win);
  86. werrstr("");
  87. if((n=write(fd, "", 0)) != 0){
  88. fprint(2, "Wiki commit %lud %d %d: %r\n", w->time, fd, n);
  89. close(fd);
  90. return -1;
  91. }
  92. seek(fd, 0, 0);
  93. if((n = read(fd, buf, 300)) < 0){
  94. fprint(2, "Wiki readback: %r\n");
  95. close(fd);
  96. return -1;
  97. }
  98. close(fd);
  99. buf[n] = '\0';
  100. sprint(buf, "%s/", buf);
  101. free(w->arg);
  102. w->arg = estrdup(buf);
  103. w->isnew = 0;
  104. wikiget(w);
  105. wikiname(w->win, w->arg);
  106. return n;
  107. }
  108. void
  109. wikiget(Wiki *w)
  110. {
  111. char *p;
  112. int fd, normal;
  113. Biobuf *bin;
  114. fprint(w->win->ctl, "dirty\n");
  115. p = emalloc(strlen(w->arg)+8+1);
  116. strcpy(p, w->arg);
  117. normal = 1;
  118. if(p[strlen(p)-1] == '/'){
  119. normal = 0;
  120. strcat(p, "current");
  121. }else if(strlen(p)>8 && strcmp(p+strlen(p)-8, "/current")==0){
  122. normal = 0;
  123. w->arg[strlen(w->arg)-7] = '\0';
  124. }
  125. if((fd = open(p, OREAD)) < 0){
  126. fprint(2, "Wiki: cannot read %s: %r\n", p);
  127. winclean(w->win);
  128. return;
  129. }
  130. free(p);
  131. winopenbody(w->win, OWRITE);
  132. bin = emalloc(sizeof(*bin));
  133. Binit(bin, fd, OREAD);
  134. p = nil;
  135. if(!normal){
  136. if((p = Brdline(bin, '\n')) == nil){
  137. fprint(2, "Wiki: cannot read title: %r\n");
  138. winclean(w->win);
  139. close(fd);
  140. free(bin);
  141. return;
  142. }
  143. p[Blinelen(bin)-1] = '\0';
  144. }
  145. /* clear window */
  146. if(w->win->data < 0)
  147. w->win->data = winopenfile(w->win, "data");
  148. if(winsetaddr(w->win, ",", 0))
  149. write(w->win->data, "", 0);
  150. if(!normal)
  151. Bprint(w->win->body, "%s\n\n", p);
  152. while(p = Brdline(bin, '\n')){
  153. p[Blinelen(bin)-1] = '\0';
  154. if(normal)
  155. Bprint(w->win->body, "%s\n", p);
  156. else{
  157. if(p[0]=='D')
  158. w->time = strtoul(p+1, 0, 10);
  159. else if(p[0]=='#')
  160. Bprint(w->win->body, "%s\n", p+1);
  161. }
  162. }
  163. winclean(w->win);
  164. free(bin);
  165. close(fd);
  166. }
  167. static int
  168. iscmd(char *s, char *cmd)
  169. {
  170. int len;
  171. len = strlen(cmd);
  172. return strncmp(s, cmd, len)==0 && (s[len]=='\0' || s[len]==' ' || s[len]=='\t' || s[len]=='\n');
  173. }
  174. static char*
  175. skip(char *s, char *cmd)
  176. {
  177. s += strlen(cmd);
  178. while(*s==' ' || *s=='\t' || *s=='\n')
  179. s++;
  180. return s;
  181. }
  182. int
  183. wikiload(Wiki *w, char *arg)
  184. {
  185. char *p, *q, *path, *addr;
  186. int rv;
  187. p = nil;
  188. if(arg[0] == '/')
  189. path = arg;
  190. else{
  191. p = emalloc(strlen(w->arg)+1+strlen(arg)+1);
  192. strcpy(p, w->arg);
  193. if(q = strrchr(p, '/')){
  194. ++q;
  195. *q = '\0';
  196. }else
  197. *p = '\0';
  198. strcat(p, arg);
  199. cleanname(p);
  200. path = p;
  201. }
  202. if(addr=strchr(path, ':'))
  203. *addr++ = '\0';
  204. rv = wikiopen(path, addr)==0;
  205. free(p);
  206. if(rv)
  207. return 1;
  208. return wikiopen(arg, 0)==0;
  209. }
  210. /* return 1 if handled, 0 otherwise */
  211. int
  212. wikicmd(Wiki *w, char *s)
  213. {
  214. char *p;
  215. s = skip(s, "");
  216. if(iscmd(s, "Del")){
  217. if(windel(w->win, 0))
  218. w->dead = 1;
  219. return 1;
  220. }
  221. if(iscmd(s, "New")){
  222. wikinew(skip(s, "New"));
  223. return 1;
  224. }
  225. if(iscmd(s, "History"))
  226. return wikiload(w, "history.txt");
  227. if(iscmd(s, "Diff"))
  228. return wikidiff(w);
  229. if(iscmd(s, "Get")){
  230. if(winisdirty(w->win) && !w->win->warned){
  231. w->win->warned = 1;
  232. fprint(2, "%s/%s modified\n", dir, w->arg);
  233. }else{
  234. w->win->warned = 0;
  235. wikiget(w);
  236. }
  237. return 1;
  238. }
  239. if(iscmd(s, "Put")){
  240. if((p=strchr(w->arg, '/')) && p[1]!='\0')
  241. fprint(2, "%s/%s is read-only\n", dir, w->arg);
  242. else
  243. wikiput(w);
  244. return 1;
  245. }
  246. return 0;
  247. }
  248. /* need to expand selection more than default word */
  249. static long
  250. eval(Window *w, char *s, ...)
  251. {
  252. char buf[64];
  253. va_list arg;
  254. va_start(arg, s);
  255. vsnprint(buf, sizeof buf, s, arg);
  256. va_end(arg);
  257. if(winsetaddr(w, buf, 1)==0)
  258. return -1;
  259. if(pread(w->addr, buf, 24, 0) != 24)
  260. return -1;
  261. return strtol(buf, 0, 10);
  262. }
  263. static int
  264. getdot(Window *w, long *q0, long *q1)
  265. {
  266. char buf[24];
  267. ctlprint(w->ctl, "addr=dot\n");
  268. if(pread(w->addr, buf, 24, 0) != 24)
  269. return -1;
  270. *q0 = atoi(buf);
  271. *q1 = atoi(buf+12);
  272. return 0;
  273. }
  274. static Event*
  275. expand(Window *w, Event *e, Event *eacme)
  276. {
  277. long q0, q1, x;
  278. if(getdot(w, &q0, &q1)==0 && q0 <= e->q0 && e->q0 <= q1){
  279. e->q0 = q0;
  280. e->q1 = q1;
  281. return e;
  282. }
  283. q0 = eval(w, "#%lud-/\\[/", e->q0);
  284. if(q0 < 0)
  285. return eacme;
  286. if(eval(w, "#%lud+/\\]/", q0) < e->q0) /* [ closes before us */
  287. return eacme;
  288. q1 = eval(w, "#%lud+/\\]/", e->q1);
  289. if(q1 < 0)
  290. return eacme;
  291. if((x=eval(w, "#%lud-/\\[/", q1))==-1 || x > e->q1) /* ] opens after us */
  292. return eacme;
  293. e->q0 = q0+1;
  294. e->q1 = q1;
  295. return e;
  296. }
  297. void
  298. acmeevent(Wiki *wiki, Event *e)
  299. {
  300. Event *ea, *e2, *eq;
  301. Window *w;
  302. char *s, *t, *buf;
  303. int na;
  304. w = wiki->win;
  305. switch(e->c1){ /* origin of action */
  306. default:
  307. Unknown:
  308. fprint(2, "unknown message %c%c\n", e->c1, e->c2);
  309. break;
  310. case 'F': /* generated by our actions; ignore */
  311. break;
  312. case 'E': /* write to body or tag; can't affect us */
  313. break;
  314. case 'K': /* type away; we don't care */
  315. if(e->c2 == 'I' || e->c2 == 'D')
  316. w->warned = 0;
  317. break;
  318. case 'M': /* mouse event */
  319. switch(e->c2){ /* type of action */
  320. case 'x': /* mouse: button 2 in tag */
  321. case 'X': /* mouse: button 2 in body */
  322. ea = nil;
  323. //e2 = nil;
  324. s = e->b;
  325. if(e->flag & 2){ /* null string with non-null expansion */
  326. e2 = recvp(w->cevent);
  327. if(e->nb==0)
  328. s = e2->b;
  329. }
  330. if(e->flag & 8){ /* chorded argument */
  331. ea = recvp(w->cevent); /* argument */
  332. na = ea->nb;
  333. recvp(w->cevent); /* ignore origin */
  334. }else
  335. na = 0;
  336. /* append chorded arguments */
  337. if(na){
  338. t = emalloc(strlen(s)+1+na+1);
  339. sprint(t, "%s %s", s, ea->b);
  340. s = t;
  341. }
  342. /* if it's a known command, do it */
  343. /* if it's a long message, it can't be for us anyway */
  344. // DPRINT(2, "exec: %s\n", s);
  345. if(!wikicmd(wiki, s)) /* send it back */
  346. winwriteevent(w, e);
  347. if(na)
  348. free(s);
  349. break;
  350. case 'l': /* mouse: button 3 in tag */
  351. case 'L': /* mouse: button 3 in body */
  352. //buf = nil;
  353. eq = e;
  354. if(e->flag & 2){ /* we do our own expansion for loads */
  355. e2 = recvp(w->cevent);
  356. eq = expand(w, eq, e2);
  357. }
  358. s = eq->b;
  359. if(eq->q1>eq->q0 && eq->nb==0){
  360. buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
  361. winread(w, eq->q0, eq->q1, buf);
  362. s = buf;
  363. }
  364. if(!wikiload(wiki, s))
  365. winwriteevent(w, e);
  366. break;
  367. case 'i': /* mouse: text inserted in tag */
  368. case 'd': /* mouse: text deleted from tag */
  369. break;
  370. case 'I': /* mouse: text inserted in body */
  371. case 'D': /* mouse: text deleted from body */
  372. w->warned = 0;
  373. break;
  374. default:
  375. goto Unknown;
  376. }
  377. }
  378. }
  379. void
  380. wikithread(void *v)
  381. {
  382. char tmp[40];
  383. Event *e;
  384. Wiki *w;
  385. w = v;
  386. if(w->isnew){
  387. sprint(tmp, "+new+%d", w->isnew);
  388. wikiname(w->win, tmp);
  389. if(w->arg){
  390. winopenbody(w->win, OWRITE);
  391. Bprint(w->win->body, "%s\n\n", w->arg);
  392. }
  393. winclean(w->win);
  394. }else if(!w->special){
  395. wikiget(w);
  396. wikiname(w->win, w->arg);
  397. if(w->addr)
  398. winselect(w->win, w->addr, 1);
  399. }
  400. fprint(w->win->ctl, "menu\n");
  401. wintagwrite(w->win, "Get History Diff New", 4+8+4+4);
  402. winclean(w->win);
  403. while(!w->dead && (e = recvp(w->win->cevent)))
  404. acmeevent(w, e);
  405. windormant(w->win);
  406. unlink(w);
  407. free(w->win);
  408. free(w->arg);
  409. free(w);
  410. threadexits(nil);
  411. }
  412. int
  413. wikiopen(char *arg, char *addr)
  414. {
  415. Dir *d;
  416. char *p;
  417. Wiki *w;
  418. /*
  419. if(arg==nil){
  420. if(write(mapfd, title, strlen(title)) < 0
  421. || seek(mapfd, 0, 0) < 0 || (n=read(mapfd, tmp, sizeof(tmp)-2)) < 0){
  422. fprint(2, "Wiki: no page '%s' found: %r\n", title);
  423. return -1;
  424. }
  425. if(tmp[n-1] == '\n')
  426. tmp[--n] = '\0';
  427. tmp[n++] = '/';
  428. tmp[n] = '\0';
  429. arg = tmp;
  430. }
  431. */
  432. /* replace embedded '\n' in links by ' ' */
  433. for(p=arg; *p; p++)
  434. if(*p=='\n')
  435. *p = ' ';
  436. if(strncmp(arg, dir, strlen(dir))==0 && arg[strlen(dir)]=='/' && arg[strlen(dir)+1])
  437. arg += strlen(dir)+1;
  438. else if(arg[0] == '/')
  439. return -1;
  440. if((d = dirstat(arg)) == nil)
  441. return -1;
  442. if((d->mode&DMDIR) && arg[strlen(arg)-1] != '/'){
  443. p = emalloc(strlen(arg)+2);
  444. strcpy(p, arg);
  445. strcat(p, "/");
  446. arg = p;
  447. }else if(!(d->mode&DMDIR) && arg[strlen(arg)-1]=='/'){
  448. arg = estrdup(arg);
  449. arg[strlen(arg)-1] = '\0';
  450. }else
  451. arg = estrdup(arg);
  452. free(d);
  453. /* rewrite /current into / */
  454. if(strlen(arg) > 8 && strcmp(arg+strlen(arg)-8, "/current")==0)
  455. arg[strlen(arg)-8+1] = '\0';
  456. /* look for window already open */
  457. for(w=wlist; w; w=w->next){
  458. if(strcmp(w->arg, arg)==0){
  459. ctlprint(w->win->ctl, "show\n");
  460. return 0;
  461. }
  462. }
  463. w = emalloc(sizeof *w);
  464. w->arg = arg;
  465. w->addr = addr;
  466. w->win = newwindow();
  467. link(w);
  468. proccreate(wineventproc, w->win, STACK);
  469. threadcreate(wikithread, w, STACK);
  470. return 0;
  471. }
  472. void
  473. wikinew(char *arg)
  474. {
  475. static int n;
  476. Wiki *w;
  477. w = emalloc(sizeof *w);
  478. if(arg)
  479. arg = estrdup(arg);
  480. w->arg = arg;
  481. w->win = newwindow();
  482. w->isnew = ++n;
  483. proccreate(wineventproc, w->win, STACK);
  484. threadcreate(wikithread, w, STACK);
  485. }
  486. typedef struct Diffarg Diffarg;
  487. struct Diffarg {
  488. Wiki *w;
  489. char *dir;
  490. };
  491. void
  492. execdiff(void *v)
  493. {
  494. char buf[64];
  495. Diffarg *a;
  496. a = v;
  497. rfork(RFFDG);
  498. close(0);
  499. open("/dev/null", OREAD);
  500. sprint(buf, "/mnt/wsys/%d/body", a->w->win->id);
  501. close(1);
  502. open(buf, OWRITE);
  503. close(2);
  504. open(buf, OWRITE);
  505. sprint(buf, "/mnt/wsys/%d", a->w->win->id);
  506. bind(buf, "/dev", MBEFORE);
  507. procexecl(nil, "/acme/wiki/wiki.diff", "wiki.diff", a->dir, nil);
  508. }
  509. int
  510. wikidiff(Wiki *w)
  511. {
  512. Diffarg *d;
  513. char *p, *q, *r;
  514. Wiki *nw;
  515. p = emalloc(strlen(w->arg)+10);
  516. strcpy(p, w->arg);
  517. if(q = strchr(p, '/'))
  518. *q = '\0';
  519. r = estrdup(p);
  520. strcat(p, "/+Diff");
  521. nw = emalloc(sizeof *w);
  522. nw->arg = p;
  523. nw->win = newwindow();
  524. nw->special = 1;
  525. d = emalloc(sizeof(*d));
  526. d->w = nw;
  527. d->dir = r;
  528. wikiname(nw->win, p);
  529. proccreate(wineventproc, nw->win, STACK);
  530. proccreate(execdiff, d, STACK);
  531. threadcreate(wikithread, nw, STACK);
  532. return 1;
  533. }