wiki.c 11 KB

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