summary refs log tree commit diff
diff options
context:
space:
mode:
authorJune McEnroe <june@causal.agency>2020-02-28 00:06:46 -0500
committerJune McEnroe <june@causal.agency>2020-02-28 00:13:42 -0500
commitc0fafbc887a147ee77278a5bfd852e171aeb471c (patch)
tree79e75b6c48f3e8b22a1583a9ddafdb1cb18040bf
parentInclude <>/-/* around nicks in coloring (diff)
downloadlitterbox-c0fafbc887a147ee77278a5bfd852e171aeb471c.tar.gz
litterbox-c0fafbc887a147ee77278a5bfd852e171aeb471c.zip
Implement the causal.agency/consumer capability
-rw-r--r--database.h30
-rw-r--r--litterbox.113
-rw-r--r--litterbox.c39
3 files changed, 74 insertions, 8 deletions
diff --git a/database.h b/database.h
index d9b2718..5893f77 100644
--- a/database.h
+++ b/database.h
@@ -31,7 +31,7 @@
 
 #define DATABASE_PATH "litterbox/litterbox.sqlite"
 
-enum { DatabaseVersion = 1 };
+enum { DatabaseVersion = 2 };
 
 #define ENUM_TYPE \
 	X(Privmsg, "privmsg") \
@@ -172,9 +172,10 @@ static inline void dbBindNull(sqlite3_stmt *stmt, const char *param) {
 	errx(EX_SOFTWARE, "sqlite3_bind_null: %s", sqlite3_errmsg(db));
 }
 
-static inline void dbBindInt(sqlite3_stmt *stmt, const char *param, int value) {
-	if (!sqlite3_bind_int(stmt, dbParam(stmt, param), value)) return;
-	errx(EX_SOFTWARE, "sqlite3_bind_int: %s", sqlite3_errmsg(db));
+static inline void
+dbBindInt(sqlite3_stmt *stmt, const char *param, sqlite3_int64 value) {
+	if (!sqlite3_bind_int64(stmt, dbParam(stmt, param), value)) return;
+	errx(EX_SOFTWARE, "sqlite3_bind_int64: %s", sqlite3_errmsg(db));
 }
 
 static inline void dbBindText5(
@@ -301,7 +302,14 @@ static const char *InitSQL = SQL(
 		) SELECT 'delete', * FROM text WHERE event = old.event;
 	END;
 
-	PRAGMA user_version = 1;
+	CREATE TABLE consumers (
+		host STRING NOT NULL,
+		port INTEGER NOT NULL,
+		pos INTEGER NOT NULL,
+		UNIQUE (host, port)
+	);
+
+	PRAGMA user_version = 2;
 
 	COMMIT TRANSACTION;
 );
@@ -328,6 +336,18 @@ static const char *MigrationSQL[] = {
 		PRAGMA user_version = 1;
 		COMMIT TRANSACTION;
 	),
+
+	SQL(
+		BEGIN TRANSACTION;
+		CREATE TABLE consumers (
+			host STRING NOT NULL,
+			port INTEGER NOT NULL,
+			pos INTEGER NOT NULL,
+			UNIQUE (host, port)
+		);
+		PRAGMA user_version = 2;
+		COMMIT TRANSACTION;
+	),
 };
 
 static inline void dbMigrate(void) {
diff --git a/litterbox.1 b/litterbox.1
index 6a6a9e7..198f289 100644
--- a/litterbox.1
+++ b/litterbox.1
@@ -1,4 +1,4 @@
-.Dd January 14, 2020
+.Dd February 28, 2020
 .Dt LITTERBOX 1
 .Os
 .
@@ -323,6 +323,17 @@ daemon implements the following:
 .Re
 .El
 .
+.Ss Extensions
+The
+.Nm
+daemon can take advantage of the
+.Sy causal.agency/consumer
+and
+.Sy causal.agency/passive
+vendor-specific IRCv3 capabilities
+implemented by
+.Xr pounce 1 .
+.
 .Sh AUTHORS
 .An June Bug Aq Mt june@causal.agency
 .
diff --git a/litterbox.c b/litterbox.c
index 0213b31..b8bc25d 100644
--- a/litterbox.c
+++ b/litterbox.c
@@ -33,6 +33,8 @@ int getopt_config(
 	const struct option *longopts, int *longindex
 );
 
+static const char *host;
+static const char *port = "6697";
 static struct tls *client;
 
 static void clientWrite(const char *ptr, size_t len) {
@@ -58,6 +60,7 @@ static void format(const char *format, ...) {
 
 enum { ParamCap = 15 };
 struct Message {
+	size_t pos;
 	char *time;
 	char *nick;
 	char *user;
@@ -75,6 +78,9 @@ static struct Message parse(char *line) {
 			char *tag = strsep(&tags, ";");
 			char *key = strsep(&tag, "=");
 			if (!strcmp(key, "time")) msg.time = tag;
+			if (!strcmp(key, "causal.agency/pos")) {
+				msg.pos = strtoull(tag, NULL, 10);
+			}
 		}
 	}
 	if (line[0] == ':') {
@@ -614,6 +620,20 @@ static int compar(const void *cmd, const void *_handler) {
 	return strcmp(cmd, handler->cmd);
 }
 
+static void updateConsumer(size_t pos) {
+	static sqlite3_stmt *stmt;
+	const char *sql = SQL(
+		INSERT INTO consumers (host, port, pos) VALUES (:host, :port, :pos)
+		ON CONFLICT (host, port) DO
+		UPDATE SET pos = :pos WHERE host = :host AND port = :port;
+	);
+	dbPersist(&stmt, sql);
+	dbBindText(stmt, ":host", host);
+	dbBindText(stmt, ":port", port);
+	dbBindInt(stmt, ":pos", pos);
+	dbRun(stmt);
+}
+
 static void handle(struct Message msg) {
 	if (!msg.cmd) return;
 	const struct Handler *handler = bsearch(
@@ -623,6 +643,7 @@ static void handle(struct Message msg) {
 	if (handler->transaction) {
 		dbExec(SQL(BEGIN TRANSACTION;));
 		handler->fn(&msg);
+		if (msg.pos) updateConsumer(msg.pos);
 		dbExec(SQL(COMMIT TRANSACTION;));
 	} else {
 		handler->fn(&msg);
@@ -650,8 +671,6 @@ int main(int argc, char *argv[]) {
 	bool insecure = false;
 	const char *cert = NULL;
 	const char *priv = NULL;
-	const char *host = NULL;
-	const char *port = "6697";
 	const char *defaultNetwork = NULL;
 
 	const char *nick = "litterbox";
@@ -764,10 +783,26 @@ int main(int argc, char *argv[]) {
 	error = tls_connect(client, host, port);
 	if (error) errx(EX_UNAVAILABLE, "tls_connect: %s", tls_error(client));
 
+	size_t consumerPos = 0;
+	sqlite3_stmt *stmt = dbPrepare(
+		SQL(SELECT pos FROM consumers WHERE host = :host AND port = :port;)
+	);
+	dbBindText(stmt, ":host", host);
+	dbBindText(stmt, ":port", port);
+	if (dbStep(stmt) == SQLITE_ROW) {
+		consumerPos = sqlite3_column_int64(stmt, 0);
+	}
+	sqlite3_finalize(stmt);
+
 	if (pass) format("PASS :%s\r\n", pass);
 	if (cert) format("CAP REQ :sasl\r\n");
 	format("CAP REQ :server-time\r\n");
 	format("CAP REQ :causal.agency/passive\r\n");
+	if (consumerPos) {
+		format("CAP REQ :causal.agency/consumer=%zu\r\n", consumerPos);
+	} else {
+		format("CAP REQ :causal.agency/consumer\r\n");
+	}
 	if (!cert) format("CAP END\r\n");
 	format("NICK :%s\r\nUSER %s 0 * :Litterbox\r\n", nick, user);