about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJune McEnroe <june@causal.agency>2020-03-25 18:56:09 -0400
committerJune McEnroe <june@causal.agency>2020-03-25 18:56:09 -0400
commitd99f20c0ff5ef7fb274a09de22b515749be9c7ec (patch)
treec7ec58d4d8fa21732cd9459b6477d2ecb6ef055d
parentTrack MODE in replies (diff)
downloadcatgirl-d99f20c0ff5ef7fb274a09de22b515749be9c7ec.tar.gz
catgirl-d99f20c0ff5ef7fb274a09de22b515749be9c7ec.zip
Add logging functions
The mkdir dance is a bit awkward...
-rw-r--r--Makefile1
-rw-r--r--README.74
-rw-r--r--catgirl.18
-rw-r--r--chat.c5
-rw-r--r--chat.h6
-rw-r--r--log.c112
-rw-r--r--xdg.c22
7 files changed, 154 insertions, 4 deletions
diff --git a/Makefile b/Makefile
index ceaaf38..ec838f5 100644
--- a/Makefile
+++ b/Makefile
@@ -13,6 +13,7 @@ OBJS += config.o
 OBJS += edit.o
 OBJS += handle.o
 OBJS += irc.o
+OBJS += log.o
 OBJS += ui.o
 OBJS += url.o
 OBJS += xdg.o
diff --git a/README.7 b/README.7
index 9daf378..0362661 100644
--- a/README.7
+++ b/README.7
@@ -1,4 +1,4 @@
-.Dd February 12, 2020
+.Dd March 25, 2020
 .Dt README 7
 .Os "Causal Agency"
 .
@@ -132,6 +132,8 @@ line editing
 tab complete
 .It Pa url.c
 URL detection
+.It Pa log.c
+chat logging
 .It Pa config.c
 configuration parsing
 .It Pa xdg.c
diff --git a/catgirl.1 b/catgirl.1
index c3547be..501163c 100644
--- a/catgirl.1
+++ b/catgirl.1
@@ -1,4 +1,4 @@
-.Dd March 23, 2020
+.Dd March 25, 2020
 .Dt CATGIRL 1
 .Os
 .
@@ -8,7 +8,7 @@
 .
 .Sh SYNOPSIS
 .Nm
-.Op Fl Rev
+.Op Fl Relv
 .Op Fl C Ar copy
 .Op Fl H Ar hash
 .Op Fl N Ar send
@@ -155,6 +155,10 @@ Join the comma-separated list of channels
 Load the TLS client private key from
 .Ar path .
 .
+.It Fl l , Cm log
+Log chat events to files in paths
+.Pa $XDG_DATA_HOME/catgirl/log/network/channel/YYYY-MM-DD.log .
+.
 .It Fl n Ar nick , Cm nick = Ar nick
 Set nickname to
 .Ar nick .
diff --git a/chat.c b/chat.c
index 9e3e374..35c0ecd 100644
--- a/chat.c
+++ b/chat.c
@@ -129,7 +129,7 @@ int main(int argc, char *argv[]) {
 	const char *user = NULL;
 	const char *real = NULL;
 
-	const char *Opts = "!C:H:N:O:RS:a:c:eg:h:j:k:n:p:r:s:u:vw:";
+	const char *Opts = "!C:H:N:O:RS:a:c:eg:h:j:k:ln:p:r:s:u:vw:";
 	const struct option LongOpts[] = {
 		{ "insecure", no_argument, NULL, '!' },
 		{ "copy", required_argument, NULL, 'C' },
@@ -144,6 +144,7 @@ int main(int argc, char *argv[]) {
 		{ "host", required_argument, NULL, 'h' },
 		{ "join", required_argument, NULL, 'j' },
 		{ "priv", required_argument, NULL, 'k' },
+		{ "log", no_argument, NULL, 'l' },
 		{ "nick", required_argument, NULL, 'n' },
 		{ "port", required_argument, NULL, 'p' },
 		{ "real", required_argument, NULL, 'r' },
@@ -171,6 +172,7 @@ int main(int argc, char *argv[]) {
 			break; case 'h': host = optarg;
 			break; case 'j': self.join = optarg;
 			break; case 'k': priv = optarg;
+			break; case 'l': logEnable = true;
 			break; case 'n': nick = optarg;
 			break; case 'p': port = optarg;
 			break; case 'r': real = optarg;
@@ -327,5 +329,6 @@ int main(int argc, char *argv[]) {
 	handle(msg);
 
 	ircClose();
+	logClose();
 	uiHide();
 }
diff --git a/chat.h b/chat.h
index 7ffcfcd..0a84053 100644
--- a/chat.h
+++ b/chat.h
@@ -259,8 +259,14 @@ void urlOpenCount(uint id, uint count);
 void urlOpenMatch(uint id, const char *str);
 void urlCopyMatch(uint id, const char *str);
 
+extern bool logEnable;
+void logFormat(uint id, const time_t *time, const char *format, ...)
+	__attribute__((format(printf, 3, 4)));
+void logClose(void);
+
 FILE *configOpen(const char *path, const char *mode);
 FILE *dataOpen(const char *path, const char *mode);
+void dataMkdir(const char *path);
 
 int getopt_config(
 	int argc, char *const *argv,
diff --git a/log.c b/log.c
new file mode 100644
index 0000000..7f99ec4
--- /dev/null
+++ b/log.c
@@ -0,0 +1,112 @@
+/* Copyright (C) 2020  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <err.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sysexits.h>
+#include <time.h>
+
+#include "chat.h"
+
+bool logEnable;
+
+static struct {
+	int year;
+	int month;
+	int day;
+	FILE *file;
+} logs[IDCap];
+
+static FILE *logFile(uint id, const struct tm *tm) {
+	if (
+		logs[id].file &&
+		logs[id].year == tm->tm_year &&
+		logs[id].month == tm->tm_mon &&
+		logs[id].day == tm->tm_mday
+	) return logs[id].file;
+
+	if (logs[id].file) {
+		int error = fclose(logs[id].file);
+		if (error) err(EX_IOERR, "%s", idNames[id]);
+	}
+
+	logs[id].year = tm->tm_year;
+	logs[id].month = tm->tm_mon;
+	logs[id].day = tm->tm_mday;
+
+	char path[PATH_MAX] = "log";
+	size_t len = strlen(path);
+	dataMkdir("");
+	dataMkdir(path);
+
+	path[len++] = '/';
+	for (const char *ch = network.name; *ch; ++ch) {
+		path[len++] = (*ch == '/' ? '_' : *ch);
+	}
+	path[len] = '\0';
+	dataMkdir(path);
+
+	path[len++] = '/';
+	for (const char *ch = idNames[id]; *ch; ++ch) {
+		path[len++] = (*ch == '/' ? '_' : *ch);
+	}
+	path[len] = '\0';
+	dataMkdir(path);
+
+	strftime(&path[len], sizeof(path) - len, "/%F.log", tm);
+	logs[id].file = dataOpen(path, "a");
+	if (!logs[id].file) exit(EX_CANTCREAT);
+
+	setlinebuf(logs[id].file);
+	return logs[id].file;
+}
+
+void logClose(void) {
+	if (!logEnable) return;
+	for (uint id = 0; id < IDCap; ++id) {
+		if (!logs[id].file) continue;
+		int error = fclose(logs[id].file);
+		if (error) err(EX_IOERR, "%s", idNames[id]);
+	}
+}
+
+void logFormat(uint id, const time_t *src, const char *format, ...) {
+	if (!logEnable) return;
+
+	time_t ts = (src ? *src : time(NULL));
+	struct tm *tm = localtime(&ts);
+	if (!tm) err(EX_OSERR, "localtime");
+
+	FILE *file = logFile(id, tm);
+
+	char buf[sizeof("0000-00-00T00:00:00+0000")];
+	strftime(buf, sizeof(buf), "%FT%T%z", tm);
+	fprintf(file, "[%s] ", buf);
+	if (ferror(file)) err(EX_IOERR, "%s", idNames[id]);
+
+	va_list ap;
+	va_start(ap, format);
+	vfprintf(file, format, ap);
+	va_end(ap);
+	if (ferror(file)) err(EX_IOERR, "%s", idNames[id]);
+
+	fprintf(file, "\n");
+	if (ferror(file)) err(EX_IOERR, "%s", idNames[id]);
+}
diff --git a/xdg.c b/xdg.c
index ed2a6e1..c70873a 100644
--- a/xdg.c
+++ b/xdg.c
@@ -134,3 +134,25 @@ local:
 	if (!file) warn("%s", path);
 	return file;
 }
+
+void dataMkdir(const char *path) {
+	const char *home = getenv("HOME");
+	const char *dataHome = getenv("XDG_DATA_HOME");
+
+	char homePath[PATH_MAX];
+	if (dataHome) {
+		snprintf(
+			homePath, sizeof(homePath),
+			"%s/" SUBDIR "/%s", dataHome, path
+		);
+	} else {
+		if (!home) return;
+		snprintf(
+			homePath, sizeof(homePath),
+			"%s/.local/share/" SUBDIR "/%s", home, path
+		);
+	}
+
+	int error = mkdir(homePath, S_IRWXU);
+	if (error && errno != EEXIST) warn("%s", homePath);
+}
so it will only show pre-rename commits. I consider this a reasonable trade-off since the "Back" button still works and the log matches the path displayed in the top bar. Since following renames requires running diff on every commit we consider, I've added a knob to the configuration file to globally enable/disable this feature. Note that we may consider a large number of commits the revision walking machinery no longer performs any path limitation so we have to examine every commit until we find a page full of commits that affect the target path or something related to it. Suggested-by: René Neumann <necoro@necoro.eu> Signed-off-by: John Keeping <john@keeping.me.uk> 2015-08-12shared: make cgit_diff_tree_cb publicJohn Keeping This will allow us to use this nice wrapper function elsewhere, avoiding dealing with the diff queue when we only need to inspect a filepair. Signed-off-by: John Keeping <john@keeping.me.uk> 2015-08-12t0110: Chain together using &&Jason A. Donenfeld 2015-08-12about: always ensure page has a trailing slashJason A. Donenfeld Otherwise we can't easily embed links to other /about/ pages. Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com> 2015-08-12filters: apply HTML escapingLazaros Koromilas http://www.w3.org/International/questions/qa-escapes#use 2015-08-12git: update to v2.5.0Christian Hesse Update to git version v2.5.0. * Upstream commit 5455ee0573a22bb793a7083d593ae1ace909cd4c (Merge branch 'bc/object-id') changed API: for_each_ref() callback functions were taught to name the objects not with "unsigned char sha1[20]" but with "struct object_id". * Upstream commit dcf692625ac569fefbe52269061230f4fde10e47 (path.c: make get_pathname() call sites return const char *) Signed-off-by: Christian Hesse <mail@eworm.de> 2015-08-12Fix processing of repo.hide and repo.ignoreDaniel Reichelt