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,
 | |
| 	}
 | |
| }
 |