/* * Adapted from Acme's Mail, mostly by * removing code and adding bits to process plan b * mailboxes. */ #include #include #include #include #include #include #include "dat.h" enum{ /* * Refresh list with a 5 minutes interval, * even if plumb events do not arrive. */ Refreshival = 5 * 60 }; #define dprint if(debug)fprint static char Msgscmds[] = "Get |Arch |Spam >Reply |Delmesg |Save"; static int allflag; static Msgs msgs; static Channel* refreshc; static int plumbfd; static int debug; static int refreshival = Refreshival; static void usage(void) { fprint(2, "usage: Msgs [-a] [-i secs] [mdir] [month]\n"); threadexitsall("usage"); } static int cmd(Msgs *m, char *s) { Window *w; char *args[10]; int nargs; w = m->win; nargs = tokenize(s, args, nelem(args)); if(nargs == 0) return 0; if(strcmp(args[0], "Msgs") == 0) return 1; else if(strcmp(s, "Put") == 0 || strcmp(s, "Get") == 0){ m->edited = 0; if(m->type == Mdir){ sendul(refreshc, 1); /* safety */ return 1; } }else if(strcmp(s, "Del") == 0){ windel(w, 1); threadexitsall(nil); } return 0; } /* * Keep longest common prefix and suffix of old and new * and replace the rest of old in window with new. * Acme uses runes; this function computes char infixes. * positions are converted later. Not too efficient. */ static void infixdiff(char *old, char *new, int *preflen, int *suflen) { int i, j; for(i = 0; old[i] != 0 && new[i] != 0 && old[i] == new[i]; i++) ; *preflen = i; i = strlen(old) - 1; j = strlen(new) - 1; for(*suflen = 0; i >= *preflen && j >= *preflen && old[i] == new[j]; ){ i--; j--; (*suflen)++; } } static void mergetext(Msgs *m, char *old, char *new) { Window *w; int olen, nlen, plen, slen, p0, p1, nc; char buf[50]; w = m->win; if(strcmp(old, new) == 0) return; olen = strlen(old); nlen = strlen(new); infixdiff(old, new, &plen, &slen); p0 = plen; if(plen > 0) p0 = utfnlen(old, plen); if(slen == plen) p1 = p0; else p1 = utfnlen(old, olen - slen); dprint(2, "old [%s]\n", old); snprint(buf, sizeof(buf), "#%d,#%d", p0, p1); dprint(2, "changes: %s (plen %d slen %d)\n", buf, plen, slen); if(plen >= nlen - slen){ dprint(2, "text1 []\n"); winwritedata(w, buf, "", 0); }else{ nc = nlen - (plen + slen); dprint(2, "text2 [%*.*s]\n", nc, nc, new+plen); winwritedata(w, buf, new+plen, nc); } winclean(w); windormant(w); } /* * Updating mail lists keeps the longest prefix and suffix * that did not change and replaces everything else. * This works well enough without depending on the contents * of the window. */ static void mdirlist(Msgs *msgs) { char *body; int nbody; char *new; long nnew; char *cmd; char *f; body = winreadbody(msgs->win, &nbody); if(body == nil) error("%s: read window body failed", msgs->path); if(allflag) f = "-a "; else f = ""; if(msgs->month != nil) cmd = esmprint("/bin/msgs %s%s %s", f, msgs->path, msgs->month); else cmd = esmprint("/bin/msgs %s%s", f, msgs->path); new = tcmdoutput(cmd, &nnew); mergetext(msgs, body, new); free(body); free(cmd); free(new); } static void mfilelist(Msgs *msgs) { char* body; int nbody; char *file; int nfile; body = winreadbody(msgs->win, &nbody); if(body == nil) error("%s: read window body failed", msgs->path); file = readfile(msgs->path, &nfile); if(file == nil) error("%s: readfile: %r", msgs->path); mergetext(msgs, body, file); free(body); free(file); } static void edited(Msgs *m) { /* not sure I like this */ if(0 && m->edited == 0){ m->edited = 1; if(m->warned == 0){ fprint(2, "%s will not be updated while dirty,\n" "you may use Put/Get to make it clean.\n", m->path); m->warned = 1; } } } static void mainctl(void *v) { Msgs *m; Window *w; Event *e, *e2, *eq, *ea; int na; char *s, *t, *buf; m = v; w = m->win; proccreate(wineventproc, w, Stack); for(;;){ e = recvp(w->cevent); switch(e->c1){ default: Unknown: print("unknown message %c%c\n", e->c1, e->c2); break; case 'E': /* write to body; can't affect us */ break; case 'F': /* generated by our actions; ignore */ break; case 'K': /* type away; we don't care (mostly) */ if(e->c2 == 'D' || e->c2 == 'I') edited(m); break; case 'M': switch(e->c2){ case 'x': case 'X': ea = nil; e2 = nil; if(e->flag & 2) e2 = recvp(w->cevent); if(e->flag & 8){ ea = recvp(w->cevent); na = ea->nb; recvp(w->cevent); }else na = 0; s = e->b; if((e->flag&2) && e->nb==0) s = e2->b; if(na){ t = emalloc(strlen(s)+1+na+1); sprint(t, "%s %s", s, ea->b); s = t; } if(!cmd(m, s)) winwriteevent(w, e); if(na) free(s); break; case 'l': case 'L': buf = nil; eq = e; if(e->flag & 2){ e2 = recvp(w->cevent); 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; } USED(s); /* processing on looks goes here, if needed */ if(1) winwriteevent(w, e); free(buf); break; case 'I': /* modify away; we don't care */ case 'D': edited(m); case 'd': case 'i': break; default: goto Unknown; } } } } static void mkmsgswin(Msgs *msgs) { char *dirname; msgs->win = newwindow(); if(msgs->type == Mdir) dirname = esmprint("%s/list", msgs->path); else dirname = nil; winname(msgs->win, dirname ? dirname : msgs->path); free(dirname); wintagwrite(msgs->win, Msgscmds, strlen(Msgscmds)); threadcreate(mainctl, msgs, Stack); } /* * Refresh the entire mail list upon plumb events, * otherwise, changes made by hand on the file system * may go unnoticed. Crude but effective. */ static void refreshthread(void*) { threadsetname("refreshthread"); while(recvul(refreshc) != 0){ while(nbrecvul(refreshc) != 0) ; if(msgs.edited == 0) msgs.list(&msgs); } threadexits(nil); } static void plumbreadproc(void*) { Plumbmsg *m; threadsetname("plumbreadproc"); for(;;){ m = plumbrecv(plumbfd); if(m == nil) threadexits(nil); plumbfree(m); sendul(refreshc, 1); } } static void timerproc(void*) { threadsetname("timerproc"); for(;;){ sendul(refreshc, 1); sleep(refreshival * 1000); } } static void initmsgs(char *dir, char *month) { char *top; char *dflt; char *s; Dir *d; msgs.path = msgs.month = nil; if(month != nil) msgs.month = estrdup(month); top = esmprint("/mail/box/%s", getuser()); dflt = esmprint("%s/msgs", top); if(dir == nil) dir = dflt; if(access(dir, AEXIST) == 0) dir = estrdup(dir); else{ s = cleanpath(dir, dflt); if(access(s, AEXIST) == 0) dir = s; else{ free(s); s = cleanpath(dir, top); if(access(s, AEXIST) == 0) dir = s; else sysfatal("%s: %r", dir); } } msgs.path = dir; d = dirstat(msgs.path); if(d == nil) sysfatal("%s: %r", msgs.path); if(d->qid.type&QTDIR){ msgs.type = Mdir; msgs.list = mdirlist; }else{ msgs.type = Mfile; msgs.list = mfilelist; } free(top); free(dflt); free(d); } void threadmain(int argc, char *argv[]) { char *month; char *dir; month = dir = nil; ARGBEGIN{ case 'd': debug = 1; break; case 'a': allflag = 1; break; case 'i': refreshival = atoi(EARGF(usage())); if(refreshival < 5) refreshival = 5; break; default: usage(); }ARGEND switch(argc){ case 0: dir = nil; break; case 1: dir = argv[0]; break; case 2: dir = argv[0]; month = argv[1]; break; default: usage(); } initmsgs(dir, month); mkmsgswin(&msgs); refreshc = chancreate(sizeof(ulong), 5); if(refreshc == nil) sysfatal("chancreate: %r\n"); if(refreshival != 0) proccreate(timerproc, nil, Stack); plumbfd = plumbopen("seemail", OREAD|OCEXEC); if(plumbfd >= 0) proccreate(plumbreadproc, nil, Stack); refreshthread(nil); threadexits(nil); }