about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--archive.c1
-rw-r--r--concat.c61
-rw-r--r--export.c12
-rw-r--r--imap.h10
4 files changed, 73 insertions, 11 deletions
diff --git a/archive.c b/archive.c
index 93f8355..c84b74e 100644
--- a/archive.c
+++ b/archive.c
@@ -181,6 +181,7 @@ int main(int argc, char *argv[]) {
 				}
 				createDir("UID");
 				createDir("message");
+				createDir("thread");
 				threads = resp.data;
 				resp.data = (struct List) {0};
 				if (exportFetch(imap, export, threads)) {
diff --git a/concat.c b/concat.c
index 26f24f6..2c58d8a 100644
--- a/concat.c
+++ b/concat.c
@@ -16,9 +16,11 @@
 
 #include <err.h>
 #include <inttypes.h>
+#include <limits.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <sys/stat.h>
 #include <sysexits.h>
 
 #include "archive.h"
@@ -50,6 +52,43 @@ void concatFetch(FILE *imap, enum Atom tag, struct List threads) {
 	fprintf(imap, " (UID ENVELOPE)\r\n");
 }
 
+static const char *uidPath(uint32_t uid, const char *type) {
+	static char buf[PATH_MAX];
+	snprintf(buf, sizeof(buf), "UID/%" PRIu32 ".%s", uid, type);
+	return buf;
+}
+
+static const char *threadPath(const char *messageID, const char *type) {
+	static char buf[PATH_MAX];
+	snprintf(buf, sizeof(buf), "thread/%s.%s", messageID, type);
+	return buf;
+}
+
+static time_t uidNewest(struct List uids, const char *type) {
+	time_t newest = 0;
+	for (size_t i = 0; i < uids.len; ++i) {
+		uint32_t uid = dataCheck(uids.ptr[i], Number).number;
+		const char *path = uidPath(uid, type);
+		struct stat file;
+		int error = stat(path, &file);
+		if (error) err(EX_DATAERR, "%s", path);
+		if (file.st_mtime > newest) newest = file.st_mtime;
+	}
+	return newest;
+}
+
+static int concatFile(FILE *dst, const char *path) {
+	char buf[4096];
+	FILE *src = fopen(path, "r");
+	if (!src) err(EX_DATAERR, "%s", path);
+	for (size_t len; (len = fread(buf, 1, sizeof(buf), src));) {
+		size_t n = fwrite(buf, len, 1, dst);
+		if (!n) return -1;
+	}
+	fclose(src);
+	return 0;
+}
+
 void concatData(struct List threads, struct List items) {
 	uint32_t uid = 0;
 	struct Envelope envelope = {0};
@@ -68,4 +107,26 @@ void concatData(struct List threads, struct List items) {
 	if (!envelope.subject) errx(EX_PROTOCOL, "missing ENVELOPE data item");
 
 	struct List thread = threadFind(threads, uid);
+	struct List flat = {0};
+	listFlatten(&flat, thread);
+
+	int error;
+	const char *path;
+	struct stat file;
+
+	path = threadPath(envelope.messageID, "mbox");
+	error = stat(path, &file);
+	if (error || file.st_mtime < uidNewest(flat, "mbox")) {
+		FILE *mbox = fopen(path, "w");
+		if (!mbox) err(EX_CANTCREAT, "%s", path);
+		for (size_t i = 0; i < flat.len; ++i) {
+			uint32_t uid = dataCheck(flat.ptr[i], Number).number;
+			error = concatFile(mbox, uidPath(uid, "mbox"));
+			if (error) err(EX_IOERR, "%s", path);
+		}
+		error = fclose(mbox);
+		if (error) err(EX_IOERR, "%s", path);
+	}
+
+	listFree(flat);
 }
diff --git a/export.c b/export.c
index d6c7260..3e0933d 100644
--- a/export.c
+++ b/export.c
@@ -38,19 +38,9 @@ static const char *messagePath(const char *messageID, const char *type) {
 	return buf;
 }
 
-static void flatten(struct List *flat, struct List nested) {
-	for (size_t i = 0; i < nested.len; ++i) {
-		if (nested.ptr[i].type == List) {
-			flatten(flat, nested.ptr[i].list);
-		} else {
-			listPush(flat, nested.ptr[i]);
-		}
-	}
-}
-
 bool exportFetch(FILE *imap, enum Atom tag, struct List threads) {
 	struct List uids = {0};
-	flatten(&uids, threads);
+	listFlatten(&uids, threads);
 	for (size_t i = uids.len - 1; i < uids.len; --i) {
 		uint32_t uid = dataCheck(uids.ptr[i], Number).number;
 		int error = 0
diff --git a/imap.h b/imap.h
index f3ca668..3a30d5a 100644
--- a/imap.h
+++ b/imap.h
@@ -125,6 +125,16 @@ static inline void listPush(struct List *list, struct Data data) {
 	list->ptr[list->len++] = data;
 }
 
+static inline void listFlatten(struct List *flat, struct List nested) {
+	for (size_t i = 0; i < nested.len; ++i) {
+		if (nested.ptr[i].type == List) {
+			listFlatten(flat, nested.ptr[i].list);
+		} else {
+			listPush(flat, nested.ptr[i]);
+		}
+	}
+}
+
 static inline void dataFree(struct Data data);
 
 static inline void listFree(struct List list) {