mirror of https://github.com/mautrix/go.git
176 lines
5.4 KiB
Go
176 lines
5.4 KiB
Go
package mautrix
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"maunium.net/go/mautrix/id"
|
|
)
|
|
|
|
var _ SyncStore = (*MemorySyncStore)(nil)
|
|
var _ SyncStore = (*AccountDataStore)(nil)
|
|
|
|
// SyncStore is an interface which must be satisfied to store client data.
|
|
//
|
|
// You can either write a struct which persists this data to disk, or you can use the
|
|
// provided "MemorySyncStore" which just keeps data around in-memory which is lost on
|
|
// restarts.
|
|
type SyncStore interface {
|
|
SaveFilterID(ctx context.Context, userID id.UserID, filterID string) error
|
|
LoadFilterID(ctx context.Context, userID id.UserID) (string, error)
|
|
SaveNextBatch(ctx context.Context, userID id.UserID, nextBatchToken string) error
|
|
LoadNextBatch(ctx context.Context, userID id.UserID) (string, error)
|
|
}
|
|
|
|
// Deprecated: renamed to SyncStore
|
|
type Storer = SyncStore
|
|
|
|
// MemorySyncStore implements the Storer interface.
|
|
//
|
|
// Everything is persisted in-memory as maps. It is not safe to load/save filter IDs
|
|
// or next batch tokens on any goroutine other than the syncing goroutine: the one
|
|
// which called Client.Sync().
|
|
type MemorySyncStore struct {
|
|
Filters map[id.UserID]string
|
|
NextBatch map[id.UserID]string
|
|
}
|
|
|
|
// SaveFilterID to memory.
|
|
func (s *MemorySyncStore) SaveFilterID(ctx context.Context, userID id.UserID, filterID string) error {
|
|
s.Filters[userID] = filterID
|
|
return nil
|
|
}
|
|
|
|
// LoadFilterID from memory.
|
|
func (s *MemorySyncStore) LoadFilterID(ctx context.Context, userID id.UserID) (string, error) {
|
|
return s.Filters[userID], nil
|
|
}
|
|
|
|
// SaveNextBatch to memory.
|
|
func (s *MemorySyncStore) SaveNextBatch(ctx context.Context, userID id.UserID, nextBatchToken string) error {
|
|
s.NextBatch[userID] = nextBatchToken
|
|
return nil
|
|
}
|
|
|
|
// LoadNextBatch from memory.
|
|
func (s *MemorySyncStore) LoadNextBatch(ctx context.Context, userID id.UserID) (string, error) {
|
|
return s.NextBatch[userID], nil
|
|
}
|
|
|
|
// NewMemorySyncStore constructs a new MemorySyncStore.
|
|
func NewMemorySyncStore() *MemorySyncStore {
|
|
return &MemorySyncStore{
|
|
Filters: make(map[id.UserID]string),
|
|
NextBatch: make(map[id.UserID]string),
|
|
}
|
|
}
|
|
|
|
// AccountDataStore uses account data to store the next batch token, and stores the filter ID in memory
|
|
// (as filters can be safely recreated every startup).
|
|
type AccountDataStore struct {
|
|
FilterID string
|
|
EventType string
|
|
client *Client
|
|
nextBatch string
|
|
}
|
|
|
|
type accountData struct {
|
|
NextBatch string `json:"next_batch"`
|
|
}
|
|
|
|
func (s *AccountDataStore) SaveFilterID(ctx context.Context, userID id.UserID, filterID string) error {
|
|
if userID.String() != s.client.UserID.String() {
|
|
panic("AccountDataStore must only be used with a single account")
|
|
}
|
|
s.FilterID = filterID
|
|
return nil
|
|
}
|
|
|
|
func (s *AccountDataStore) LoadFilterID(ctx context.Context, userID id.UserID) (string, error) {
|
|
if userID.String() != s.client.UserID.String() {
|
|
panic("AccountDataStore must only be used with a single account")
|
|
}
|
|
return s.FilterID, nil
|
|
}
|
|
|
|
func (s *AccountDataStore) SaveNextBatch(ctx context.Context, userID id.UserID, nextBatchToken string) error {
|
|
if userID.String() != s.client.UserID.String() {
|
|
panic("AccountDataStore must only be used with a single account")
|
|
} else if nextBatchToken == s.nextBatch {
|
|
return nil
|
|
}
|
|
|
|
data := accountData{
|
|
NextBatch: nextBatchToken,
|
|
}
|
|
|
|
err := s.client.SetAccountData(ctx, s.EventType, data)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to save next batch token to account data: %w", err)
|
|
} else {
|
|
s.client.Log.Debug().
|
|
Str("old_token", s.nextBatch).
|
|
Str("new_token", nextBatchToken).
|
|
Msg("Saved next batch token")
|
|
s.nextBatch = nextBatchToken
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *AccountDataStore) LoadNextBatch(ctx context.Context, userID id.UserID) (string, error) {
|
|
if userID.String() != s.client.UserID.String() {
|
|
panic("AccountDataStore must only be used with a single account")
|
|
}
|
|
|
|
data := &accountData{}
|
|
|
|
err := s.client.GetAccountData(ctx, s.EventType, data)
|
|
if err != nil {
|
|
if errors.Is(err, MNotFound) {
|
|
s.client.Log.Debug().Msg("No next batch token found in account data")
|
|
return "", nil
|
|
} else {
|
|
return "", fmt.Errorf("failed to load next batch token from account data: %w", err)
|
|
}
|
|
}
|
|
s.nextBatch = data.NextBatch
|
|
s.client.Log.Debug().Str("next_batch", data.NextBatch).Msg("Loaded next batch token from account data")
|
|
|
|
return s.nextBatch, nil
|
|
}
|
|
|
|
// NewAccountDataStore returns a new AccountDataStore, which stores
|
|
// the next_batch token as a custom event in account data in the
|
|
// homeserver.
|
|
//
|
|
// AccountDataStore is only appropriate for bots, not appservices.
|
|
//
|
|
// The event type should be a reversed DNS name like tld.domain.sub.internal and
|
|
// must be unique for a client. The data stored in it is considered internal
|
|
// and must not be modified through outside means. You should also add a filter
|
|
// for account data changes of this event type, to avoid ending up in a sync
|
|
// loop:
|
|
//
|
|
// filter := mautrix.Filter{
|
|
// AccountData: mautrix.FilterPart{
|
|
// Limit: 20,
|
|
// NotTypes: []event.Type{
|
|
// event.NewEventType(eventType),
|
|
// },
|
|
// },
|
|
// }
|
|
// // If you use a custom Syncer, set the filter there, not like this
|
|
// client.Syncer.(*mautrix.DefaultSyncer).FilterJSON = &filter
|
|
// client.Store = mautrix.NewAccountDataStore("com.example.mybot.store", client)
|
|
// go func() {
|
|
// err := client.Sync()
|
|
// // don't forget to check err
|
|
// }()
|
|
func NewAccountDataStore(eventType string, client *Client) *AccountDataStore {
|
|
return &AccountDataStore{
|
|
EventType: eventType,
|
|
client: client,
|
|
}
|
|
}
|