diff options
author | June McEnroe <june@causal.agency> | 2021-06-11 17:32:18 -0400 |
---|---|---|
committer | June McEnroe <june@causal.agency> | 2021-06-11 18:29:36 -0400 |
commit | f1f9c20bd8d9d175f8db73b062a60a9f79e95a7e (patch) | |
tree | c0db35013b96f6c6fd5322c5f058b681a790df0e /archive.c | |
parent | Generalize index.{atom,html} to search pages (diff) | |
download | bubger-f1f9c20bd8d9d175f8db73b062a60a9f79e95a7e.tar.gz bubger-f1f9c20bd8d9d175f8db73b062a60a9f79e95a7e.zip |
Generate arbitrary search pages and feeds
First export ALL threads, then generate search pages. Skip search threads that weren't exported by the ALL search, i.e. non-root threads.
Diffstat (limited to '')
-rw-r--r-- | archive.c | 134 |
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: |