soju/msgstore.go
Simon Ser 2b4f0a870f msgstore: take Network as arg instead of network
The message stores don't need to access the internal network
struct, they just need network metadata such as ID and name.

This can ease moving message stores into a separate package in the
future.
2021-11-03 16:37:01 +01:00

123 lines
3.7 KiB
Go

package soju
import (
"bytes"
"encoding/base64"
"fmt"
"time"
"git.sr.ht/~sircmpwn/go-bare"
"gopkg.in/irc.v3"
)
// messageStore is a per-user store for IRC messages.
type messageStore interface {
Close() error
// LastMsgID queries the last message ID for the given network, entity and
// date. The message ID returned may not refer to a valid message, but can be
// used in history queries.
LastMsgID(network *Network, entity string, t time.Time) (string, error)
// LoadLatestID queries the latest non-event messages for the given network,
// entity and date, up to a count of limit messages, sorted from oldest to newest.
LoadLatestID(network *Network, entity, id string, limit int) ([]*irc.Message, error)
Append(network *Network, entity string, msg *irc.Message) (id string, err error)
}
type chatHistoryTarget struct {
Name string
LatestMessage time.Time
}
// chatHistoryMessageStore is a message store that supports chat history
// operations.
type chatHistoryMessageStore interface {
messageStore
// ListTargets lists channels and nicknames by time of the latest message.
// It returns up to limit targets, starting from start and ending on end,
// both excluded. end may be before or after start.
// If events is false, only PRIVMSG/NOTICE messages are considered.
ListTargets(network *Network, start, end time.Time, limit int, events bool) ([]chatHistoryTarget, 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.
// If events is false, only PRIVMSG/NOTICE messages are considered.
LoadBeforeTime(network *Network, entity string, start, end time.Time, limit int, events bool) ([]*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.
// If events is false, only PRIVMSG/NOTICE messages are considered.
LoadAfterTime(network *Network, entity string, start, end time.Time, limit int, events bool) ([]*irc.Message, error)
}
type msgIDType uint
const (
msgIDNone msgIDType = iota
msgIDMemory
msgIDFS
)
const msgIDVersion uint = 0
type msgIDHeader struct {
Version uint
Network bare.Int
Target string
Type msgIDType
}
type msgIDBody interface {
msgIDType() msgIDType
}
func formatMsgID(netID int64, target string, body msgIDBody) string {
var buf bytes.Buffer
w := bare.NewWriter(&buf)
header := msgIDHeader{
Version: msgIDVersion,
Network: bare.Int(netID),
Target: target,
Type: body.msgIDType(),
}
if err := bare.MarshalWriter(w, &header); err != nil {
panic(err)
}
if err := bare.MarshalWriter(w, body); err != nil {
panic(err)
}
return base64.RawURLEncoding.EncodeToString(buf.Bytes())
}
func parseMsgID(s string, body msgIDBody) (netID int64, target string, err error) {
b, err := base64.RawURLEncoding.DecodeString(s)
if err != nil {
return 0, "", fmt.Errorf("invalid internal message ID: %v", err)
}
r := bare.NewReader(bytes.NewReader(b))
var header msgIDHeader
if err := bare.UnmarshalBareReader(r, &header); err != nil {
return 0, "", fmt.Errorf("invalid internal message ID: %v", err)
}
if header.Version != msgIDVersion {
return 0, "", fmt.Errorf("invalid internal message ID: got version %v, want %v", header.Version, msgIDVersion)
}
if body != nil {
typ := body.msgIDType()
if header.Type != typ {
return 0, "", fmt.Errorf("invalid internal message ID: got type %v, want %v", header.Type, typ)
}
if err := bare.UnmarshalBareReader(r, body); err != nil {
return 0, "", fmt.Errorf("invalid internal message ID: %v", err)
}
}
return int64(header.Network), header.Target, nil
}