summary refs log tree commit diff
path: root/vendor/maunium.net/go/mautrix/syncstore.go
diff options
context:
space:
mode:
authorEmile <git@emile.space>2024-10-25 15:55:50 +0200
committerEmile <git@emile.space>2024-10-25 15:55:50 +0200
commitc90f36e3dd179d2de96f4f5fe38d8dc9a9de6dfe (patch)
tree89e9afb41c5bf76f48cfb09305a2d3db8d302b06 /vendor/maunium.net/go/mautrix/syncstore.go
parent98bbb0f559a8883bc47bae80607dbe326a448e61 (diff)
vendor HEAD main
Diffstat (limited to 'vendor/maunium.net/go/mautrix/syncstore.go')
-rw-r--r--vendor/maunium.net/go/mautrix/syncstore.go175
1 files changed, 175 insertions, 0 deletions
diff --git a/vendor/maunium.net/go/mautrix/syncstore.go b/vendor/maunium.net/go/mautrix/syncstore.go
new file mode 100644
index 0000000..6c7fc9e
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/syncstore.go
@@ -0,0 +1,175 @@
+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,
+	}
+}