From b078ccaf7a1da7d76a6fa3c3f626dbcb2c3d487e Mon Sep 17 00:00:00 2001 From: Hubert Hirtz Date: Sat, 27 Mar 2021 13:08:31 +0100 Subject: [PATCH] Implement CHATHISTORY BETWEEN --- downstream.go | 54 +++++++++++++++++++++++++++++++------------------- irc.go | 20 +++++++++++++++++++ msgstore.go | 10 ++++++++-- msgstore_fs.go | 31 +++++++++++++++-------------- 4 files changed, 78 insertions(+), 37 deletions(-) diff --git a/downstream.go b/downstream.go index 95b67ac..e27b4a9 100644 --- a/downstream.go +++ b/downstream.go @@ -1801,11 +1801,22 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error { if err := parseMessageParams(msg, &subcommand); err != nil { return err } - var target, criteria, limitStr string - if err := parseMessageParams(msg, nil, &target, &criteria, &limitStr); err != nil { + var target, limitStr string + var boundsStr [2]string + switch subcommand { + case "AFTER", "BEFORE": + if err := parseMessageParams(msg, nil, &target, &boundsStr[0], &limitStr); err != nil { + return err + } + case "BETWEEN": + if err := parseMessageParams(msg, nil, &target, &boundsStr[0], &boundsStr[1], &limitStr); err != nil { + return err + } + default: + // TODO: support LATEST, AROUND return ircError{&irc.Message{ Command: "FAIL", - Params: []string{"CHATHISTORY", "NEED_MORE_PARAMS", subcommand, "Missing parameters"}, + Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, "Unknown command"}, }} } @@ -1824,20 +1835,23 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error { entity = uc.network.casemap(entity) // TODO: support msgid criteria - criteriaParts := strings.SplitN(criteria, "=", 2) - if len(criteriaParts) != 2 || criteriaParts[0] != "timestamp" { + var bounds [2]time.Time + bounds[0] = parseChatHistoryBound(boundsStr[0]) + if bounds[0].IsZero() { return ircError{&irc.Message{ Command: "FAIL", - Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, criteria, "Unknown criteria"}, + Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, boundsStr[0], "Invalid first bound"}, }} } - timestamp, err := time.Parse(serverTimeLayout, criteriaParts[1]) - if err != nil { - return ircError{&irc.Message{ - Command: "FAIL", - Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, criteria, "Invalid criteria"}, - }} + if boundsStr[1] != "" { + bounds[1] = parseChatHistoryBound(boundsStr[1]) + if bounds[1].IsZero() { + return ircError{&irc.Message{ + Command: "FAIL", + Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, boundsStr[1], "Invalid second bound"}, + }} + } } limit, err := strconv.Atoi(limitStr) @@ -1851,15 +1865,15 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error { var history []*irc.Message switch subcommand { case "BEFORE": - history, err = store.LoadBeforeTime(uc.network, entity, timestamp, limit) + history, err = store.LoadBeforeTime(uc.network, entity, bounds[0], time.Time{}, limit) case "AFTER": - history, err = store.LoadAfterTime(uc.network, entity, timestamp, limit) - default: - // TODO: support LATEST, BETWEEN - return ircError{&irc.Message{ - Command: "FAIL", - Params: []string{"CHATHISTORY", "UNKNOWN_COMMAND", subcommand, "Unknown command"}, - }} + history, err = store.LoadAfterTime(uc.network, entity, bounds[0], time.Now(), limit) + case "BETWEEN": + if bounds[0].Before(bounds[1]) { + history, err = store.LoadAfterTime(uc.network, entity, bounds[0], bounds[1], limit) + } else { + history, err = store.LoadBeforeTime(uc.network, entity, bounds[0], bounds[1], limit) + } } if err != nil { dc.logger.Printf("failed fetching %q messages for chathistory: %v", target, err) diff --git a/irc.go b/irc.go index 0c7cb2e..e78d48f 100644 --- a/irc.go +++ b/irc.go @@ -4,6 +4,7 @@ import ( "fmt" "sort" "strings" + "time" "unicode" "unicode/utf8" @@ -637,3 +638,22 @@ func isHighlight(text, nick string) bool { text = text[i+len(nick):] } } + +// parseChatHistoryBound parses the given CHATHISTORY parameter as a bound. +// The zero time is returned on error. +func parseChatHistoryBound(param string) time.Time { + parts := strings.SplitN(param, "=", 2) + if len(parts) != 2 { + return time.Time{} + } + switch parts[0] { + case "timestamp": + timestamp, err := time.Parse(serverTimeLayout, parts[1]) + if err != nil { + return time.Time{} + } + return timestamp + default: + return time.Time{} + } +} diff --git a/msgstore.go b/msgstore.go index 36541cc..8e83f0a 100644 --- a/msgstore.go +++ b/msgstore.go @@ -26,8 +26,14 @@ type messageStore interface { type chatHistoryMessageStore interface { messageStore - LoadBeforeTime(network *network, entity string, t time.Time, limit int) ([]*irc.Message, error) - LoadAfterTime(network *network, entity string, t time.Time, limit int) ([]*irc.Message, error) + // LoadBeforeTime loads up to limit messages before start down to end. The + // returned messages must be between and excluding the provided bounds. + // end is before start. + LoadBeforeTime(network *network, entity string, start, end time.Time, limit int) ([]*irc.Message, error) + // LoadBeforeTime loads up to limit messages after start up to end. The + // returned messages must be between and excluding the provided bounds. + // end is after start. + LoadAfterTime(network *network, entity string, start, end time.Time, limit int) ([]*irc.Message, error) } type msgIDType uint diff --git a/msgstore_fs.go b/msgstore_fs.go index 9a25c0a..abd888b 100644 --- a/msgstore_fs.go +++ b/msgstore_fs.go @@ -257,7 +257,7 @@ func parseMessage(line, entity string, ref time.Time) (*irc.Message, time.Time, return msg, t, nil } -func (ms *fsMessageStore) parseMessagesBefore(network *network, entity string, ref time.Time, limit int, afterOffset int64) ([]*irc.Message, error) { +func (ms *fsMessageStore) parseMessagesBefore(network *network, entity string, ref time.Time, end time.Time, limit int, afterOffset int64) ([]*irc.Message, error) { path := ms.logPath(network, entity, ref) f, err := os.Open(path) if err != nil { @@ -284,7 +284,7 @@ func (ms *fsMessageStore) parseMessagesBefore(network *network, entity string, r msg, t, err := parseMessage(sc.Text(), entity, ref) if err != nil { return nil, err - } else if msg == nil { + } else if msg == nil || !t.After(end) { continue } else if !t.Before(ref) { break @@ -313,7 +313,7 @@ func (ms *fsMessageStore) parseMessagesBefore(network *network, entity string, r } } -func (ms *fsMessageStore) parseMessagesAfter(network *network, entity string, ref time.Time, limit int) ([]*irc.Message, error) { +func (ms *fsMessageStore) parseMessagesAfter(network *network, entity string, ref time.Time, end time.Time, limit int) ([]*irc.Message, error) { path := ms.logPath(network, entity, ref) f, err := os.Open(path) if err != nil { @@ -332,6 +332,8 @@ func (ms *fsMessageStore) parseMessagesAfter(network *network, entity string, re return nil, err } else if msg == nil || !t.After(ref) { continue + } else if !t.Before(end) { + break } history = append(history, msg) @@ -343,12 +345,12 @@ func (ms *fsMessageStore) parseMessagesAfter(network *network, entity string, re return history, nil } -func (ms *fsMessageStore) LoadBeforeTime(network *network, entity string, t time.Time, limit int) ([]*irc.Message, error) { +func (ms *fsMessageStore) LoadBeforeTime(network *network, entity string, start time.Time, end time.Time, limit int) ([]*irc.Message, error) { history := make([]*irc.Message, limit) remaining := limit tries := 0 - for remaining > 0 && tries < fsMessageStoreMaxTries { - buf, err := ms.parseMessagesBefore(network, entity, t, remaining, -1) + for remaining > 0 && tries < fsMessageStoreMaxTries && end.Before(start) { + buf, err := ms.parseMessagesBefore(network, entity, start, end, remaining, -1) if err != nil { return nil, err } @@ -359,20 +361,19 @@ func (ms *fsMessageStore) LoadBeforeTime(network *network, entity string, t time } copy(history[remaining-len(buf):], buf) remaining -= len(buf) - year, month, day := t.Date() - t = time.Date(year, month, day, 0, 0, 0, 0, t.Location()).Add(-1) + year, month, day := start.Date() + start = time.Date(year, month, day, 0, 0, 0, 0, start.Location()).Add(-1) } return history[remaining:], nil } -func (ms *fsMessageStore) LoadAfterTime(network *network, entity string, t time.Time, limit int) ([]*irc.Message, error) { +func (ms *fsMessageStore) LoadAfterTime(network *network, entity string, start time.Time, end time.Time, limit int) ([]*irc.Message, error) { var history []*irc.Message remaining := limit tries := 0 - now := time.Now() - for remaining > 0 && tries < fsMessageStoreMaxTries && t.Before(now) { - buf, err := ms.parseMessagesAfter(network, entity, t, remaining) + for remaining > 0 && tries < fsMessageStoreMaxTries && start.Before(end) { + buf, err := ms.parseMessagesAfter(network, entity, start, end, remaining) if err != nil { return nil, err } @@ -383,8 +384,8 @@ func (ms *fsMessageStore) LoadAfterTime(network *network, entity string, t time. } history = append(history, buf...) remaining -= len(buf) - year, month, day := t.Date() - t = time.Date(year, month, day+1, 0, 0, 0, 0, t.Location()) + year, month, day := start.Date() + start = time.Date(year, month, day+1, 0, 0, 0, 0, start.Location()) } return history, nil } @@ -420,7 +421,7 @@ func (ms *fsMessageStore) LoadLatestID(network *network, entity, id string, limi offset = afterOffset } - buf, err := ms.parseMessagesBefore(network, entity, t, remaining, offset) + buf, err := ms.parseMessagesBefore(network, entity, t, time.Time{}, remaining, offset) if err != nil { return nil, err }