Implement CHATHISTORY BETWEEN

This commit is contained in:
Hubert Hirtz 2021-03-27 13:08:31 +01:00 committed by Simon Ser
parent bede274f32
commit b078ccaf7a
4 changed files with 78 additions and 37 deletions

View file

@ -1801,11 +1801,22 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
if err := parseMessageParams(msg, &subcommand); err != nil { if err := parseMessageParams(msg, &subcommand); err != nil {
return err return err
} }
var target, criteria, limitStr string var target, limitStr string
if err := parseMessageParams(msg, nil, &target, &criteria, &limitStr); err != nil { 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{ return ircError{&irc.Message{
Command: "FAIL", Command: "FAIL",
Params: []string{"CHATHISTORY", "NEED_MORE_PARAMS", subcommand, "Missing parameters"}, Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, "Unknown command"},
}} }}
} }
@ -1824,21 +1835,24 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
entity = uc.network.casemap(entity) entity = uc.network.casemap(entity)
// TODO: support msgid criteria // TODO: support msgid criteria
criteriaParts := strings.SplitN(criteria, "=", 2) var bounds [2]time.Time
if len(criteriaParts) != 2 || criteriaParts[0] != "timestamp" { bounds[0] = parseChatHistoryBound(boundsStr[0])
if bounds[0].IsZero() {
return ircError{&irc.Message{ return ircError{&irc.Message{
Command: "FAIL", 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 boundsStr[1] != "" {
if err != nil { bounds[1] = parseChatHistoryBound(boundsStr[1])
if bounds[1].IsZero() {
return ircError{&irc.Message{ return ircError{&irc.Message{
Command: "FAIL", Command: "FAIL",
Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, criteria, "Invalid criteria"}, Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, boundsStr[1], "Invalid second bound"},
}} }}
} }
}
limit, err := strconv.Atoi(limitStr) limit, err := strconv.Atoi(limitStr)
if err != nil || limit < 0 || limit > dc.srv.HistoryLimit { if err != nil || limit < 0 || limit > dc.srv.HistoryLimit {
@ -1851,15 +1865,15 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
var history []*irc.Message var history []*irc.Message
switch subcommand { switch subcommand {
case "BEFORE": 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": case "AFTER":
history, err = store.LoadAfterTime(uc.network, entity, timestamp, limit) history, err = store.LoadAfterTime(uc.network, entity, bounds[0], time.Now(), limit)
default: case "BETWEEN":
// TODO: support LATEST, BETWEEN if bounds[0].Before(bounds[1]) {
return ircError{&irc.Message{ history, err = store.LoadAfterTime(uc.network, entity, bounds[0], bounds[1], limit)
Command: "FAIL", } else {
Params: []string{"CHATHISTORY", "UNKNOWN_COMMAND", subcommand, "Unknown command"}, history, err = store.LoadBeforeTime(uc.network, entity, bounds[0], bounds[1], limit)
}} }
} }
if err != nil { if err != nil {
dc.logger.Printf("failed fetching %q messages for chathistory: %v", target, err) dc.logger.Printf("failed fetching %q messages for chathistory: %v", target, err)

20
irc.go
View file

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"sort" "sort"
"strings" "strings"
"time"
"unicode" "unicode"
"unicode/utf8" "unicode/utf8"
@ -637,3 +638,22 @@ func isHighlight(text, nick string) bool {
text = text[i+len(nick):] 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{}
}
}

View file

@ -26,8 +26,14 @@ type messageStore interface {
type chatHistoryMessageStore interface { type chatHistoryMessageStore interface {
messageStore messageStore
LoadBeforeTime(network *network, entity string, t time.Time, limit int) ([]*irc.Message, error) // LoadBeforeTime loads up to limit messages before start down to end. The
LoadAfterTime(network *network, entity string, t time.Time, limit int) ([]*irc.Message, error) // 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 type msgIDType uint

View file

@ -257,7 +257,7 @@ func parseMessage(line, entity string, ref time.Time) (*irc.Message, time.Time,
return msg, t, nil 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) path := ms.logPath(network, entity, ref)
f, err := os.Open(path) f, err := os.Open(path)
if err != nil { if err != nil {
@ -284,7 +284,7 @@ func (ms *fsMessageStore) parseMessagesBefore(network *network, entity string, r
msg, t, err := parseMessage(sc.Text(), entity, ref) msg, t, err := parseMessage(sc.Text(), entity, ref)
if err != nil { if err != nil {
return nil, err return nil, err
} else if msg == nil { } else if msg == nil || !t.After(end) {
continue continue
} else if !t.Before(ref) { } else if !t.Before(ref) {
break 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) path := ms.logPath(network, entity, ref)
f, err := os.Open(path) f, err := os.Open(path)
if err != nil { if err != nil {
@ -332,6 +332,8 @@ func (ms *fsMessageStore) parseMessagesAfter(network *network, entity string, re
return nil, err return nil, err
} else if msg == nil || !t.After(ref) { } else if msg == nil || !t.After(ref) {
continue continue
} else if !t.Before(end) {
break
} }
history = append(history, msg) history = append(history, msg)
@ -343,12 +345,12 @@ func (ms *fsMessageStore) parseMessagesAfter(network *network, entity string, re
return history, nil 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) history := make([]*irc.Message, limit)
remaining := limit remaining := limit
tries := 0 tries := 0
for remaining > 0 && tries < fsMessageStoreMaxTries { for remaining > 0 && tries < fsMessageStoreMaxTries && end.Before(start) {
buf, err := ms.parseMessagesBefore(network, entity, t, remaining, -1) buf, err := ms.parseMessagesBefore(network, entity, start, end, remaining, -1)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -359,20 +361,19 @@ func (ms *fsMessageStore) LoadBeforeTime(network *network, entity string, t time
} }
copy(history[remaining-len(buf):], buf) copy(history[remaining-len(buf):], buf)
remaining -= len(buf) remaining -= len(buf)
year, month, day := t.Date() year, month, day := start.Date()
t = time.Date(year, month, day, 0, 0, 0, 0, t.Location()).Add(-1) start = time.Date(year, month, day, 0, 0, 0, 0, start.Location()).Add(-1)
} }
return history[remaining:], nil 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 var history []*irc.Message
remaining := limit remaining := limit
tries := 0 tries := 0
now := time.Now() for remaining > 0 && tries < fsMessageStoreMaxTries && start.Before(end) {
for remaining > 0 && tries < fsMessageStoreMaxTries && t.Before(now) { buf, err := ms.parseMessagesAfter(network, entity, start, end, remaining)
buf, err := ms.parseMessagesAfter(network, entity, t, remaining)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -383,8 +384,8 @@ func (ms *fsMessageStore) LoadAfterTime(network *network, entity string, t time.
} }
history = append(history, buf...) history = append(history, buf...)
remaining -= len(buf) remaining -= len(buf)
year, month, day := t.Date() year, month, day := start.Date()
t = time.Date(year, month, day+1, 0, 0, 0, 0, t.Location()) start = time.Date(year, month, day+1, 0, 0, 0, 0, start.Location())
} }
return history, nil return history, nil
} }
@ -420,7 +421,7 @@ func (ms *fsMessageStore) LoadLatestID(network *network, entity, id string, limi
offset = afterOffset 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 { if err != nil {
return nil, err return nil, err
} }