diff options
| -rw-r--r-- | Makefile | 1 | ||||
| -rw-r--r-- | README.7 | 4 | ||||
| -rw-r--r-- | catgirl.1 | 8 | ||||
| -rw-r--r-- | chat.c | 5 | ||||
| -rw-r--r-- | chat.h | 6 | ||||
| -rw-r--r-- | log.c | 112 | ||||
| -rw-r--r-- | xdg.c | 22 | 
7 files changed, 154 insertions, 4 deletions
@@ -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 @@ -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 @@ -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 . @@ -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();  } @@ -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, @@ -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]); +} @@ -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); +}  | 
