about summary refs log tree commit diff
path: root/archive.c
diff options
context:
space:
mode:
Diffstat (limited to 'archive.c')
-rw-r--r--archive.c134
1 files changed, 119 insertions, 15 deletions
diff --git a/archive.c b/archive.c
index 0bee374..400e040 100644
--- a/archive.c
+++ b/archive.c
@@ -80,6 +80,107 @@ static void createDirs(void) {
 	createDir("thread");
 }
 
+static struct Search {
+	size_t cap;
+	size_t len;
+	char **names;
+	char **exprs;
+} search;
+
+static void searchAdd(const char *name, const char *expr) {
+	if (search.len == search.cap) {
+		search.cap = (search.cap ? search.cap * 2 : 8);
+		search.names = realloc(
+			search.names, sizeof(*search.names) * search.cap
+		);
+		search.exprs = realloc(
+			search.exprs, sizeof(*search.exprs) * search.cap
+		);
+		if (!search.names || !search.exprs) err(EX_OSERR, "realloc");
+	}
+	size_t i = search.len++;
+	search.names[i] = strdup(name);
+	search.exprs[i] = strdup(expr);
+	if (!search.names[i] || !search.exprs[i]) err(EX_OSERR, "strdup");
+}
+
+static int searchRead(const char *path) {
+	FILE *file = fopen(path, "r");
+	if (!file) return -1;
+
+	int line = 1;
+	size_t cap = 0;
+	char *buf = NULL;
+	for (ssize_t len; 0 < (len = getline(&buf, &cap, file)); ++line) {
+		if (buf[len - 1] == '\n') buf[len - 1] = '\0';
+		if (!buf[0] || buf[0] == '#') continue;
+
+		char *expr = buf;
+		char *name = strsep(&expr, " \t");
+		if (!expr) {
+			errx(EX_DATAERR, "%s:%d: missing search expression", path, line);
+		}
+		searchAdd(name, &expr[strspn(expr, " \t")]);
+	}
+	if (ferror(file)) err(EX_IOERR, "%s", path);
+	fclose(file);
+	return 0;
+}
+
+static void searchDefault(void) {
+	for (size_t i = 0; i < search.len; ++i) {
+		if (!strcmp(search.names[i], "index")) return;
+	}
+	searchAdd("index", "ALL");
+}
+
+static const char *algo = "REFERENCES";
+
+static void
+searchThreads(struct IMAP *imap, const char *name, const char *expr) {
+	struct Resp resp;
+	struct List threads = {0};
+	struct List envelopeItems = {0};
+	struct Envelope *envelopes = NULL;
+
+	enum Atom thread = atom("thread");
+	fprintf(
+		imap->w, "%s UID THREAD %s UTF-8 %s\r\n",
+		Atoms[thread], algo, expr
+	);
+	for (; resp = respOk(imapResp(imap)), resp.tag != thread; respFree(resp)) {
+		if (resp.resp != AtomThread) continue;
+		threads = resp.data;
+		resp.data = (struct List) {0}; // prevent freeing threads with resp
+	}
+	respFree(resp);
+	if (!threads.len) goto concat;
+
+	enum Atom concat = atom("concat");
+	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);
+		listPush(&envelopeItems, items);
+	}
+	respFree(resp);
+
+concat:
+	concatSearch(name, threads, envelopes);
+
+	for (size_t i = 0; i < threads.len; ++i) {
+		envelopeFree(envelopes[i]);
+	}
+	free(envelopes);
+	listFree(envelopeItems);
+	listFree(threads);
+}
+
 int main(int argc, char *argv[]) {
 	int exitStatus = 0;
 
@@ -90,8 +191,7 @@ int main(int argc, char *argv[]) {
 
 	bool idle = false;
 	const char *mailbox = "Archive";
-	const char *algo = "REFERENCES";
-	const char *search = "ALL";
+	const char *searchPath = NULL;
 
 	for (
 		int opt;
@@ -104,7 +204,7 @@ int main(int argc, char *argv[]) {
 				if (error) err(EX_NOINPUT, "%s", optarg);
 			}
 			break; case 'H': concatHead = optarg;
-			break; case 'S': search = optarg;
+			break; case 'S': searchPath = optarg;
 			break; case 'T': baseTitle = optarg;
 			break; case 'a': algo = optarg;
 			break; case 'h': host = optarg;
@@ -131,6 +231,14 @@ int main(int argc, char *argv[]) {
 	}
 	if (!baseTitle) baseTitle = mailbox;
 
+	if (searchPath) {
+		int error = searchRead(searchPath);
+		if (error) err(EX_NOINPUT, "%s", searchPath);
+	} else {
+		searchRead("SEARCH");
+	}
+	searchDefault();
+
 	char *pass = NULL;
 	if (passPath) {
 		FILE *file = fopen(passPath, "r");
@@ -209,24 +317,18 @@ examine:;
 	} else {
 		goto logout;
 	}
+	createDirs();
 
 	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
-	);
+	fprintf(imap.w, "%s UID THREAD %s UTF-8 ALL\r\n", Atoms[thread], algo);
 	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);
 
-	createDirs();
 	enum Atom export = atom("export");
 	if (!exportFetch(imap.w, export, threads)) {
 		goto concat;
@@ -260,10 +362,6 @@ concat:;
 	respFree(resp);
 
 	concatThreads(threads, envelopes);
-	concatSearch("index", threads, envelopes);
-	fflush(stdout);
-	uidWrite("UIDNEXT", uidNext);
-
 	for (size_t i = 0; i < threads.len; ++i) {
 		envelopeFree(envelopes[i]);
 	}
@@ -271,6 +369,12 @@ concat:;
 	listFree(envelopeItems);
 	listFree(threads);
 
+	for (size_t i = 0; i < search.len; ++i) {
+		searchThreads(&imap, search.names[i], search.exprs[i]);
+	}
+
+	fflush(stdout);
+	uidWrite("UIDNEXT", uidNext);
 	if (!idle) goto logout;
 
 idle: