From 2e665e03b6175b3f31f0ef1e058183417df1456e Mon Sep 17 00:00:00 2001 From: Luke Bratch Date: Thu, 24 Nov 2022 00:08:34 +0000 Subject: 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(). --- TODO | 3 + functions.c | 132 ++++------------------------------ functions.h | 6 -- message.c | 4 +- replay.c | 233 ++++++++++++++++++++++++++++++++++++++++++++++++++++++------ replay.h | 18 ++++- 6 files changed, 246 insertions(+), 150 deletions(-) diff --git a/TODO b/TODO index a5f65f0..e10a4e9 100644 --- a/TODO +++ b/TODO @@ -10,6 +10,7 @@ macOS compiler may need limits.h included in structures.h. Sometimes replaymode = "lastspoke" will replay the last message you sent if you spoke last and sometimes it doesn't - change to always include your last message? Time based issue due to "seconds ago" rather than specific timestamp? Perhaps extract timestamp and replay from there. +(Pending real world testing to be marked as fixed.) Can memory usage be reduced further? (e.g. better channel struct management) @@ -40,4 +41,6 @@ Git version in code. Sending ^D when running with ./blabouncer -f does crazy stuff. +Connecting from irssi triggers doautoreplay() from "USER received" in message.c twice. + Crash when requesting 30 hour replay. diff --git a/functions.c b/functions.c index 917f688..6d85d8e 100644 --- a/functions.c +++ b/functions.c @@ -873,110 +873,6 @@ int channelindex(struct channel *channels, int maxchannelcount, char *name) { return -1; } -// 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 doreplay(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 = replaylines(replayseconds, settings->basedir); - debugprint(DEBUG_FULL, "Replay log lines: '%d'.\n", numlines); - - if (numlines < 0) { - debugprint(DEBUG_CRIT, "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, "Error requesting replay line.\n"); - return 0; - } else if (ret == -1) { - debugprint(DEBUG_FULL, "doreplay(): readreplayline() said to ignore replay line.\n"); - continue; - } - - // 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(outgoingmsg); - // 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 j = 0; j < 3; j++) { - // Try to split - if ((token = strsep(&strcopy, " ")) == NULL) { - debugprint(DEBUG_CRIT, "doreplay(): error splitting string on iteration '%d', returning!\n", j); - return 0; - } - // Copy into the token array (strlen + 1 to get the NULL terminator) - strncpy(tokens[j], 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, "Not sending '%s' replay line '%s'.\n", tokens[1], outgoingmsg); - free(strcopyPtr); - continue; - } - } - - // 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, "Not sending '%s' replay line '%s'.\n", tokens[1], outgoingmsg); - free(strcopyPtr); - continue; - } - } - - free(strcopyPtr); - - debugprint(DEBUG_FULL, "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; -} - // Send the auto replay to the requested client, where 'sourcefd' is the client // to send to. The type of replay will depend on the user's settings. // Returns 1 for success or 0 for failure. @@ -989,9 +885,9 @@ int doautoreplay(int sourcefd, struct client *clients, struct settings *settings // If replaymode = "time" then send whatever replayseconds is set to if (!strncmp(settings->replaymode, "time", strlen("time"))) { - debugprint(DEBUG_FULL, "doautoreplay(): replaymode = \"time\", calling doreplay() with '%d' seconds.\n", settings->replayseconds); - if (!doreplay(sourcefd, settings->replayseconds, clients, settings, ircdstate, channels)) { - debugprint(DEBUG_SOME, "doautoreplay(): doreplay() returned 0, returning 0 to caller...\n"); + debugprint(DEBUG_FULL, "doautoreplay(): replaymode = \"time\", calling doreplaytime() with '%d' seconds.\n", settings->replayseconds); + if (!doreplaytime(sourcefd, settings->replayseconds, clients, settings, ircdstate, channels)) { + debugprint(DEBUG_SOME, "doautoreplay(): doreplaytime() returned 0, returning 0 to caller...\n"); return 0; } return 1; @@ -999,15 +895,15 @@ int doautoreplay(int sourcefd, struct client *clients, struct settings *settings // If replaymode = "lastspoke" then send whatever happened since the user's current nick last spoke if (!strncmp(settings->replaymode, "lastspoke", strlen("lastspoke"))) { - debugprint(DEBUG_FULL, "doautoreplay(): replaymode = \"lastspoke\".\n", settings->replayseconds); - int secondsago = lastspokesecondsago(ircdstate->ircnick, settings->basedir); - if (secondsago < 1) { - debugprint(DEBUG_SOME, "doautoreplay(): lastspokesecondsago() returned < 1, returning 0 to caller...\n"); + debugprint(DEBUG_FULL, "doautoreplay(): replaymode = \"lastspoke\", calling doreplaylastspoke().\n"); + long linenumber = lastspokelinenumber(ircdstate->ircnick, settings->basedir); + if (linenumber < 1) { + debugprint(DEBUG_SOME, "doautoreplay(): lastspokelinenumber() returned < 1, returning 0 to caller...\n"); return 0; } - debugprint(DEBUG_FULL, "doautoreplay(): replaymode = \"lastspoke\", sending lines from '%d' seconds ago to sourcefd '%d'.\n", secondsago, sourcefd); - if (!doreplay(sourcefd, secondsago, clients, settings, ircdstate, channels)) { - debugprint(DEBUG_SOME, "doautoreplay(): doreplay() returned 0, returning 0 to caller...\n"); + debugprint(DEBUG_FULL, "doautoreplay(): replaymode = \"lastspoke\", replaying from line '%ld' to sourcefd '%d'.\n", linenumber, sourcefd); + if (!doreplaylastspoke(sourcefd, linenumber, clients, settings, ircdstate, channels)) { + debugprint(DEBUG_SOME, "doautoreplay(): doreplaylastspoke() returned 0, returning 0 to caller...\n"); // CORRECT? return 0; } return 1; @@ -1016,8 +912,8 @@ int doautoreplay(int sourcefd, struct client *clients, struct settings *settings // If replaymode = "noclients" and there is currently only one client connected, then send whatever happened since there were last no clients connected if (!strncmp(settings->replaymode, "noclients", strlen("noclients")) && numclients(clients) == 1) { debugprint(DEBUG_FULL, "doautoreplay(): replaymode = \"noclients\", sending '%d' seconds of replay.\n", time(NULL) - ircdstate->clientsnonetime); - if (!doreplay(sourcefd, time(NULL) - ircdstate->clientsnonetime, clients, settings, ircdstate, channels)) { - debugprint(DEBUG_SOME, "doautoreplay(): doreplay() returned 0, returning 0 to caller...\n"); + if (!doreplaytime(sourcefd, time(NULL) - ircdstate->clientsnonetime, clients, settings, ircdstate, channels)) { + debugprint(DEBUG_SOME, "doautoreplay(): doreplaytime() returned 0, returning 0 to caller...\n"); return 0; } return 1; @@ -1026,8 +922,8 @@ int doautoreplay(int sourcefd, struct client *clients, struct settings *settings // If replaymode = "lastchange" then send whatever happened since the last client registration or disconnection if (!strncmp(settings->replaymode, "lastchange", strlen("lastchange"))) { debugprint(DEBUG_FULL, "doautoreplay(): replaymode = \"lastchange\", sending '%d' seconds of replay.\n", time(NULL) - ircdstate->clientchangetime); - if (!doreplay(sourcefd, time(NULL) - ircdstate->clientchangetime, clients, settings, ircdstate, channels)) { - debugprint(DEBUG_SOME, "doautoreplay(): doreplay() returned 0, returning 0 to caller...\n"); + if (!doreplaytime(sourcefd, time(NULL) - ircdstate->clientchangetime, clients, settings, ircdstate, channels)) { + debugprint(DEBUG_SOME, "doautoreplay(): doreplaytime() returned 0, returning 0 to caller...\n"); return 0; } return 1; diff --git a/functions.h b/functions.h index c22d7ac..28b6413 100644 --- a/functions.h +++ b/functions.h @@ -142,12 +142,6 @@ int inchannel(struct channel *channels, int maxchannelcount, char *name); // Returns -1 if there was an error. int channelindex(struct channel *channels, int maxchannelcount, char *name); -// Send the requested number of lines of replay log 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 doreplay(int sourcefd, int replayseconds, struct client *clients, struct settings *settings, struct ircdstate *ircdstate, struct channel *channels); - // Send the auto replay to the requested client, where 'sourcefd' is the client // to send to. The type of replay will depend on the user's settings. // Returns 1 for success or 0 for failure. diff --git a/message.c b/message.c index 9c07a83..c205709 100644 --- a/message.c +++ b/message.c @@ -1370,7 +1370,7 @@ int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int debugprint(DEBUG_FULL, "Replaying '%s' which is '%d' seconds.\n", tokens[2], replayseconds); - if (!doreplay(sourcefd, replayseconds, clients, settings, ircdstate, channels)) { + if (!doreplaytime(sourcefd, replayseconds, clients, settings, ircdstate, channels)) { snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Unable to read replay log file!", ircdstate->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); } @@ -1448,7 +1448,7 @@ int processclientmessage(SSL *server_ssl, char *str, struct client *clients, int sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); return 1; } - if (!doreplay(sourcefd, time(NULL) - codetime, clients, settings, ircdstate, channels)) { + if (!doreplaytime(sourcefd, time(NULL) - codetime, clients, settings, ircdstate, channels)) { snprintf(outgoingmsg, MAXDATASIZE, "NOTICE %s :Unable to read replay log file!", ircdstate->ircnick); sendtoclient(sourcefd, outgoingmsg, clients, settings, 0); return 1; 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 diff --git a/replay.h b/replay.h index 039c131..dcf6771 100644 --- a/replay.h +++ b/replay.h @@ -38,13 +38,25 @@ #define TIMELENF 11 // [HH:MM:SS] = 10 characters + 1 for null char #define DATETIMELEN 50 // Should be enough for a full datetime string // TODO - Is it!? I think it will only ever be as long as [DD/MM/YY HH:MM:SS] (20 including null) -int replaylines(int seconds, char *basedir); +// 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 replaylinestime(int seconds, char *basedir); int readreplayline(int seconds, int linenum, char *str, struct settings *settings, struct ircdstate *ircdstate); -// 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); + +// 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); + +// 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); int writereplayline(char *str, char *basedir); -- cgit v1.2.3