about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--archive.c166
-rw-r--r--archive.h5
-rw-r--r--concat.c7
-rw-r--r--export.c14
-rw-r--r--imap.h11
5 files changed, 116 insertions, 87 deletions
diff --git a/archive.c b/archive.c
index e741b4e..3b7d003 100644
--- a/archive.c
+++ b/archive.c
@@ -103,101 +103,119 @@ int main(int argc, char *argv[]) {
 		if (!pass) errx(EX_CONFIG, ENV_PASSWORD " unset");
 	}
 
-	enum Atom login = 0;
-	enum Atom examine = atom("EXAMINE");
-	enum Atom thread = atom("THREAD");
-	enum Atom export = 0;
-	enum Atom concat = 0;
+	enum Atom AtomThread = atom("THREAD");
+
+	enum {
+		Ready,
+		Login,
+		Examine,
+		Thread,
+		Export,
+		Concat,
+		Logout,
+	} state = Ready;
+
+	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");
 
 	uint32_t uidNext = 0;
 	struct List threads = {0};
-
 	FILE *imap = imapOpen(host, port);
 	for (struct Resp resp; resp = imapResp(imap), resp.resp != AtomBye;) {
 		if (resp.resp == AtomNo || resp.resp == AtomBad) {
 			errx(EX_CONFIG, "%s %s", Atoms[resp.resp], resp.text);
 		}
 
-		if (!login) {
-			login = atom("login");
-			fprintf(
-				imap, "%s LOGIN \"%s\" \"%s\"\r\n",
-				Atoms[login], user, pass
-			);
-		}
+		switch (state) {
+			break; case Ready: {
+				fprintf(
+					imap, "%s LOGIN \"%s\" \"%s\"\r\n",
+					Atoms[login], user, pass
+				);
+				state = Login;
+			}
 
-		if (resp.tag == login) {
-			fprintf(imap, "%s EXAMINE \"%s\"\r\n", Atoms[examine], mailbox);
-		}
+			break; case Login: {
+				if (resp.tag != login) break;
+				fprintf(imap, "%s EXAMINE \"%s\"\r\n", Atoms[examine], mailbox);
+				state = Examine;
+			}
 
-		if (
-			resp.resp == AtomOk &&
-			resp.code.len > 1 &&
-			resp.code.ptr[0].type == Atom
-		) {
-			enum Atom code = resp.code.ptr[0].atom;
-			if (code == AtomUIDValidity || code == AtomUIDNext) {
-				if (resp.code.ptr[1].type != Number) {
-					errx(EX_PROTOCOL, "invalid %s", Atoms[code]);
+			break; case Examine: {
+				if (resp.resp == AtomOk && resp.code.len > 1) {
+					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, "ayy LOGOUT\r\n");
+							state = Logout;
+						}
+					}
 				}
+
+				if (resp.tag != examine) break;
+				fprintf(
+					imap, "%s UID THREAD %s UTF-8 %s\r\n",
+					Atoms[thread], algo, search
+				);
+				state = Thread;
 			}
-			if (code == AtomUIDValidity) {
-				uint32_t validity = resp.code.ptr[1].number;
-				uint32_t previous = uidRead("UIDVALIDITY");
-				if (previous && validity != previous) {
-					errx(
-						EX_TEMPFAIL,
-						"UIDVALIDITY changed; fresh export required"
-					);
+
+			break; case Thread: {
+				if (resp.resp != AtomThread) break;
+				if (!resp.data.len) {
+					errx(EX_TEMPFAIL, "no messages matching %s", search);
 				}
-				if (!previous) uidWrite("UIDVALIDITY", validity);
-			}
-			if (code == AtomUIDNext) {
-				uidNext = resp.code.ptr[1].number;
-				uint32_t prev = uidRead("UIDNEXT");
-				if (uidNext == prev) {
-					examine = 0;
-					fprintf(imap, "ayy LOGOUT\r\n");
+				createDir("UID");
+				createDir("message");
+				threads = resp.data;
+				resp.data = (struct List) {0};
+				if (exportFetch(imap, export, threads)) {
+					state = Export;
+				} else {
+					concatFetch(imap, concat, threads);
+					state = Concat;
 				}
 			}
-		}
 
-		if (resp.tag == examine) {
-			fprintf(
-				imap, "%s UID THREAD %s UTF-8 %s\r\n",
-				Atoms[thread], algo, search
-			);
-		}
-
-		if (resp.resp == thread) {
-			if (!resp.data.len) {
-				errx(EX_TEMPFAIL, "no messages matching %s", search);
+			break; case Export: {
+				if (resp.resp == AtomFetch) {
+					if (!resp.data.len) errx(EX_PROTOCOL, "missing FETCH data");
+					exportData(dataCheck(resp.data.ptr[0], List).list);
+				}
+				if (resp.tag != export) break;
+				concatFetch(imap, concat, threads);
+				state = Concat;
 			}
-			createDir("UID");
-			createDir("message");
-			threads = resp.data;
-			resp.data = (struct List) {0};
-			export = exportFetch(imap, threads);
-			if (!export) concat = concatFetch(imap, threads);
-		}
 
-		if (export && resp.resp == AtomFetch) {
-			if (!resp.data.len || resp.data.ptr[0].type != List) {
-				errx(EX_PROTOCOL, "invalid FETCH data");
+			break; case Concat: {
+				if (resp.resp == AtomFetch) {
+					if (!resp.data.len) errx(EX_PROTOCOL, "missing FETCH data");
+					concatData(threads, dataCheck(resp.data.ptr[0], List).list);
+				}
+				if (resp.tag != concat) break;
+				uidWrite("UIDNEXT", uidNext);
+				fprintf(imap, "ayy LOGOUT\r\n");
+				state = Logout;
 			}
-			exportData(resp.data.ptr[0].list);
+			
+			break; case Logout:;
 		}
-
-		if (export && resp.tag == export) {
-			export = 0;
-			concat = concatFetch(imap, threads);
-		}
-
-		if (concat && resp.tag == concat) {
-			uidWrite("UIDNEXT", uidNext);
-			fprintf(imap, "ayy LOGOUT\r\n");
-		}
-
 		respFree(resp);
 	}
 	fclose(imap);
diff --git a/archive.h b/archive.h
index 872bf00..83f2e76 100644
--- a/archive.h
+++ b/archive.h
@@ -64,10 +64,11 @@ static inline void envelopeFree(struct Envelope envelope) {
 	free(envelope.bcc.addrs);
 }
 
-enum Atom exportFetch(FILE *imap, struct List threads);
+bool exportFetch(FILE *imap, enum Atom tag, struct List threads);
 void exportData(struct List items);
 
-enum Atom concatFetch(FILE *imap, struct List threads);
+void concatFetch(FILE *imap, enum Atom tag, struct List threads);
+void concatData(struct List threads, struct List items);
 
 #define TEMPLATE(...) #__VA_ARGS__
 
diff --git a/concat.c b/concat.c
index 217662a..3417e8a 100644
--- a/concat.c
+++ b/concat.c
@@ -34,8 +34,7 @@ static uint32_t threadRoot(struct List thread) {
 	return thread.ptr[0].number;
 }
 
-enum Atom concatFetch(FILE *imap, struct List threads) {
-	enum Atom tag = atom("concatFetch");
+void concatFetch(FILE *imap, enum Atom tag, struct List threads) {
 	fprintf(imap, "%s UID FETCH ", Atoms[tag]);
 	for (size_t i = 0; i < threads.len; ++i) {
 		if (threads.ptr[i].type != List) errx(EX_PROTOCOL, "invalid thread");
@@ -43,5 +42,7 @@ enum Atom concatFetch(FILE *imap, struct List threads) {
 		fprintf(imap, "%s%" PRIu32, (i ? "," : ""), root);
 	}
 	fprintf(imap, " (UID ENVELOPE)\r\n");
-	return tag;
+}
+
+void concatData(struct List threads, struct List items) {
 }
diff --git a/export.c b/export.c
index 2ff6c5d..c8d742f 100644
--- a/export.c
+++ b/export.c
@@ -50,7 +50,7 @@ static void flatten(struct List *flat, struct List nested) {
 	}
 }
 
-enum Atom exportFetch(FILE *imap, struct List threads) {
+bool exportFetch(FILE *imap, enum Atom tag, struct List threads) {
 	struct List uids = {0};
 	flatten(&uids, threads);
 	for (size_t i = uids.len - 1; i < uids.len; --i) {
@@ -66,10 +66,11 @@ enum Atom exportFetch(FILE *imap, struct List threads) {
 			uids.ptr[i] = uids.ptr[--uids.len];
 		}
 	}
-	enum Atom tag = 0;
-	if (!uids.len) goto done;
+	if (!uids.len) {
+		listFree(uids);
+		return false;
+	}
 
-	tag = atom("exportFetch");
 	fprintf(imap, "%s UID FETCH ", Atoms[tag]);
 	for (size_t i = 0; i < uids.len; ++i) {
 		fprintf(imap, "%s%" PRIu32, (i ? "," : ""), uids.ptr[i].number);
@@ -81,10 +82,7 @@ enum Atom exportFetch(FILE *imap, struct List threads) {
 		"BODY[HEADER.FIELDS (" MBOX_HEADERS ")] BODY[TEXT]"
 		")\r\n"
 	);
-
-done:
-	listFree(uids);
-	return tag;
+	return true;
 }
 
 static struct Address parseAddress(struct List list) {
diff --git a/imap.h b/imap.h
index f9b3106..a7b4b64 100644
--- a/imap.h
+++ b/imap.h
@@ -99,6 +99,17 @@ struct Data {
 	};
 };
 
+static inline struct Data dataCheck(struct Data data, enum Type type) {
+	const char *Types[] = { "atom", "number", "string", "list" };
+	if (data.type != type) {
+		errx(
+			EX_PROTOCOL, "expected %s, found %s",
+			Types[type], Types[data.type]
+		);
+	}
+	return data;
+}
+
 static inline void listPush(struct List *list, struct Data data) {
 	if (list->len == list->cap) {
 		list->cap = (list->cap ? list->cap * 2 : 4);