summaryrefslogtreecommitdiff
path: root/replay.c
diff options
context:
space:
mode:
authorLuke Bratch <luke@bratch.co.uk>2022-11-24 00:08:34 +0000
committerLuke Bratch <luke@bratch.co.uk>2022-11-24 00:08:34 +0000
commit2e665e03b6175b3f31f0ef1e058183417df1456e (patch)
tree32674c99ac25a3df5c166db89260c60e15764c10 /replay.c
parent680d8535a87be8aa0d5ef6432d1f87561ebfcb5f (diff)
Fix replaymode = "lastspoke" by using line numbers rather than time to calculate replay start point.
Introduce new function doreplaylastspoke() to achieve this, move doreplay() into replay.c as doreplaytime() and refactor common things into sanitisereplay().
Diffstat (limited to 'replay.c')
-rw-r--r--replay.c233
1 files changed, 212 insertions, 21 deletions
diff --git a/replay.c b/replay.c
index 5dcf5e6..c63e859 100644
--- a/replay.c
+++ b/replay.c
@@ -175,9 +175,78 @@ void formattime(char *str, int replaydates) {
str[len + strlen(timestampf)] = '\0';
}
+// Sanitise a potential replay string 'str' by detecting if replay line is any of:
+// 1. TOPIC/JOIN/PART but we're not in the channel any more
+// 2. TOPIC/JOIN/PART and it was from us
+// 3. NICK from us but not our current nick
+// Returns 1 if the replay line should go through as is
+// Returns 0 if the replay line should be skipped
+// Returns -1 on error
+int sanitisereplay (char *str, struct ircdstate *ircdstate, struct channel *channels) {
+ // Check if the replay line is a TOPIC, a JOIN, or a PART so we don't
+ // replay those if we are not currently in the channel they are from
+ // otherwise clients and state go a bit mad.
+ // Never replay them if they are from us.
+
+ // Copy to a temporary string
+ char *strcopy = strdup(str);
+ // Keep track of initial pointer for free()ing later
+ char *strcopyPtr = strcopy;
+
+ // Build array of each space-separated token
+ char tokens[3][MAXDATASIZE];
+ char *token;
+
+ for (int i = 0; i < 3; i++) {
+ // Try to split
+ if ((token = strsep(&strcopy, " ")) == NULL) {
+ debugprint(DEBUG_CRIT, "sanitisereplay(): error splitting string on iteration '%d', returning!\n", i);
+ free(strcopyPtr);
+ return -1;
+ }
+ // Copy into the token array (strlen + 1 to get the NULL terminator)
+ strncpy(tokens[i], token, strlen(token) + 1);
+ }
+
+ if (strncmp(tokens[1], "TOPIC", strlen("TOPIC")) == 0 ||
+ strncmp(tokens[1], "JOIN", strlen("JOIN")) == 0 ||
+ strncmp(tokens[1], "PART", strlen("PART")) == 0) {
+ // Skip over colon if present in channel name
+ int offset = 0;
+ if (tokens[2][0] == ':') {
+ offset = 1;
+ }
+
+ // To make sure it's not us
+ extractnickfromprefix(tokens[0], 1);
+
+ // Check if we're currently in this channel or if the log line is from us
+ if (!inchannel(channels, ircdstate->maxchannelcount, tokens[2] + offset) ||
+ ((strlen(tokens[0]) == strlen(ircdstate->ircnick)) && (strncmp(tokens[0], ircdstate->ircnick, strlen(tokens[0])) == 0))) {
+ debugprint(DEBUG_FULL, "sanitisereplay(): Not sending '%s' replay line '%s'.\n", tokens[1], str);
+ free(strcopyPtr);
+ return 0;
+ }
+ }
+
+ // Separate special check for if a NICK change is from us but it isn't our current nick
+ if (strncmp(tokens[1], "NICK", strlen("NICK")) == 0) {
+ extractnickfromprefix(tokens[0], 1);
+
+ if ((strlen(tokens[0]) == strlen(ircdstate->ircnick)) && (strncmp(tokens[0], ircdstate->ircnick, strlen(tokens[0])) == 0)) {
+ debugprint(DEBUG_FULL, "sanitisereplay(): Not sending '%s' replay line '%s'.\n", tokens[1], str);
+ free(strcopyPtr);
+ return 0;
+ }
+ }
+
+ free(strcopyPtr);
+ return 1;
+}
+
// Return the number of lines in the replay log since 'seconds' seconds ago, or -1 if there a problem.
// 'basedir' is the directory in which to find 'replay.log'.
-int replaylines(int seconds, char *basedir) {
+int replaylinestime(int seconds, char *basedir) {
FILE *fp;
char str[MAXCHAR];
char filename[PATH_MAX];
@@ -287,35 +356,37 @@ int readreplayline(int seconds, int linenum, char *str, struct settings *setting
return 0;
}
-// Returns the number of seconds ago that 'nick' last spoke, or -1 if there is a problem.
+// Returns the line number in the replay log file on which 'nick' last spoke, or -1 if there is a problem.
// 'basedir' is the directory in which to find 'replay.log'.
-int lastspokesecondsago(char *nick, char *basedir) {
+long lastspokelinenumber(char *nick, char *basedir) {
FILE *fp;
- char str[MAXCHAR];
+ char line[MAXCHAR];
char filename[PATH_MAX];
// Build path
- snprintf(filename, PATH_MAX, "%s/replay.log", basedir);
-
- int lastspoketime = 0; // When 'nick' last spoke
+ if (!snprintf(filename, PATH_MAX, "%s/replay.log", basedir)) {
+ debugprint(DEBUG_CRIT, "lastspokelinenumber(): error: couldn't prepare replay path, exiting!\n");
+ exit(1);
+ }
- // Get the current time for comparison later
- int timenow = (int)time(NULL);
+ long linenumber = -1; // Line number where 'nick' last spoke
fp = fopen(filename, "r");
if (fp == NULL) {
- debugprint(DEBUG_FULL, "error: replaylineslastspoke(): could not open replay log '%s'.\n", filename);
+ debugprint(DEBUG_FULL, "error: lastspokelinenumber(): could not open replay log '%s'.\n", filename);
// Assume the file just doesn't exist yet - TODO - Interpret error codes to see what happened.
- return 0;
+ return -1;
}
- while (fgets(str, MAXCHAR, fp) != NULL) {
+ long curlinenumber = -1; // Current line number of this file reading loop
+ while (fgets(line, MAXCHAR, fp) != NULL) {
+ curlinenumber++;
// Split the line up to determine if it was a PRIVMSG sent by the requested 'nick'
// TODO - This may also be terribly inefficient
// Copy to a temporary string
- char *strcopy = strdup(str);
+ char *strcopy = strdup(line);
// Keep track of initial pointer for free()ing later
char *strcopyPtr = strcopy;
@@ -326,7 +397,7 @@ int lastspokesecondsago(char *nick, char *basedir) {
for (int i = 0; i < 3; i++) {
// Try to split
if ((token = strsep(&strcopy, " ")) == NULL) {
- debugprint(DEBUG_CRIT, "replaylineslastspoke(): error splitting string on iteration '%d', returning -1!\n", i);
+ debugprint(DEBUG_CRIT, "lastspokelinenumber(): error splitting string on iteration '%d', returning -1!\n", i);
return -1;
}
// Copy into the token array (strlen + 1 to get the NULL terminator)
@@ -337,14 +408,14 @@ int lastspokesecondsago(char *nick, char *basedir) {
// Make sure there were at least three tokens
if (counter < 3) {
- debugprint(DEBUG_CRIT, "replaylineslastspoke(): not enough tokens on line, only '%d', returning -1!\n", counter);
+ debugprint(DEBUG_CRIT, "lastspokelinenumber(): not enough tokens on line, only '%d', returning -1!\n", counter);
return -1;
}
// Make sure it started with a valid timestamp
int timestamp = gettimestamp(tokens[0]);
if (timestamp < 0) {
- debugprint(DEBUG_CRIT, "replaylineslastspoke(): line didn't start with a timestamp, returning -1!\n", counter);
+ debugprint(DEBUG_CRIT, "lastspokelinenumber(): line didn't start with a timestamp, returning -1!\n", counter);
}
// Is it a PRIVMSG?
@@ -356,16 +427,136 @@ int lastspokesecondsago(char *nick, char *basedir) {
// Was it said by our 'nick'? Disable extractnickfromprefix() debugging
// as it gets very noisy when we call it from here.
extractnickfromprefix(tokens[1], 0);
- if ((strlen(tokens[1]) == strlen(nick)) && (strncmp(tokens[1], nick, strlen(nick)))) {
- // Not our 'nick', continue
+ if ((strlen(tokens[1]) == strlen(nick)) && (strncmp(tokens[1], nick, strlen(nick)) == 0)) {
+ // Our 'nick' found, set the line number
+ linenumber = curlinenumber;
+ }
+ }
+
+ debugprint(DEBUG_FULL, "lastspokelinenumber(): last spoke on line '%ld'.\n", linenumber);
+
+ fclose(fp);
+ return linenumber;
+}
+
+// Send the requested number of seconds worth of replay log lines to the requested client.
+// 'sourcefd' is the client to send to, and 'replayseconds' is the number of
+// seconds of replay log to replay.
+// Returns 1 for success or 0 for failure.
+int doreplaytime(int sourcefd, int replayseconds, struct client *clients, struct settings *settings, struct ircdstate *ircdstate, struct channel *channels) {
+ char outgoingmsg[MAXDATASIZE];
+
+ // Figure out how many lines to replay
+ int numlines = replaylinestime(replayseconds, settings->basedir);
+ debugprint(DEBUG_FULL, "doreplaytime(): Replay log lines: '%d'.\n", numlines);
+
+ if (numlines < 0) {
+ debugprint(DEBUG_CRIT, "doreplaytime(): Error getting number of replay lines.\n");
+ return 0;
+ } else if (numlines == 0) {
+ snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :0 replay log lines found in the time requested, nothing to send.", ircdstate->ircnick);
+ sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
+ return 1;
+ }
+
+ // Announce the start
+ snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Starting log replay....", ircdstate->ircnick);
+ sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
+
+ // Replay those lines!
+ for (int i = 0; i < numlines; i++) {
+ int ret = readreplayline(replayseconds, i, outgoingmsg, settings, ircdstate);
+ if (ret == 0) {
+ debugprint(DEBUG_CRIT, "doreplaytime(): Error requesting replay line.\n");
+ return 0;
+ } else if (ret == -1) {
+ debugprint(DEBUG_FULL, "doreplaytime(): readreplayline() said to ignore replay line.\n");
continue;
}
- lastspoketime = timestamp;
+ if (sanitisereplay(outgoingmsg, ircdstate, channels) < 1) {
+ continue;
+ }
+
+ debugprint(DEBUG_FULL, "doreplaytime(): Sending replay line: '%s'.\n", outgoingmsg);
+ sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
}
- fclose(fp);
- return timenow - lastspoketime;
+ // Announce the end
+ snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Log replay complete.", ircdstate->ircnick);
+ sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
+
+ return 1;
+}
+
+// Send replay log lines from line number 'linenumber' onwards, to client 'sourcefd'.
+// Returns 1 for success or 0 for failure.
+int doreplaylastspoke(int sourcefd, long linenumber, struct client *clients, struct settings *settings, struct ircdstate *ircdstate, struct channel *channels) {
+ debugprint(DEBUG_FULL, "doreplaylastspoke(): replaying from linenumber '%ld' to sourcefd '%d'.\n", linenumber, sourcefd);
+
+ FILE *fp;
+ char line[MAXCHAR];
+ char filename[PATH_MAX];
+ char outgoingmsg[MAXDATASIZE];
+
+ // Build path
+ if (!snprintf(filename, PATH_MAX, "%s/replay.log", settings->basedir)) {
+ debugprint(DEBUG_CRIT, "doreplaylastspoke(): error: couldn't prepare replay path, exiting!\n");
+ exit(1);
+ }
+
+ fp = fopen(filename, "r");
+
+ if (fp == NULL) {
+ debugprint(DEBUG_FULL, "error: doreplaylastspoke(): could not open replay log '%s'.\n", filename);
+ // Assume the file just doesn't exist yet - TODO - Interpret error codes to see what happened.
+ return 0;
+ }
+
+ // Announce the start
+ snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Starting log replay....", ircdstate->ircnick);
+ sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
+
+ long curlinenumber = -1; // Current line number of this file reading loop
+ while (fgets(line, MAXCHAR, fp) != NULL) {
+ curlinenumber++;
+ // Skip through the file until line number 'linenumber'...
+ if (curlinenumber < linenumber) {
+ continue;
+ }
+ // ...carry on once we've reached 'linenumber'
+
+ // Read the timestamp from the line
+ int timestamp = gettimestamp(line);
+
+ // Make sure and it wasn't before blabouncer launched...
+ if (timestamp < ircdstate->launchtime) {
+ // Don't replay if this replay line happened before blabouncer launched,
+ // to avoid weird synchronisation issues with uncertain events from before
+ // we launched.
+ debugprint(DEBUG_FULL, "doreplaylastspoke(): Ignoring line '%s' from before we launched.\n", line);
+ continue;
+ }
+
+ // Insert our formatted [HH:MM:SS] timestamp into the message
+ formattime(line, settings->replaydates);
+
+ strncpy(outgoingmsg, line, strlen(line));
+ outgoingmsg[strlen(line)] = '\0';
+
+ if (sanitisereplay(outgoingmsg, ircdstate, channels) < 1) {
+ continue;
+ }
+
+ debugprint(DEBUG_FULL, "doreplaylastspoke(): Sending replay line: '%s'.\n", outgoingmsg);
+ sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
+ }
+
+ // Announce the end
+ snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Log replay complete.", ircdstate->ircnick);
+ sendtoclient(sourcefd, outgoingmsg, clients, settings, 0);
+
+ return 1;
}
// Write the line 'str' to the replay log file after prepending it with