[go: up one dir, main page]

File: msgstore.go

package info (click to toggle)
soju 0.10.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,016 kB
  • sloc: sql: 243; sh: 55; makefile: 49; python: 32
file content (157 lines) | stat: -rw-r--r-- 4,615 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package msgstore

import (
	"bytes"
	"context"
	"encoding/base64"
	"fmt"
	"time"

	"git.sr.ht/~sircmpwn/go-bare"
	"gopkg.in/irc.v4"

	"codeberg.org/emersion/soju/database"
)

type LoadMessageOptions struct {
	Network *database.Network
	Entity  string
	Limit   int
	Events  bool
}

// Store is a per-user store for IRC messages.
type Store 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(ctx context.Context, network *database.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(ctx context.Context, id string, options *LoadMessageOptions) ([]*irc.Message, error)
	Append(ctx context.Context, network *database.Network, entity string, msg *irc.Message) (id string, err error)
}

type ChatHistoryTarget struct {
	Name          string
	LatestMessage time.Time
}

// ChatHistoryStore is a message store that supports chat history operations.
type ChatHistoryStore interface {
	Store

	// 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(ctx context.Context, network *database.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(ctx context.Context, start, end time.Time, options *LoadMessageOptions) ([]*irc.Message, error)
	// LoadAfterTime 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(ctx context.Context, start, end time.Time, options *LoadMessageOptions) ([]*irc.Message, error)
}

type SearchMessageOptions struct {
	Start time.Time
	End   time.Time
	Limit int
	From  string
	In    string
	Text  string
}

// SearchStore is a message store that supports server-side search operations.
type SearchStore interface {
	Store

	// Search returns messages matching the specified options.
	Search(ctx context.Context, network *database.Network, options *SearchMessageOptions) ([]*irc.Message, error)
}

// RenameNetworkStore is a message store which needs to be notified of network
// name changes.
type RenameNetworkStore interface {
	Store

	RenameNetwork(oldNet, newNet *database.Network) error
}

type msgIDType uint

const (
	msgIDNone msgIDType = iota
	msgIDMemory
	msgIDFS
	msgIDDB
)

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
}