603 lines
11 KiB
C
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;
|
|
}
|
|
|