summary refs log tree commit diff
diff options
context:
space:
mode:
authorJune McEnroe <june@causal.agency>2020-12-11 00:39:43 -0500
committerJune McEnroe <june@causal.agency>2020-12-11 00:39:43 -0500
commit9e6a0261f680886111cc536a01f271bf88bd954e (patch)
tree46ffb79f20663406e0a45e069559ad34b4450e77
parentAdd imapIdle (diff)
downloadbubger-9e6a0261f680886111cc536a01f271bf88bd954e.tar.gz
bubger-9e6a0261f680886111cc536a01f271bf88bd954e.zip
Add -i to idle
And rewrite the entire main flow as separate loops with labels. I think
it's much clearer and, importantly, much less indented.
-rw-r--r--archive.c218
-rw-r--r--bubger.123
2 files changed, 115 insertions, 126 deletions
diff --git a/archive.c b/archive.c
index ca218af..756f822 100644
--- a/archive.c
+++ b/archive.c
@@ -28,6 +28,7 @@
 #include <err.h>
 #include <errno.h>
 #include <inttypes.h>
+#include <stdbool.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -85,11 +86,15 @@ int main(int argc, char *argv[]) {
 	const char *user = NULL;
 	const char *passPath = NULL;
 
+	bool idle = false;
 	const char *mailbox = "Archive";
 	const char *algo = "REFERENCES";
 	const char *search = "ALL";
 
-	for (int opt; 0 < (opt = getopt(argc, argv, "C:H:S:a:h:m:p:qs:t:u:vw:y:"));) {
+	for (
+		int opt;
+		0 < (opt = getopt(argc, argv, "C:H:S:a:h:im:p:qs:t:u:vw:y:"));
+	) {
 		switch (opt) {
 			break; case 'C': {
 				int error = chdir(optarg);
@@ -99,6 +104,7 @@ int main(int argc, char *argv[]) {
 			break; case 'S': search = optarg;
 			break; case 'a': algo = optarg;
 			break; case 'h': host = optarg;
+			break; case 'i': idle = true;
 			break; case 'm': baseMailto = optarg;
 			break; case 'p': port = optarg;
 			break; case 'q': exitStatus = EXIT_FAILURE;
@@ -137,141 +143,105 @@ int main(int argc, char *argv[]) {
 		if (!pass) errx(EX_CONFIG, ENV_PASSWORD " unset");
 	}
 
-	enum {
-		Ready,
-		Login,
-		Examine,
-		Thread,
-		Export,
-		Concat,
-		Logout,
-	} state = Ready;
-	size_t exportTags = 0;
+	struct Resp resp;
+	struct IMAP imap = imapOpen(host, port);
+	respFree(respOk(imapResp(&imap)));
 
 	enum Atom login = atom("login");
-	enum Atom examine = atom("examine");
-	enum Atom thread = atom("thread");
-	enum Atom export = atom("export");
-	enum Atom concat = atom("concat");
+	fprintf(imap.w, "%s LOGIN \"%s\" \"%s\"\r\n", Atoms[login], user, pass);
+	for (; resp = respOk(imapResp(&imap)), resp.tag != login; respFree(resp));
+	respFree(resp);
 
+examine:;
 	uint32_t uidNext = 0;
-	struct List threads = {0};
-	struct Envelope *envelopes = NULL;
-
-	struct IMAP imap = imapOpen(host, port);
-	for (
-		struct Resp resp;
-		resp = imapResp(&imap), resp.resp != AtomBye;
-		respFree(resp)
-	) {
-		if (resp.resp == AtomNo || resp.resp == AtomBad) {
-			errx(EX_CONFIG, "%s: %s", Atoms[resp.tag], resp.text);
+	uint32_t uidValidity = 0;
+	enum Atom examine = atom("examine");
+	fprintf(imap.w, "%s EXAMINE \"%s\"\r\n", Atoms[examine], mailbox);
+	for (; resp = respOk(imapResp(&imap)), resp.tag != examine; respFree(resp)) {
+		if (resp.resp != AtomOk || resp.code.len < 2) continue;
+		enum Atom code = dataCheck(resp.code.ptr[0], Atom).atom;
+		if (code == AtomUIDValidity) {
+			uidValidity = dataCheck(resp.code.ptr[1], Number).number;
+		} else if (code == AtomUIDNext) {
+			uidNext = dataCheck(resp.code.ptr[1], Number).number;
 		}
+	}
+	respFree(resp);
 
-		switch (state) {
-			break; case Ready: {
-				fprintf(
-					imap.w, "%s LOGIN \"%s\" \"%s\"\r\n",
-					Atoms[login], user, pass
-				);
-				state = Login;
-			}
-
-			break; case Login: {
-				if (resp.tag != login) break;
-				fprintf(imap.w, "%s EXAMINE \"%s\"\r\n", Atoms[examine], mailbox);
-				state = Examine;
-			}
-
-			break; case Examine: {
-				if (resp.tag == examine) {
-					fprintf(
-						imap.w, "%s UID THREAD %s UTF-8 %s\r\n",
-						Atoms[thread], algo, search
-					);
-					state = Thread;
-					break;
-				}
-
-				if (resp.resp != AtomOk || !resp.code.len) break;
-				enum Atom code = dataCheck(resp.code.ptr[0], Atom).atom;
-				struct Data value = resp.code.ptr[1];
-
-				if (code == AtomUIDValidity) {
-					uint32_t validity = dataCheck(value, Number).number;
-					uint32_t previous = uidRead("UIDVALIDITY");
-					if (previous && validity != previous) {
-						errx(
-							EX_TEMPFAIL,
-							"UIDVALIDITY changed; fresh export required"
-						);
-					}
-					if (!previous) uidWrite("UIDVALIDITY", validity);
-
-				} else if (code == AtomUIDNext) {
-					uidNext = dataCheck(value, Number).number;
-					uint32_t prev = uidRead("UIDNEXT");
-					if (uidNext == prev) {
-						fprintf(imap.w, "ayy LOGOUT\r\n");
-						state = Logout;
-					} else {
-						exitStatus = EXIT_SUCCESS;
-					}
-				}
-			}
-
-			break; case Thread: {
-				if (resp.resp != AtomThread) break;
-				if (!resp.data.len) {
-					errx(EX_TEMPFAIL, "no messages matching %s", search);
-				}
-				createDirs();
+	uint32_t previous = uidRead("UIDVALIDITY");
+	if (previous && uidValidity != previous) {
+		errx(EX_TEMPFAIL, "UIDVALIDITY changed; fresh export required");
+	}
+	if (!previous) uidWrite("UIDVALIDITY", uidValidity);
 
-				threads = resp.data;
-				resp.data = (struct List) {0}; // prevent freeing threads
-				envelopes = calloc(threads.len, sizeof(*envelopes));
-				if (!envelopes) err(EX_OSERR, "calloc");
+	previous = uidRead("UIDNEXT");
+	if (uidNext != previous) {
+		exitStatus = EXIT_SUCCESS;
+	} else if (idle) {
+		goto idle;
+	} else {
+		goto logout;
+	}
 
-				if (exportFetch(imap.w, export, threads)) {
-					exportTags = 1;
-					state = Export;
-				} else {
-					concatFetch(imap.w, concat, threads);
-					state = Concat;
-				}
-			}
+	struct List threads = {0};
+	enum Atom thread = atom("thread");
+	fprintf(
+		imap.w, "%s UID THREAD %s UTF-8 %s\r\n",
+		Atoms[thread], algo, search
+	);
+	for (; resp = respOk(imapResp(&imap)), resp.tag != thread; respFree(resp)) {
+		if (resp.resp != AtomThread) continue;
+		if (!resp.data.len) {
+			errx(EX_TEMPFAIL, "no messages matching %s", search);
+		}
+		threads = resp.data;
+		resp.data = (struct List) {0}; // prevent freeing threads with resp
+	}
+	respFree(resp);
 
-			break; case Export: {
-				if (resp.resp == AtomFetch) {
-					if (!resp.data.len) errx(EX_PROTOCOL, "missing FETCH data");
-					struct List items = dataCheck(resp.data.ptr[0], List).list;
-					if (exportData(imap.w, export, items)) exportTags++;
-				}
-				if (resp.tag != export || --exportTags) break;
-				concatFetch(imap.w, concat, threads);
-				state = Concat;
-			}
+	createDirs();
+	enum Atom export = atom("export");
+	if (!exportFetch(imap.w, export, threads)) {
+		goto concat;
+	}
+	for (
+		size_t exportTags = 1;
+		resp = respOk(imapResp(&imap)), resp.tag != export || --exportTags;
+		respFree(resp)
+	) {
+		if (resp.resp != AtomFetch) continue;
+		if (!resp.data.len) errx(EX_PROTOCOL, "missing FETCH data");
+		struct List items = dataCheck(resp.data.ptr[0], List).list;
+		if (exportData(imap.w, export, items)) exportTags++;
+	}
+	respFree(resp);
 
-			break; case Concat: {
-				if (resp.resp == AtomFetch) {
-					if (!resp.data.len) errx(EX_PROTOCOL, "missing FETCH data");
-					// Prevent freeing data in envelopes:
-					struct Data items = dataTake(&resp.data.ptr[0]);
-					concatData(threads, envelopes, dataCheck(items, List).list);
-				}
-				if (resp.tag != concat) break;
-				concatThreads(threads, envelopes);
-				concatIndex(threads, envelopes);
-				uidWrite("UIDNEXT", uidNext);
-				fprintf(imap.w, "ayy LOGOUT\r\n");
-				state = Logout;
-			}
-			
-			break; case Logout:;
-		}
+concat:;
+	enum Atom concat = atom("concat");
+	struct Envelope *envelopes = calloc(threads.len, sizeof(*envelopes));
+	if (!envelopes) err(EX_OSERR, "calloc");
+	concatFetch(imap.w, concat, threads);
+	for (; resp = respOk(imapResp(&imap)), resp.tag != concat; respFree(resp)) {
+		if (resp.resp != AtomFetch) continue;
+		if (!resp.data.len) errx(EX_PROTOCOL, "missing FETCH data");
+		// Prevent freeing data in envelopes with resp:
+		struct Data items = dataTake(&resp.data.ptr[0]);
+		concatData(threads, envelopes, dataCheck(items, List).list);
 	}
+	respFree(resp);
+
+	concatThreads(threads, envelopes);
+	concatIndex(threads, envelopes);
+	uidWrite("UIDNEXT", uidNext);
+	if (!idle) goto logout;
+
+idle:
+	respFree(respOk(imapIdle(&imap, atom("idle"))));
+	goto examine;
+	
+logout:
+	fprintf(imap.w, "ayy LOGOUT\r\n");
 	fclose(imap.r);
 	fclose(imap.w);
-
 	return exitStatus;
 }
diff --git a/bubger.1 b/bubger.1
index d31f9b4..64007f0 100644
--- a/bubger.1
+++ b/bubger.1
@@ -1,4 +1,4 @@
-.Dd December  4, 2020
+.Dd December 10, 2020
 .Dt BUBGER 1
 .Os
 .
@@ -8,7 +8,7 @@
 .
 .Sh SYNOPSIS
 .Nm
-.Op Fl qv
+.Op Fl iqv
 .Op Fl C Ar path
 .Op Fl H Ar head
 .Op Fl S Ar search
@@ -71,6 +71,16 @@ The default host is determined by
 SRV record lookup on the domain name of
 .Ar user .
 .
+.It Fl i
+Continually wait for
+new messages in the mailbox
+using IMAP IDLE.
+Writes to
+.Pa UIDNEXT
+indicate that the
+rendered archive
+has been updated.
+.
 .It Fl m Ar addr
 Set the mailto and reply Cc address for the archive.
 The default address is
@@ -176,6 +186,15 @@ bubger -q list@example.org \e
 .Re
 .It
 .Rs
+.%A B. Leiba
+.%T IMAP4 IDLE command
+.%I IETF
+.%R RFC 2177
+.%D June 1997
+.%U https://tools.ietf.org/html/rfc2177
+.Re
+.It
+.Rs
 .%A E. Levinson
 .%T Content-ID and Message-ID Uniform Resource Locators
 .%I IETF