plan9fox/acme/wiki/src/wiki.c
2011-04-14 17:27:24 +00:00

603 lines
11 KiB
C

#include "awiki.h"
Wiki *wlist;
void
link(Wiki *w)
{
if(w->linked)
return;
w->linked = 1;
w->prev = nil;
w->next = wlist;
if(wlist)
wlist->prev = w;
wlist = w;
}
void
unlink(Wiki *w)
{
if(!w->linked)
return;
w->linked = 0;
if(w->next)
w->next->prev = w->prev;
if(w->prev)
w->prev->next = w->next;
else
wlist = w->next;
w->next = nil;
w->prev = nil;
}
void
wikiname(Window *w, char *name)
{
char *p, *q;
p = emalloc(strlen(dir)+1+strlen(name)+1+1);
strcpy(p, dir);
strcat(p, "/");
strcat(p, name);
for(q=p; *q; q++)
if(*q==' ')
*q = '_';
winname(w, p);
free(p);
}
int
wikiput(Wiki *w)
{
int fd, n;
char buf[1024], *p;
Biobuf *b;
if((fd = open("new", ORDWR)) < 0){
fprint(2, "Wiki: cannot open raw: %r\n");
return -1;
}
winopenbody(w->win, OREAD);
b = w->win->body;
if((p = Brdline(b, '\n'))==nil){
Short:
winclosebody(w->win);
fprint(2, "Wiki: no data\n");
close(fd);
return -1;
}
write(fd, p, Blinelen(b));
snprint(buf, sizeof buf, "D%lud\n", w->time);
if(email)
snprint(buf+strlen(buf), sizeof(buf)-strlen(buf), "A%s\n", email);
if(Bgetc(b) == '#'){
p = Brdline(b, '\n');
if(p == nil)
goto Short;
snprint(buf+strlen(buf), sizeof(buf)-strlen(buf), "C%s\n", p);
}
write(fd, buf, strlen(buf));
write(fd, "\n\n", 2);
while((n = Bread(b, buf, sizeof buf)) > 0)
write(fd, buf, n);
winclosebody(w->win);
werrstr("");
if((n=write(fd, "", 0)) != 0){
fprint(2, "Wiki commit %lud %d %d: %r\n", w->time, fd, n);
close(fd);
return -1;
}
seek(fd, 0, 0);
if((n = read(fd, buf, 300)) < 0){
fprint(2, "Wiki readback: %r\n");
close(fd);
return -1;
}
close(fd);
buf[n] = '\0';
sprint(buf, "%s/", buf);
free(w->arg);
w->arg = estrdup(buf);
w->isnew = 0;
wikiget(w);
wikiname(w->win, w->arg);
return n;
}
void
wikiget(Wiki *w)
{
char *p;
int fd, normal;
Biobuf *bin;
fprint(w->win->ctl, "dirty\n");
p = emalloc(strlen(w->arg)+8+1);
strcpy(p, w->arg);
normal = 1;
if(p[strlen(p)-1] == '/'){
normal = 0;
strcat(p, "current");
}else if(strlen(p)>8 && strcmp(p+strlen(p)-8, "/current")==0){
normal = 0;
w->arg[strlen(w->arg)-7] = '\0';
}
if((fd = open(p, OREAD)) < 0){
fprint(2, "Wiki: cannot read %s: %r\n", p);
winclean(w->win);
return;
}
free(p);
winopenbody(w->win, OWRITE);
bin = emalloc(sizeof(*bin));
Binit(bin, fd, OREAD);
p = nil;
if(!normal){
if((p = Brdline(bin, '\n')) == nil){
fprint(2, "Wiki: cannot read title: %r\n");
winclean(w->win);
close(fd);
free(bin);
return;
}
p[Blinelen(bin)-1] = '\0';
}
/* clear window */
if(w->win->data < 0)
w->win->data = winopenfile(w->win, "data");
if(winsetaddr(w->win, ",", 0))
write(w->win->data, "", 0);
if(!normal)
Bprint(w->win->body, "%s\n\n", p);
while(p = Brdline(bin, '\n')){
p[Blinelen(bin)-1] = '\0';
if(normal)
Bprint(w->win->body, "%s\n", p);
else{
if(p[0]=='D')
w->time = strtoul(p+1, 0, 10);
else if(p[0]=='#')
Bprint(w->win->body, "%s\n", p+1);
}
}
winclean(w->win);
free(bin);
close(fd);
}
static int
iscmd(char *s, char *cmd)
{
int len;
len = strlen(cmd);
return strncmp(s, cmd, len)==0 && (s[len]=='\0' || s[len]==' ' || s[len]=='\t' || s[len]=='\n');
}
static char*
skip(char *s, char *cmd)
{
s += strlen(cmd);
while(*s==' ' || *s=='\t' || *s=='\n')
s++;
return s;
}
int
wikiload(Wiki *w, char *arg)
{
char *p, *q, *path, *addr;
int rv;
p = nil;
if(arg[0] == '/')
path = arg;
else{
p = emalloc(strlen(w->arg)+1+strlen(arg)+1);
strcpy(p, w->arg);
if(q = strrchr(p, '/')){
++q;
*q = '\0';
}else
*p = '\0';
strcat(p, arg);
cleanname(p);
path = p;
}
if(addr=strchr(path, ':'))
*addr++ = '\0';
rv = wikiopen(path, addr)==0;
free(p);
if(rv)
return 1;
return wikiopen(arg, 0)==0;
}
/* return 1 if handled, 0 otherwise */
int
wikicmd(Wiki *w, char *s)
{
char *p;
s = skip(s, "");
if(iscmd(s, "Del")){
if(windel(w->win, 0))
w->dead = 1;
return 1;
}
if(iscmd(s, "New")){
wikinew(skip(s, "New"));
return 1;
}
if(iscmd(s, "History"))
return wikiload(w, "history.txt");
if(iscmd(s, "Diff"))
return wikidiff(w);
if(iscmd(s, "Get")){
if(winisdirty(w->win) && !w->win->warned){
w->win->warned = 1;
fprint(2, "%s/%s modified\n", dir, w->arg);
}else{
w->win->warned = 0;
wikiget(w);
}
return 1;
}
if(iscmd(s, "Put")){
if((p=strchr(w->arg, '/')) && p[1]!='\0')
fprint(2, "%s/%s is read-only\n", dir, w->arg);
else
wikiput(w);
return 1;
}
return 0;
}
/* need to expand selection more than default word */
static long
eval(Window *w, char *s, ...)
{
char buf[64];
va_list arg;
va_start(arg, s);
vsnprint(buf, sizeof buf, s, arg);
va_end(arg);
if(winsetaddr(w, buf, 1)==0)
return -1;
if(pread(w->addr, buf, 24, 0) != 24)
return -1;
return strtol(buf, 0, 10);
}
static int
getdot(Window *w, long *q0, long *q1)
{
char buf[24];
ctlprint(w->ctl, "addr=dot\n");
if(pread(w->addr, buf, 24, 0) != 24)
return -1;
*q0 = atoi(buf);
*q1 = atoi(buf+12);
return 0;
}
static Event*
expand(Window *w, Event *e, Event *eacme)
{
long q0, q1, x;
if(getdot(w, &q0, &q1)==0 && q0 <= e->q0 && e->q0 <= q1){
e->q0 = q0;
e->q1 = q1;
return e;
}
q0 = eval(w, "#%lud-/\\[/", e->q0);
if(q0 < 0)
return eacme;
if(eval(w, "#%lud+/\\]/", q0) < e->q0) /* [ closes before us */
return eacme;
q1 = eval(w, "#%lud+/\\]/", e->q1);
if(q1 < 0)
return eacme;
if((x=eval(w, "#%lud-/\\[/", q1))==-1 || x > e->q1) /* ] opens after us */
return eacme;
e->q0 = q0+1;
e->q1 = q1;
return e;
}
void
acmeevent(Wiki *wiki, Event *e)
{
Event *ea, *e2, *eq;
Window *w;
char *s, *t, *buf;
int na;
w = wiki->win;
switch(e->c1){ /* origin of action */
default:
Unknown:
fprint(2, "unknown message %c%c\n", e->c1, e->c2);
break;
case 'F': /* generated by our actions; ignore */
break;
case 'E': /* write to body or tag; can't affect us */
break;
case 'K': /* type away; we don't care */
if(e->c2 == 'I' || e->c2 == 'D')
w->warned = 0;
break;
case 'M': /* mouse event */
switch(e->c2){ /* type of action */
case 'x': /* mouse: button 2 in tag */
case 'X': /* mouse: button 2 in body */
ea = nil;
//e2 = nil;
s = e->b;
if(e->flag & 2){ /* null string with non-null expansion */
e2 = recvp(w->cevent);
if(e->nb==0)
s = e2->b;
}
if(e->flag & 8){ /* chorded argument */
ea = recvp(w->cevent); /* argument */
na = ea->nb;
recvp(w->cevent); /* ignore origin */
}else
na = 0;
/* append chorded arguments */
if(na){
t = emalloc(strlen(s)+1+na+1);
sprint(t, "%s %s", s, ea->b);
s = t;
}
/* if it's a known command, do it */
/* if it's a long message, it can't be for us anyway */
// DPRINT(2, "exec: %s\n", s);
if(!wikicmd(wiki, s)) /* send it back */
winwriteevent(w, e);
if(na)
free(s);
break;
case 'l': /* mouse: button 3 in tag */
case 'L': /* mouse: button 3 in body */
//buf = nil;
eq = e;
if(e->flag & 2){ /* we do our own expansion for loads */
e2 = recvp(w->cevent);
eq = expand(w, eq, e2);
}
s = eq->b;
if(eq->q1>eq->q0 && eq->nb==0){
buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
winread(w, eq->q0, eq->q1, buf);
s = buf;
}
if(!wikiload(wiki, s))
winwriteevent(w, e);
break;
case 'i': /* mouse: text inserted in tag */
case 'd': /* mouse: text deleted from tag */
break;
case 'I': /* mouse: text inserted in body */
case 'D': /* mouse: text deleted from body */
w->warned = 0;
break;
default:
goto Unknown;
}
}
}
void
wikithread(void *v)
{
char tmp[40];
Event *e;
Wiki *w;
w = v;
if(w->isnew){
sprint(tmp, "+new+%d", w->isnew);
wikiname(w->win, tmp);
if(w->arg){
winopenbody(w->win, OWRITE);
Bprint(w->win->body, "%s\n\n", w->arg);
}
winclean(w->win);
}else if(!w->special){
wikiget(w);
wikiname(w->win, w->arg);
if(w->addr)
winselect(w->win, w->addr, 1);
}
fprint(w->win->ctl, "menu\n");
wintagwrite(w->win, "Get History Diff New", 4+8+4+4);
winclean(w->win);
while(!w->dead && (e = recvp(w->win->cevent)))
acmeevent(w, e);
windormant(w->win);
unlink(w);
free(w->win);
free(w->arg);
free(w);
threadexits(nil);
}
int
wikiopen(char *arg, char *addr)
{
Dir *d;
char *p;
Wiki *w;
/*
if(arg==nil){
if(write(mapfd, title, strlen(title)) < 0
|| seek(mapfd, 0, 0) < 0 || (n=read(mapfd, tmp, sizeof(tmp)-2)) < 0){
fprint(2, "Wiki: no page '%s' found: %r\n", title);
return -1;
}
if(tmp[n-1] == '\n')
tmp[--n] = '\0';
tmp[n++] = '/';
tmp[n] = '\0';
arg = tmp;
}
*/
/* replace embedded '\n' in links by ' ' */
for(p=arg; *p; p++)
if(*p=='\n')
*p = ' ';
if(strncmp(arg, dir, strlen(dir))==0 && arg[strlen(dir)]=='/' && arg[strlen(dir)+1])
arg += strlen(dir)+1;
else if(arg[0] == '/')
return -1;
if((d = dirstat(arg)) == nil)
return -1;
if((d->mode&DMDIR) && arg[strlen(arg)-1] != '/'){
p = emalloc(strlen(arg)+2);
strcpy(p, arg);
strcat(p, "/");
arg = p;
}else if(!(d->mode&DMDIR) && arg[strlen(arg)-1]=='/'){
arg = estrdup(arg);
arg[strlen(arg)-1] = '\0';
}else
arg = estrdup(arg);
free(d);
/* rewrite /current into / */
if(strlen(arg) > 8 && strcmp(arg+strlen(arg)-8, "/current")==0)
arg[strlen(arg)-8+1] = '\0';
/* look for window already open */
for(w=wlist; w; w=w->next){
if(strcmp(w->arg, arg)==0){
ctlprint(w->win->ctl, "show\n");
return 0;
}
}
w = emalloc(sizeof *w);
w->arg = arg;
w->addr = addr;
w->win = newwindow();
link(w);
proccreate(wineventproc, w->win, STACK);
threadcreate(wikithread, w, STACK);
return 0;
}
void
wikinew(char *arg)
{
static int n;
Wiki *w;
w = emalloc(sizeof *w);
if(arg)
arg = estrdup(arg);
w->arg = arg;
w->win = newwindow();
w->isnew = ++n;
proccreate(wineventproc, w->win, STACK);
threadcreate(wikithread, w, STACK);
}
typedef struct Diffarg Diffarg;
struct Diffarg {
Wiki *w;
char *dir;
};
void
execdiff(void *v)
{
char buf[64];
Diffarg *a;
a = v;
rfork(RFFDG);
close(0);
open("/dev/null", OREAD);
sprint(buf, "/mnt/wsys/%d/body", a->w->win->id);
close(1);
open(buf, OWRITE);
close(2);
open(buf, OWRITE);
sprint(buf, "/mnt/wsys/%d", a->w->win->id);
bind(buf, "/dev", MBEFORE);
procexecl(nil, "/acme/wiki/wiki.diff", "wiki.diff", a->dir, nil);
}
int
wikidiff(Wiki *w)
{
Diffarg *d;
char *p, *q, *r;
Wiki *nw;
p = emalloc(strlen(w->arg)+10);
strcpy(p, w->arg);
if(q = strchr(p, '/'))
*q = '\0';
r = estrdup(p);
strcat(p, "/+Diff");
nw = emalloc(sizeof *w);
nw->arg = p;
nw->win = newwindow();
nw->special = 1;
d = emalloc(sizeof(*d));
d->w = nw;
d->dir = r;
wikiname(nw->win, p);
proccreate(wineventproc, nw->win, STACK);
proccreate(execdiff, d, STACK);
threadcreate(wikithread, nw, STACK);
return 1;
}