about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJune McEnroe <june@causal.agency>2021-06-12 19:20:47 -0400
committerJune McEnroe <june@causal.agency>2021-06-12 19:20:47 -0400
commit9b09a5ff483aef05dc5b4d9ab0fd0243d21cb1d3 (patch)
tree2a2be7ddda0e4161833e0e35cdf8585aa8016fc7
parentAdd margin between header navs (diff)
downloadbubger-9b09a5ff483aef05dc5b4d9ab0fd0243d21cb1d3.tar.gz
bubger-9b09a5ff483aef05dc5b4d9ab0fd0243d21cb1d3.zip
Use SEARCH for a subset of thread roots
This does way less duplicate work by fetching all threads and all
thread root envelopes once, then doing searches for subsets of
thread roots.
Diffstat (limited to '')
-rw-r--r--archive.c76
-rw-r--r--archive.h34
-rw-r--r--atom.c12
-rw-r--r--concat.c86
-rw-r--r--html.c75
-rw-r--r--imap.h1
6 files changed, 154 insertions, 130 deletions
diff --git a/archive.c b/archive.c
index 74491a1..20ea5d0 100644
--- a/archive.c
+++ b/archive.c
@@ -80,12 +80,7 @@ static void createDirs(void) {
 	createDir("thread");
 }
 
-static struct Search {
-	size_t cap;
-	size_t len;
-	char **names;
-	char **exprs;
-} search;
+struct Search search;
 
 static void searchAdd(const char *name, const char *expr) {
 	if (search.len == search.cap) {
@@ -134,51 +129,24 @@ static void searchDefault(void) {
 	searchAdd("index", "ALL");
 }
 
-static const char *algo = "REFERENCES";
-
-static void
-searchThreads(struct IMAP *imap, const char *name, const char *expr) {
+static void searchThreads(
+	struct IMAP *imap, struct List threads, const struct Envelope *envelopes,
+	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
+	struct List roots = {0};
+
+	enum Atom search = atom("search");
+	concatSearch(imap->w, search, threads, expr);
+	for (; resp = respOk(imapResp(imap)), resp.tag != search; respFree(resp)) {
+		if (resp.resp != AtomSearch) continue;
+		roots = resp.data;
+		resp.data = (struct List) {0}; // prevent freeing roots 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, search.names, search.len);
-
-	for (size_t i = 0; i < threads.len; ++i) {
-		envelopeFree(envelopes[i]);
-	}
-	free(envelopes);
-	listFree(envelopeItems);
-	listFree(threads);
+	concatIndex(name, roots, threads, envelopes);
+	listFree(roots);
 }
 
 int main(int argc, char *argv[]) {
@@ -191,6 +159,7 @@ int main(int argc, char *argv[]) {
 
 	bool idle = false;
 	const char *mailbox = "Archive";
+	const char *algo = "REFERENCES";
 	const char *searchPath = NULL;
 
 	for (
@@ -198,7 +167,7 @@ int main(int argc, char *argv[]) {
 		0 < (opt = getopt(argc, argv, "A:C:H:S:T:a:h:im:p:qs:u:vw:y:"));
 	) {
 		switch (opt) {
-			break; case 'A': concatSearchEntries = strtoul(optarg, NULL, 10);
+			break; case 'A': concatIndexEntries = strtoul(optarg, NULL, 10);
 			break; case 'C': {
 				int error = chdir(optarg);
 				if (error) err(EX_NOINPUT, "%s", optarg);
@@ -362,6 +331,13 @@ concat:;
 	respFree(resp);
 
 	concatThreads(threads, envelopes);
+	for (size_t i = 0; i < search.len; ++i) {
+		searchThreads(
+			&imap, threads, envelopes,
+			search.names[i], search.exprs[i]
+		);
+	}
+
 	for (size_t i = 0; i < threads.len; ++i) {
 		envelopeFree(envelopes[i]);
 	}
@@ -369,10 +345,6 @@ 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;
diff --git a/archive.h b/archive.h
index 7740b98..d6b9608 100644
--- a/archive.h
+++ b/archive.h
@@ -44,7 +44,7 @@
 #define PATH_THREAD "thread/[messageID].[type]"
 #define PATH_ATTACHMENT \
 	"attachment/[messageID]/[section]/[name][disposition][.][subtype]"
-#define PATH_SEARCH "[name].[type]"
+#define PATH_INDEX "[name].[type]"
 
 #define MBOX_HEADERS \
 	"Date Subject From Sender Reply-To To Cc Bcc " \
@@ -60,6 +60,13 @@ extern const char *baseMailto;
 extern const char *baseSubscribe;
 extern const char *baseStylesheet;
 
+extern struct Search {
+	size_t cap;
+	size_t len;
+	char **names;
+	char **exprs;
+} search;
+
 static inline struct U32 {
 	char s[sizeof("4294967295")];
 } u32(uint32_t u) {
@@ -215,15 +222,18 @@ bool exportFetch(FILE *imap, enum Atom tag, struct List threads);
 bool exportData(FILE *imap, enum Atom tag, struct List items);
 
 extern const char *concatHead;
-extern size_t concatSearchEntries;
+extern size_t concatIndexEntries;
 void concatFetch(FILE *imap, enum Atom tag, struct List threads);
+void concatSearch(
+	FILE *imap, enum Atom tag, struct List threads, const char *expr
+);
 void concatData(
 	struct List threads, struct Envelope *envelopes, struct List items
 );
 void concatThreads(struct List threads, const struct Envelope *envelopes);
-void concatSearch(
-	const char *name, struct List threads, const struct Envelope *envelopes,
-	char *searches[const], size_t len
+void concatIndex(
+	const char *name, struct List roots,
+	struct List threads, const struct Envelope *envelopes
 );
 
 int mboxFrom(FILE *file);
@@ -235,8 +245,8 @@ int atomContent(FILE *file, const char *content);
 int atomEntryClose(FILE *file);
 int atomThreadOpen(FILE *file, const struct Envelope *envelope);
 int atomThreadClose(FILE *file);
-int atomSearchOpen(FILE *file, const char *name);
-int atomSearchClose(FILE *file);
+int atomIndexOpen(FILE *file, const char *name);
+int atomIndexClose(FILE *file);
 
 int htmlMessageOpen(FILE *file, const struct Envelope *envelope, bool nested);
 int htmlInline(FILE *file, const struct BodyPart *part, const char *content);
@@ -251,11 +261,9 @@ int htmlThreadOpen(FILE *file, const struct Envelope *envelope);
 int htmlSubthreadOpen(FILE *file, struct List thread);
 int htmlSubthreadClose(FILE *file);
 int htmlThreadClose(FILE *file);
-int htmlSearchHead(FILE *file, const char *name);
-int htmlSearchOpen(
-	FILE *file, const char *name, char *searches[const], size_t len
-);
-int htmlSearchThread(
+int htmlIndexHead(FILE *file, const char *name);
+int htmlIndexOpen(FILE *file, const char *name);
+int htmlIndexThread(
 	FILE *file, const struct Envelope *envelope, struct List thread
 );
-int htmlSearchClose(FILE *file);
+int htmlIndexClose(FILE *file);
diff --git a/atom.c b/atom.c
index a58f71c..b49a2b7 100644
--- a/atom.c
+++ b/atom.c
@@ -163,18 +163,18 @@ int atomThreadClose(FILE *file) {
 	return templateRender(file, Q(</feed>), NULL, NULL);
 }
 
-static char *atomSearchURL(const char *name, const char *type) {
+static char *atomIndexURL(const char *name, const char *type) {
 	struct Variable vars[] = {
 		{ "name", name },
 		{ "type", type },
 		{0},
 	};
-	return templateString("/" PATH_SEARCH, vars, escapeURL);
+	return templateString("/" PATH_INDEX, vars, escapeURL);
 }
 
-int atomSearchOpen(FILE *file, const char *name) {
-	char *atom = atomSearchURL(name, "atom");
-	char *html = atomSearchURL(name, "html");
+int atomIndexOpen(FILE *file, const char *name) {
+	char *atom = atomIndexURL(name, "atom");
+	char *html = atomIndexURL(name, "html");
 	const char *template = XML_DECL Q(
 		<feed xmlns="http://www.w3.org/2005/Atom">
 		<generator uri="[generator]">bubger</generator>
@@ -200,6 +200,6 @@ int atomSearchOpen(FILE *file, const char *name) {
 	return error;
 }
 
-int atomSearchClose(FILE *file) {
+int atomIndexClose(FILE *file) {
 	return templateRender(file, Q(</feed>), NULL, NULL);
 }
diff --git a/concat.c b/concat.c
index 800a7a2..54a49a7 100644
--- a/concat.c
+++ b/concat.c
@@ -27,6 +27,7 @@
 
 #include <err.h>
 #include <inttypes.h>
+#include <stdbool.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -54,6 +55,17 @@ void concatFetch(FILE *imap, enum Atom tag, struct List threads) {
 	fprintf(imap, " (UID ENVELOPE)\r\n");
 }
 
+void concatSearch(
+	FILE *imap, enum Atom tag, struct List threads, const char *expr
+) {
+	fprintf(imap, "%s UID SEARCH CHARSET UTF-8 UID ", Atoms[tag]);
+	for (size_t i = 0; i < threads.len; ++i) {
+		uint32_t root = threadRoot(dataCheck(threads.ptr[i], List).list);
+		fprintf(imap, "%s%" PRIu32, (i ? "," : ""), root);
+	}
+	fprintf(imap, " %s\r\n", expr);
+}
+
 void concatData(
 	struct List threads, struct Envelope *envelopes, struct List items
 ) {
@@ -230,13 +242,13 @@ void concatThreads(struct List threads, const struct Envelope *envelopes) {
 	}
 }
 
-static char *searchPath(const char *name, const char *type) {
+static char *indexPath(const char *name, const char *type) {
 	struct Variable vars[] = {
 		{ "name", name },
 		{ "type", type },
 		{0},
 	};
-	return templateString(PATH_SEARCH, vars, escapePath);
+	return templateString(PATH_INDEX, vars, escapePath);
 }
 
 static int numberCompare(const void *_a, const void *_b) {
@@ -261,23 +273,43 @@ static int sortCompare(const void *_a, const void *_b) {
 	}
 }
 
-size_t concatSearchEntries = 20;
+size_t concatIndexEntries = 20;
 
-void concatSearch(
-	const char *name, struct List threads, const struct Envelope *envelopes,
-	char *searches[const], size_t len
+void concatIndex(
+	const char *name, struct List roots,
+	struct List threads, const struct Envelope *envelopes
 ) {
-	char *path = searchPath(name, "atom");
+	bool *bitmap = calloc(threads.len, sizeof(*bitmap));
+	if (!bitmap) err(EX_OSERR, "calloc");
+
+	for (size_t i = 0; i < roots.len; ++i) {
+		dataCheck(roots.ptr[i], Number);
+	}
+	for (size_t i = 0; i < threads.len; ++i) {
+		uint32_t root = threadRoot(dataCheck(threads.ptr[i], List).list);
+		for (size_t j = 0; j < roots.len; ++j) {
+			if (root == roots.ptr[j].number) {
+				bitmap[i] = true;
+				break;
+			}
+		}
+	}
+
+	char *path = indexPath(name, "atom");
 	FILE *file = fopen(path, "w");
 	if (!file) err(EX_CANTCREAT, "%s", path);
 
-	int error = atomSearchOpen(file, name);
+	int error = atomIndexOpen(file, name);
 	if (error) err(EX_IOERR, "%s", path);
 
 	struct List flat = {0};
-	listFlatten(&flat, threads);
+	for (size_t i = 0; i < threads.len; ++i) {
+		if (!bitmap[i]) continue;
+		listFlatten(&flat, threads.ptr[i].list);
+	}
 	qsort(flat.ptr, flat.len, sizeof(*flat.ptr), numberCompare);
-	for (size_t i = 0; i < flat.len && i < concatSearchEntries; ++i) {
+
+	for (size_t i = 0; i < flat.len && i < concatIndexEntries; ++i) {
 		uint32_t uid = dataCheck(flat.ptr[i], Number).number;
 		char *src = uidPath(uid, "atom");
 		error = concatFile(file, src);
@@ -286,30 +318,37 @@ void concatSearch(
 	}
 	listFree(flat);
 
-	error = atomSearchClose(file) || fclose(file);
+	error = atomIndexClose(file) || fclose(file);
 	if (error) err(EX_IOERR, "%s", path);
 	if (!quiet) printf("%s\n", path);
 	free(path);
-
-	struct Sort *order = calloc(threads.len, sizeof(*order));
+	
+	size_t len = 0;
+	struct Sort *order = calloc(roots.len, sizeof(*order));
 	if (!order) err(EX_OSERR, "calloc");
 
 	for (size_t i = 0; i < threads.len; ++i) {
-		order[i].index = i;
-		order[i].created = envelopes[i].time;
+		if (!bitmap[i]) continue;
 
 		struct stat status;
 		char *path = threadPath(envelopes[i].messageID, "html");
-		if (!stat(path, &status)) order[i].updated = status.st_mtime;
+		error = stat(path, &status);
+		if (error) err(EX_DATAERR, "%s", path);
 		free(path);
+
+		order[len].index = i;
+		order[len].created = envelopes[i].time;
+		order[len].updated = status.st_mtime;
+		len++;
 	}
-	qsort(order, threads.len, sizeof(*order), sortCompare);
+	qsort(order, len, sizeof(*order), sortCompare);
+	free(bitmap);
 
-	path = searchPath(name, "html");
+	path = indexPath(name, "html");
 	file = fopen(path, "w");
 	if (!file) err(EX_CANTCREAT, "%s", path);
 
-	error = htmlSearchHead(file, name);
+	error = htmlIndexHead(file, name);
 	if (error) err(EX_IOERR, "%s", path);
 
 	if (concatHead) {
@@ -317,19 +356,18 @@ void concatSearch(
 		if (error) err(EX_IOERR, "%s", path);
 	}
 
-	error = htmlSearchOpen(file, name, searches, len);
+	error = htmlIndexOpen(file, name);
 	if (error) err(EX_IOERR, "%s", path);
 
-	for (size_t i = threads.len - 1; i < threads.len; --i) {
-		if (!order[i].updated) continue;
+	for (size_t i = len - 1; i < len; --i) {
 		const struct Envelope *envelope = &envelopes[order[i].index];
 		struct List thread = dataCheck(threads.ptr[order[i].index], List).list;
-		error = htmlSearchThread(file, envelope, thread);
+		error = htmlIndexThread(file, envelope, thread);
 		if (error) err(EX_IOERR, "%s", path);
 	}
 	free(order);
 
-	error = htmlSearchClose(file) || fclose(file);
+	error = htmlIndexClose(file) || fclose(file);
 	if (error) err(EX_IOERR, "%s", path);
 	if (!quiet) printf("%s\n", path);
 	free(path);
diff --git a/html.c b/html.c
index 6b25996..1dc531c 100644
--- a/html.c
+++ b/html.c
@@ -512,17 +512,17 @@ int htmlThreadClose(FILE *file) {
 		|| htmlFooter(file);
 }
 
-static char *htmlSearchURL(const char *name, const char *type) {
+static char *htmlIndexURL(const char *name, const char *type) {
 	struct Variable vars[] = {
 		{ "name", name },
 		{ "type", type },
 		{0},
 	};
-	return templateString(PATH_SEARCH, vars, escapeURL);
+	return templateString(PATH_INDEX, vars, escapeURL);
 }
 
-int htmlSearchHead(FILE *file, const char *name) {
-	char *atom = htmlSearchURL(name, "atom");
+int htmlIndexHead(FILE *file, const char *name) {
+	char *atom = htmlIndexURL(name, "atom");
 	const char *template = Q(
 		<!DOCTYPE html>
 		<meta charset="utf-8">
@@ -545,40 +545,47 @@ int htmlSearchHead(FILE *file, const char *name) {
 	return error;
 }
 
-static int htmlSearchNav(
-	FILE *file, const char *name, char *searches[const], size_t len
-) {
-	if (len < 2 || strcmp(name, "index")) return 0;
-	int error = templateRender(file, Q(<nav>), NULL, NULL);
+static int htmlSearchNavItem(FILE *file, const char *name, bool current) {
+	char *url = NULL;
+	const char *template;
+	if (current) {
+		template = Q(<b>[name]</b>) " ";
+	} else {
+		url = htmlIndexURL(name, "html");
+		template = Q(<a href="[url]">[name]</a>) " ";
+	}
+	struct Variable vars[] = {
+		{ "url", url },
+		{ "name", name },
+		{0},
+	};
+	int error = templateRender(file, template, vars, escapeXML);
+	free(url);
+	return error;
+}
+
+static int htmlSearchNav(FILE *file, const char *name) {
+	if (search.len < 2) return 0;
+	int error = 0
+		|| templateRender(file, Q(<nav>), NULL, NULL)
+		|| htmlSearchNavItem(file, "index", !strcmp(name, "index"));
 	if (error) return error;
-	for (size_t i = 0; i < len; ++i) {
-		if (!strcmp(searches[i], "index")) continue;
-		char *url = htmlSearchURL(searches[i], "html");
-		const char *template = Q(<a href="[url]">[search]</a>) " ";
-		struct Variable vars[] = {
-			{ "url", url },
-			{ "search", searches[i] },
-			{0},
-		};
-		error = templateRender(file, template, vars, escapeXML);
-		free(url);
+	for (size_t i = 0; i < search.len; ++i) {
+		if (!strcmp(search.names[i], "index")) continue;
+		error = htmlSearchNavItem(
+			file, search.names[i], !strcmp(search.names[i], name)
+		);
 		if (error) return error;
 	}
 	return templateRender(file, Q(</nav>), NULL, NULL);
 }
 
-int htmlSearchOpen(
-	FILE *file, const char *name, char *searches[const], size_t len
-) {
-	char *index = htmlSearchURL("index", "html");
-	char *atom = htmlSearchURL(name, "atom");
+int htmlIndexOpen(FILE *file, const char *name) {
+	char *atom = htmlIndexURL(name, "atom");
 	const char *template = Q(
 		<header class="index">
 			<h1>[+name][name] - [-][title]</h1>
 			<nav>
-				[+name]
-				<a href="[index]">index</a>
-				[-]
 				<a href="[atom]">follow</a>
 				[+subscribe]
 				<a href="[subscribe]">subscribe</a>
@@ -596,7 +603,6 @@ int htmlSearchOpen(
 	struct Variable vars[] = {
 		{ "name", (strcmp(name, "index") ? name : NULL) },
 		{ "title", baseTitle },
-		{ "index", index },
 		{ "atom", atom },
 		{ "subscribe", baseSubscribe },
 		{ "mailto", baseMailto },
@@ -604,14 +610,13 @@ int htmlSearchOpen(
 	};
 	int error = 0
 		|| templateRender(file, template, vars, escapeXML)
-		|| htmlSearchNav(file, name, searches, len)
+		|| htmlSearchNav(file, name)
 		|| templateRender(file, tail, NULL, NULL);
-	free(index);
 	free(atom);
 	return error;
 }
 
-static char *htmlSearchThreadURL(const struct Envelope *envelope) {
+static char *htmlIndexThreadURL(const struct Envelope *envelope) {
 	struct Variable vars[] = {
 		{ "messageID", envelope->messageID },
 		{ "type", "html" },
@@ -620,10 +625,10 @@ static char *htmlSearchThreadURL(const struct Envelope *envelope) {
 	return templateString(PATH_THREAD, vars, escapeURL);
 }
 
-int htmlSearchThread(
+int htmlIndexThread(
 	FILE *file, const struct Envelope *envelope, struct List thread
 ) {
-	char *url = htmlSearchThreadURL(envelope);
+	char *url = htmlIndexThreadURL(envelope);
 	const char *template = Q(
 		<li>
 			<h2 class="Subject"><a href="[url]">[subject]</a></h2>
@@ -648,7 +653,7 @@ int htmlSearchThread(
 	return error;
 }
 
-int htmlSearchClose(FILE *file) {
+int htmlIndexClose(FILE *file) {
 	return 0
 		|| templateRender(file, Q(</ol></main>), NULL, NULL)
 		|| htmlFooter(file);
diff --git a/imap.h b/imap.h
index 015c8b8..e5a925a 100644
--- a/imap.h
+++ b/imap.h
@@ -48,6 +48,7 @@
 	X(AtomUIDNext, "UIDNEXT") \
 	X(AtomUIDValidity, "UIDVALIDITY") \
 	X(AtomFetch, "FETCH") \
+	X(AtomSearch, "SEARCH") \
 	X(AtomThread, "THREAD") \
 	X(AtomUID, "UID") \
 	X(AtomEnvelope, "ENVELOPE") \