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 {
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)

20
irc.go
View file

@ -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{}
}
}

View file

@ -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

View file

@ -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
}