Plan 9 from Bell Labs’s /usr/web/sources/contrib/nemo/root/acme/msgs/src/msgs.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


/*
 * 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);
}


Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to [email protected].