Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

#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;
}