/*
* Adapted from Acme's Mail, mostly by
* removing code and adding bits to process plan b
* mailboxes.
*/
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <plumb.h>
#include <ctype.h>
#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);
}
|