From 9e6a0261f680886111cc536a01f271bf88bd954e Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Fri, 11 Dec 2020 00:39:43 -0500 Subject: 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. --- archive.c | 218 +++++++++++++++++++++++++++----------------------------------- bubger.1 | 23 ++++++- 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 #include #include +#include #include #include #include @@ -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 -- cgit 1.4.1