source: code/trunk/msgstore.go@ 654

Last change on this file since 654 was 549, checked in by contact, 4 years ago

Implement CHATHISTORY TARGETS

References: https://github.com/ircv3/ircv3-specifications/pull/450

File size: 3.3 KB
Line 
1package soju
2
3import (
4 "bytes"
5 "encoding/base64"
6 "fmt"
7 "time"
8
9 "git.sr.ht/~sircmpwn/go-bare"
10 "gopkg.in/irc.v3"
11)
12
13// messageStore is a per-user store for IRC messages.
14type messageStore interface {
15 Close() error
16 // LastMsgID queries the last message ID for the given network, entity and
17 // date. The message ID returned may not refer to a valid message, but can be
18 // used in history queries.
19 LastMsgID(network *network, entity string, t time.Time) (string, error)
20 LoadLatestID(network *network, entity, id string, limit int) ([]*irc.Message, error)
21 Append(network *network, entity string, msg *irc.Message) (id string, err error)
22}
23
24type chatHistoryTarget struct {
25 Name string
26 LatestMessage time.Time
27}
28
29// chatHistoryMessageStore is a message store that supports chat history
30// operations.
31type chatHistoryMessageStore interface {
32 messageStore
33
34 // ListTargets lists channels and nicknames by time of the latest message.
35 // It returns up to limit targets, starting from start and ending on end,
36 // both excluded. end may be before or after start.
37 ListTargets(network *network, start, end time.Time, limit int) ([]chatHistoryTarget, error)
38 // LoadBeforeTime loads up to limit messages before start down to end. The
39 // returned messages must be between and excluding the provided bounds.
40 // end is before start.
41 LoadBeforeTime(network *network, entity string, start, end time.Time, limit int) ([]*irc.Message, error)
42 // LoadBeforeTime loads up to limit messages after start up to end. The
43 // returned messages must be between and excluding the provided bounds.
44 // end is after start.
45 LoadAfterTime(network *network, entity string, start, end time.Time, limit int) ([]*irc.Message, error)
46}
47
48type msgIDType uint
49
50const (
51 msgIDNone msgIDType = iota
52 msgIDMemory
53 msgIDFS
54)
55
56const msgIDVersion uint = 0
57
58type msgIDHeader struct {
59 Version uint
60 Network bare.Int
61 Target string
62 Type msgIDType
63}
64
65type msgIDBody interface {
66 msgIDType() msgIDType
67}
68
69func formatMsgID(netID int64, target string, body msgIDBody) string {
70 var buf bytes.Buffer
71 w := bare.NewWriter(&buf)
72
73 header := msgIDHeader{
74 Version: msgIDVersion,
75 Network: bare.Int(netID),
76 Target: target,
77 Type: body.msgIDType(),
78 }
79 if err := bare.MarshalWriter(w, &header); err != nil {
80 panic(err)
81 }
82 if err := bare.MarshalWriter(w, body); err != nil {
83 panic(err)
84 }
85 return base64.RawURLEncoding.EncodeToString(buf.Bytes())
86}
87
88func parseMsgID(s string, body msgIDBody) (netID int64, target string, err error) {
89 b, err := base64.RawURLEncoding.DecodeString(s)
90 if err != nil {
91 return 0, "", fmt.Errorf("invalid internal message ID: %v", err)
92 }
93
94 r := bare.NewReader(bytes.NewReader(b))
95
96 var header msgIDHeader
97 if err := bare.UnmarshalBareReader(r, &header); err != nil {
98 return 0, "", fmt.Errorf("invalid internal message ID: %v", err)
99 }
100
101 if header.Version != msgIDVersion {
102 return 0, "", fmt.Errorf("invalid internal message ID: got version %v, want %v", header.Version, msgIDVersion)
103 }
104
105 if body != nil {
106 typ := body.msgIDType()
107 if header.Type != typ {
108 return 0, "", fmt.Errorf("invalid internal message ID: got type %v, want %v", header.Type, typ)
109 }
110
111 if err := bare.UnmarshalBareReader(r, body); err != nil {
112 return 0, "", fmt.Errorf("invalid internal message ID: %v", err)
113 }
114 }
115
116 return int64(header.Network), header.Target, nil
117}
Note: See TracBrowser for help on using the repository browser.