hirc

[archived] IRC client
git clone https://hhvn.uk/hirc
git clone git://hhvn.uk/hirc
Log | Files | Refs

hist.c (12092B)


      1 /*
      2  * src/hist.c from hirc
      3  *
      4  * Copyright (c) 2021-2022 hhvn <dev@hhvn.uk>
      5  *
      6  * Permission to use, copy, modify, and distribute this software for any
      7  * purpose with or without fee is hereby granted, provided that the above
      8  * copyright notice and this permission notice appear in all copies.
      9  *
     10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     17  *
     18  */
     19 
     20 #include <stdio.h>
     21 #include <fcntl.h>
     22 #include <errno.h>
     23 #include <regex.h>
     24 #include <stdlib.h>
     25 #include <unistd.h>
     26 #include <string.h>
     27 #include <stdarg.h>
     28 #include <stdlib.h>
     29 #include <ncurses.h>
     30 #include <sys/stat.h>
     31 #include "hirc.h"
     32 
     33 void
     34 hist_free(struct History *history) {
     35 	param_free(history->_params);
     36 	nick_free(history->from);
     37 	pfree(&history->raw);
     38 	pfree(&history->format);
     39 	pfree(&history->rformat);
     40 	pfree(&history);
     41 }
     42 
     43 void
     44 hist_free_list(struct HistInfo *histinfo) {
     45 	struct History *p, *prev;
     46 
     47 	if (!histinfo->history)
     48 		return;
     49 
     50 	prev = histinfo->history;
     51 	p = prev->next;
     52 	while (prev) {
     53 		hist_free(prev);
     54 		prev = p;
     55 		if (p)
     56 			p = p->next;
     57 	}
     58 	histinfo->history = NULL;
     59 }
     60 
     61 struct History *
     62 hist_create(struct HistInfo *histinfo, struct Nick *from, char *msg,
     63 		enum Activity activity, time_t timestamp, enum HistOpt options) {
     64 	struct History *new;
     65 	struct Nick *np;
     66 	char *nick;
     67 
     68 	assert_warn(msg, NULL);
     69 
     70 	new = emalloc(sizeof(struct History));
     71 	new->prev = new->next = NULL;
     72 	new->timestamp = timestamp ? timestamp : time(NULL);
     73 	new->activity = activity;
     74 	new->raw = estrdup(msg);
     75 	new->_params = new->params = param_create(msg);
     76 	new->rformat = new->format = NULL;
     77 	new->options = options;
     78 	new->origin = histinfo;
     79 
     80 	if (from) {
     81 		new->from = nick_dup(from);
     82 	} else if (**new->_params == ':') {
     83 		np = NULL;
     84 		if (histinfo->channel && histinfo->channel->nicks) {
     85 			prefix_tokenize(*new->_params, &nick, NULL, NULL);
     86 			np = nick_get(&histinfo->channel->nicks, nick);
     87 			free(nick);
     88 		}
     89 
     90 		if (np)
     91 			new->from = nick_dup(np);
     92 		else
     93 			new->from = nick_create(*new->_params, ' ', histinfo->server);
     94 	} else {
     95 		new->from = NULL;
     96 	}
     97 
     98 	/* Update histinfo->server->self */
     99 	if (new->from && new->from->self && histinfo->server) {
    100 		if (new->from->ident && strcmp_n(new->from->ident, histinfo->server->self->ident) != 0) {
    101 			free(histinfo->server->self->ident);
    102 			histinfo->server->self->ident = strdup(new->from->ident);
    103 		}
    104 		if (new->from->host && strcmp_n(new->from->host, histinfo->server->self->host) != 0) {
    105 			free(histinfo->server->self->host);
    106 			histinfo->server->self->host = strdup(new->from->host);
    107 		}
    108 	}
    109 
    110 	if (**new->_params == ':')
    111 		new->params++;
    112 
    113 	return new;
    114 }
    115 
    116 struct History *
    117 hist_addp(struct HistInfo *histinfo, struct History *p, enum Activity activity, enum HistOpt options) {
    118 	return hist_add(histinfo, p->raw, activity, p->timestamp, options);
    119 }
    120 
    121 struct History *
    122 hist_add(struct HistInfo *histinfo,
    123 		char *msg, enum Activity activity,
    124 		time_t timestamp, enum HistOpt options) {
    125 	struct Nick *from = NULL;
    126 	struct History *new, *p;
    127 	struct Ignore *ign;
    128 	struct tm ptm, ctm;
    129 	int i;
    130 
    131 	assert_warn(histinfo && msg, NULL);
    132 
    133 	if (options & HIST_MAIN) {
    134 		if (options & HIST_TMP && histinfo == main_buf) {
    135 			hist_add(main_buf, msg, activity, timestamp, options & ~(HIST_MAIN|HIST_TMP|HIST_LOG));
    136 			new = NULL;
    137 			goto ui;
    138 		} else if (histinfo != main_buf) {
    139 			hist_add(main_buf, msg, activity, timestamp, options & ~(HIST_MAIN|HIST_TMP|HIST_LOG));
    140 		} else {
    141 			ui_error("HIST_MAIN specified, but history is &main_buf", NULL);
    142 		}
    143 	}
    144 
    145 	if (options & HIST_SELF && histinfo->server) {
    146 		if (histinfo->channel && histinfo->channel->nicks)
    147 			from = nick_get(&histinfo->channel->nicks, histinfo->server->self->nick);
    148 		if (!from)
    149 			from = histinfo->server->self;
    150 	}
    151 
    152 	new = hist_create(histinfo, from, msg, activity, timestamp, options);
    153 
    154 	if (!(options & HIST_NIGN)) {
    155 		for (ign = ignores; ign; ign = ign->next) {
    156 			if ((!ign->server ||
    157 					(histinfo->server && strcmp_n(ign->server, histinfo->server->name) == 0)) &&
    158 					(!ign->format || strcmp_n(format_get(new), ign->format) == 0) &&
    159 					regexec(&ign->regex, msg, 0, NULL, 0) == 0) {
    160 				if (!ign->noact) {
    161 					options |= HIST_IGN;
    162 					new->options = options;
    163 				}
    164 				activity = Activity_none;
    165 			}
    166 		}
    167 	}
    168 
    169 	if (strcmp(msg, "SELF_NEW_DAY") != 0 &&
    170 			histinfo && histinfo->history &&
    171 			histinfo->history->timestamp < timestamp &&
    172 			!(histinfo->history->options & HIST_RLOG) &&
    173 			!(histinfo->history->options & HIST_GREP) &&
    174 			!(options & HIST_GREP)) {
    175 		localtime_r(&histinfo->history->timestamp, &ptm);
    176 		localtime_r(&timestamp, &ctm);
    177 		if (ptm.tm_mday != ctm.tm_mday || ptm.tm_mon != ctm.tm_mon || ptm.tm_year != ctm.tm_year) {
    178 			ctm.tm_sec = ctm.tm_min = ctm.tm_hour = 0;
    179 			hist_format(histinfo, Activity_none, histinfo->server ? HIST_DFL : HIST_SHOW,
    180 					"SELF_NEW_DAY %lld :day changed to", (long long)mktime(&ctm));
    181 			histinfo->history->timestamp = mktime(&ctm); /* set timestamp of SELF_NEW_DAY:
    182 									really hist_format should take a timestamp */
    183 		}
    184 	}
    185 
    186 	if (!histinfo->history) {
    187 		histinfo->history = new;
    188 		goto ui;
    189 	}
    190 
    191 	for (i=0, p = histinfo->history; p && p->next; p = p->next, i++);
    192 	if (i == (HIST_MAX-1)) {
    193 		pfree(&p->next);
    194 		p->next = NULL;
    195 	}
    196 
    197 	new->next = histinfo->history;
    198 	new->next->prev = new;
    199 	histinfo->history = new;
    200 
    201 ui:
    202 	if (options & HIST_SHOW &&
    203 			activity >= Activity_hilight &&
    204 			config_getl("misc.bell"))
    205 		beep();
    206 
    207 	if (histinfo && options & HIST_SHOW &&
    208 			activity > histinfo->activity &&
    209 			histinfo != selected.history) {
    210 		histinfo->activity = activity;
    211 		windows[Win_buflist].refresh = 1;
    212 	}
    213 
    214 	if (histinfo && options & HIST_SHOW && histinfo != selected.history) {
    215 		if (options & HIST_IGN)
    216 			histinfo->ignored++;
    217 		else
    218 			histinfo->unread++;
    219 	}
    220 
    221 	if (options & HIST_LOG) {
    222 		if (histinfo->server)
    223 			hist_log(new);
    224 		else
    225 			ui_error("HIST_LOG specified, but server is NULL", NULL);
    226 	}
    227 
    228 	/* TODO: this triggers way too often, need to have some sort of delay */
    229 	if (selected.history == histinfo) {
    230 		if (options & HIST_SELF)
    231 			windows[Win_main].scroll = -1;
    232 		else if (windows[Win_main].scroll >= 0)
    233 			windows[Win_main].scroll += 1;
    234 		windows[Win_main].refresh = 1;
    235 	}
    236 
    237 	return new;
    238 }
    239 
    240 void
    241 hist_purgeopt(struct HistInfo *histinfo, enum HistOpt options) {
    242 	struct History *p, *next;
    243 
    244 	assert_warn(histinfo,);
    245 
    246 	p = histinfo->history;
    247 
    248 	for (; p; p = next) {
    249 		next = p->next;
    250 		if (p->options & options) {
    251 			if (p->prev)
    252 				p->prev->next = p->next;
    253 			else
    254 				histinfo->history = p->next;
    255 
    256 			if (p->next)
    257 				p->next->prev = p->prev;
    258 			else if (!p->prev)
    259 				histinfo->history = NULL;
    260 
    261 			pfree(&p);
    262 		}
    263 	}
    264 }
    265 
    266 struct History *
    267 hist_format(struct HistInfo *histinfo, enum Activity activity, enum HistOpt options, char *format, ...) {
    268 	char msg[1024];
    269 	va_list ap;
    270 
    271 	va_start(ap, format);
    272 	vsnprintf(msg, sizeof(msg), format, ap);
    273 	va_end(ap);
    274 
    275 	if (histinfo)
    276 		return hist_add(histinfo, msg, Activity_status, 0, options);
    277 	else
    278 		return hist_create(histinfo, NULL, msg, Activity_status, 0, options);
    279 }
    280 
    281 int
    282 hist_log(struct History *hist) {
    283 	char filename[2048];
    284 	FILE *f;
    285 	char *logdir;
    286 	int ret;
    287 	struct stat st;
    288 	char *nick, *ident, *host, *raw;
    289 
    290 	if (!config_getl("log.toggle"))
    291 		return -2;
    292 
    293 	if ((logdir = config_gets("log.dir")) == NULL)
    294 		return -3;
    295 
    296 	logdir = homepath(logdir);
    297 
    298 	if (!hist || !hist->origin || !hist->origin->server)
    299 		return -4;
    300 
    301 	if (stat(logdir, &st) == -1) {
    302 		if (mkdir(logdir, 0700) == -1) {
    303 			ui_error("Could not create dir '%s': %s", logdir, strerror(errno));
    304 			return -5;
    305 		}
    306 	}
    307 
    308 	/* use ',' as it's illegal in dns hostnames (I think) and channel names */
    309 	if (hist->origin->channel)
    310 		snprintf(filename, sizeof(filename), "%s/%s,%s.log", logdir, hist->origin->server->name, hist->origin->channel->name);
    311 	else
    312 		snprintf(filename, sizeof(filename), "%s/%s.log", logdir, hist->origin->server->name);
    313 
    314 	if (!(f = fopen(filename, "a"))) {
    315 		ui_error("Could not open '%s': %s", filename, strerror(errno));
    316 		return -6;
    317 	}
    318 
    319 	if (hist->from) {
    320 		nick  = hist->from->nick  ? hist->from->nick  : " ";
    321 		ident = hist->from->ident ? hist->from->ident : " ";
    322 		host  = hist->from->host  ? hist->from->host  : " ";
    323 	} else {
    324 		nick = ident = host = " ";
    325 	}
    326 
    327 	if (*hist->raw == ':' && strchr(hist->raw, ' '))
    328 		raw = strchr(hist->raw, ' ') + 1;
    329 	else
    330 		raw = hist->raw;
    331 
    332 	ret = fprintf(f,
    333 			"v2\t%lld\t%d\t%d\t%d\t%c\t%s\t%s\t%s\t%s\n",
    334 			(long long)hist->timestamp,
    335 			hist->activity,
    336 			hist->options, /* write all options - only options ANDing with HIST_LOGACCEPT are read later */
    337 			hist->from ? hist->from->self : 0, /* If from does not exist, it's probably not from us */
    338 			hist->from ? hist->from->priv : ' ',
    339 			nick, ident, host, raw);
    340 
    341 	if (ret < 0) {
    342 		ui_error("Could not write to '%s': %s", filename, strerror(errno));
    343 		fclose(f);
    344 		return -7;
    345 	}
    346 
    347 	fclose(f);
    348 	return 0;
    349 }
    350 
    351 struct History *
    352 hist_loadlog(struct HistInfo *hist, char *server, char *channel) {
    353 	struct History *head = NULL, *p, *prev;
    354 	struct stat st;
    355 	char filename[2048];
    356 	char *logdir;
    357 	FILE *f;
    358 	char *lines[HIST_MAX];
    359 	char buf[2048];
    360 	int i, j;
    361 	char *version;
    362 	char *tok[8];
    363 	char *msg;
    364 	time_t timestamp;
    365 	enum Activity activity;
    366 	enum HistOpt options;
    367 	char *prefix;
    368 	size_t len;
    369 	struct Nick *from;
    370 
    371 	assert_warn(server && hist, NULL);
    372 
    373 	if ((logdir = config_gets("log.dir")) == NULL)
    374 		return NULL;
    375 
    376 	logdir = homepath(logdir);
    377 
    378 	if (channel)
    379 		snprintf(filename, sizeof(filename), "%s/%s,%s.log", logdir, server, channel);
    380 	else
    381 		snprintf(filename, sizeof(filename), "%s/%s.log", logdir, server);
    382 
    383 	if (stat(filename, &st) == -1)
    384 		return NULL;
    385 
    386 	if (!(f = fopen(filename, "rb")))
    387 		return NULL;
    388 
    389 	memset(lines, 0, sizeof(lines));
    390 
    391 	while (fgets(buf, sizeof(buf), f)) {
    392 		pfree(&lines[HIST_MAX - 1]);
    393 		memmove(lines + 1, lines, HIST_MAX - 1);
    394 		buf[strlen(buf) - 1] = '\0'; /* strip newline */
    395 		lines[0] = estrdup(buf);
    396 	}
    397 
    398 	for (i = 0, prev = NULL; i < HIST_MAX && lines[i]; i++) {
    399 		if (*lines[i] == 'v')
    400 			version = strtok_r(lines[i], "\t", &msg) + 1; /* in future versioning could allow for back-compat */
    401 		else
    402 			version = NULL;
    403 		tok[0] = strtok_r(*lines[i] == 'v' ? NULL : lines[i], "\t", &msg);
    404 		for (j = 1; j < (sizeof(tok) / sizeof(tok[0])); j++)
    405 			tok[j] = strtok_r(NULL, "\t", &msg); /* strtok_r will store remaining text after the tokens in msg.
    406 							      * This is used instead of a tok[8] as messages can contain tabs. */
    407 
    408 		if (!tok[0] || !tok[1] || !tok[2] ||
    409 				!tok[3] || !tok[4] || !tok[5] ||
    410 				!tok[6] || !tok[7] || !msg) {
    411 			pfree(&lines[i]);
    412 			continue;
    413 		}
    414 
    415 		timestamp = (time_t)strtoll(tok[0], NULL, 10);
    416 		activity = (int)strtol(tok[1], NULL, 10);
    417 		options = HIST_RLOG|(strtol(tok[2], NULL, 10) & HIST_LOGACCEPT);
    418 
    419 		len = 1;
    420 		if (*tok[5] != ' ')
    421 			len += strlen(tok[5]);
    422 		if (*tok[6] != ' ')
    423 			len += strlen(tok[6]) + 1;
    424 		if (*tok[7] != ' ')
    425 			len += strlen(tok[7]) + 1;
    426 		prefix = smprintf(len, "%s%s%s%s%s",
    427 				tok[5] ? tok[5] : "",
    428 				tok[6] ? "!" : "", tok[6] ? tok[6] : "",
    429 				tok[7] ? "@" : "", tok[7] ? tok[7] : "");
    430 		from = nick_create(prefix, *tok[4], hist->server);
    431 		if (from)
    432 			from->self = *tok[3] == '1';
    433 
    434 		p = hist_create(hist, from, msg, activity, timestamp, options);
    435 
    436 		if (!head)
    437 			head = p;
    438 
    439 		if (prev) {
    440 			prev->next = p;
    441 			p->prev = prev;
    442 		}
    443 		prev = p;
    444 
    445 		nick_free(from);
    446 		pfree(&prefix);
    447 		pfree(&lines[i]);
    448 	}
    449 
    450 	fclose(f);
    451 
    452 	if (head) {
    453 		p = hist_format(NULL, Activity_none, HIST_SHOW|HIST_RLOG, "SELF_LOG_RESTORE %lld :log restored up to", (long long)st.st_mtime);
    454 		p->origin = hist;
    455 		p->next = head;
    456 		head->prev = p;
    457 		head = p;
    458 	}
    459 	return head;
    460 }
    461 
    462 int
    463 hist_len(struct History **history) {
    464 	struct History *p;
    465 	int i;
    466 
    467 	for (i=0, p = *history; p; p = p->next)
    468 		i++;
    469 	return i;
    470 }