mirror of
https://github.com/anyproto/any-sync.git
synced 2025-06-08 05:57:03 +09:00
WIP work on components
This commit is contained in:
parent
467536ac97
commit
b0fa43fb14
34 changed files with 1343 additions and 838 deletions
51
app/app.go
51
app/app.go
|
@ -55,6 +55,7 @@ type ComponentStatable interface {
|
||||||
// App is the central part of the application
|
// App is the central part of the application
|
||||||
// It contains and manages all components
|
// It contains and manages all components
|
||||||
type App struct {
|
type App struct {
|
||||||
|
parent *App
|
||||||
components []Component
|
components []Component
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
startStat Stat
|
startStat Stat
|
||||||
|
@ -109,15 +110,29 @@ func VersionDescription() string {
|
||||||
return fmt.Sprintf("build on %s from %s at #%s(%s)", BuildDate, GitBranch, GitCommit, GitState)
|
return fmt.Sprintf("build on %s from %s at #%s(%s)", BuildDate, GitBranch, GitCommit, GitState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChildApp creates a child container which has access to parent's components
|
||||||
|
// It doesn't call Start on any of the parent's components
|
||||||
|
func (app *App) ChildApp() *App {
|
||||||
|
return &App{
|
||||||
|
parent: app,
|
||||||
|
deviceState: app.deviceState,
|
||||||
|
anySyncVersion: app.AnySyncVersion(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Register adds service to registry
|
// Register adds service to registry
|
||||||
// All components will be started in the order they were registered
|
// All components will be started in the order they were registered
|
||||||
func (app *App) Register(s Component) *App {
|
func (app *App) Register(s Component) *App {
|
||||||
app.mu.Lock()
|
app.mu.Lock()
|
||||||
defer app.mu.Unlock()
|
defer app.mu.Unlock()
|
||||||
for _, es := range app.components {
|
current := app
|
||||||
if s.Name() == es.Name() {
|
for current != nil {
|
||||||
panic(fmt.Errorf("component '%s' already registered", s.Name()))
|
for _, es := range current.components {
|
||||||
|
if s.Name() == es.Name() {
|
||||||
|
panic(fmt.Errorf("component '%s' already registered", s.Name()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
current = current.parent
|
||||||
}
|
}
|
||||||
app.components = append(app.components, s)
|
app.components = append(app.components, s)
|
||||||
return app
|
return app
|
||||||
|
@ -128,10 +143,14 @@ func (app *App) Register(s Component) *App {
|
||||||
func (app *App) Component(name string) Component {
|
func (app *App) Component(name string) Component {
|
||||||
app.mu.RLock()
|
app.mu.RLock()
|
||||||
defer app.mu.RUnlock()
|
defer app.mu.RUnlock()
|
||||||
for _, s := range app.components {
|
current := app
|
||||||
if s.Name() == name {
|
for current != nil {
|
||||||
return s
|
for _, s := range current.components {
|
||||||
|
if s.Name() == name {
|
||||||
|
return s
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
current = current.parent
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -149,10 +168,14 @@ func (app *App) MustComponent(name string) Component {
|
||||||
func MustComponent[i any](app *App) i {
|
func MustComponent[i any](app *App) i {
|
||||||
app.mu.RLock()
|
app.mu.RLock()
|
||||||
defer app.mu.RUnlock()
|
defer app.mu.RUnlock()
|
||||||
for _, s := range app.components {
|
current := app
|
||||||
if v, ok := s.(i); ok {
|
for current != nil {
|
||||||
return v
|
for _, s := range current.components {
|
||||||
|
if v, ok := s.(i); ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
current = current.parent
|
||||||
}
|
}
|
||||||
empty := new(i)
|
empty := new(i)
|
||||||
panic(fmt.Errorf("component with interface %T is not found", empty))
|
panic(fmt.Errorf("component with interface %T is not found", empty))
|
||||||
|
@ -162,9 +185,13 @@ func MustComponent[i any](app *App) i {
|
||||||
func (app *App) ComponentNames() (names []string) {
|
func (app *App) ComponentNames() (names []string) {
|
||||||
app.mu.RLock()
|
app.mu.RLock()
|
||||||
defer app.mu.RUnlock()
|
defer app.mu.RUnlock()
|
||||||
names = make([]string, len(app.components))
|
names = make([]string, 0, len(app.components))
|
||||||
for i, c := range app.components {
|
current := app
|
||||||
names[i] = c.Name()
|
for current != nil {
|
||||||
|
for _, c := range current.components {
|
||||||
|
names = append(names, c.Name())
|
||||||
|
}
|
||||||
|
current = current.parent
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,20 @@ func TestAppServiceRegistry(t *testing.T) {
|
||||||
names := app.ComponentNames()
|
names := app.ComponentNames()
|
||||||
assert.Equal(t, names, []string{"c1", "r1", "s1"})
|
assert.Equal(t, names, []string{"c1", "r1", "s1"})
|
||||||
})
|
})
|
||||||
|
t.Run("Child MustComponent", func(t *testing.T) {
|
||||||
|
app := app.ChildApp()
|
||||||
|
app.Register(newTestService(testTypeComponent, "x1", nil, nil))
|
||||||
|
for _, name := range []string{"c1", "r1", "s1", "x1"} {
|
||||||
|
assert.NotPanics(t, func() { app.MustComponent(name) }, name)
|
||||||
|
}
|
||||||
|
assert.Panics(t, func() { app.MustComponent("not-registered") })
|
||||||
|
})
|
||||||
|
t.Run("Child ComponentNames", func(t *testing.T) {
|
||||||
|
app := app.ChildApp()
|
||||||
|
app.Register(newTestService(testTypeComponent, "x1", nil, nil))
|
||||||
|
names := app.ComponentNames()
|
||||||
|
assert.Equal(t, names, []string{"x1", "c1", "r1", "s1"})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAppStart(t *testing.T) {
|
func TestAppStart(t *testing.T) {
|
||||||
|
|
|
@ -2,32 +2,53 @@ package commonspace
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/anyproto/any-sync/app"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/syncobjectgetter"
|
"github.com/anyproto/any-sync/commonspace/object/syncobjectgetter"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/spacestate"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
type commonGetter struct {
|
type ObjectManager interface {
|
||||||
|
treemanager.TreeManager
|
||||||
|
AddObject(object syncobjectgetter.SyncObject)
|
||||||
|
GetObject(ctx context.Context, objectId string) (obj syncobjectgetter.SyncObject, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type objectManager struct {
|
||||||
treemanager.TreeManager
|
treemanager.TreeManager
|
||||||
spaceId string
|
spaceId string
|
||||||
reservedObjects []syncobjectgetter.SyncObject
|
reservedObjects []syncobjectgetter.SyncObject
|
||||||
spaceIsClosed *atomic.Bool
|
spaceIsClosed *atomic.Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCommonGetter(spaceId string, getter treemanager.TreeManager, spaceIsClosed *atomic.Bool) *commonGetter {
|
func NewObjectManager(manager treemanager.TreeManager) ObjectManager {
|
||||||
return &commonGetter{
|
return &objectManager{
|
||||||
TreeManager: getter,
|
TreeManager: manager,
|
||||||
spaceId: spaceId,
|
|
||||||
spaceIsClosed: spaceIsClosed,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *commonGetter) AddObject(object syncobjectgetter.SyncObject) {
|
func (c *objectManager) Init(a *app.App) (err error) {
|
||||||
|
state := a.MustComponent(spacestate.CName).(*spacestate.SpaceState)
|
||||||
|
c.spaceId = state.SpaceId
|
||||||
|
c.spaceIsClosed = state.SpaceIsClosed
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *objectManager) Run(ctx context.Context) (err error) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *objectManager) Close(ctx context.Context) (err error) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *objectManager) AddObject(object syncobjectgetter.SyncObject) {
|
||||||
c.reservedObjects = append(c.reservedObjects, object)
|
c.reservedObjects = append(c.reservedObjects, object)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *commonGetter) GetTree(ctx context.Context, spaceId, treeId string) (objecttree.ObjectTree, error) {
|
func (c *objectManager) GetTree(ctx context.Context, spaceId, treeId string) (objecttree.ObjectTree, error) {
|
||||||
if c.spaceIsClosed.Load() {
|
if c.spaceIsClosed.Load() {
|
||||||
return nil, ErrSpaceClosed
|
return nil, ErrSpaceClosed
|
||||||
}
|
}
|
||||||
|
@ -37,7 +58,7 @@ func (c *commonGetter) GetTree(ctx context.Context, spaceId, treeId string) (obj
|
||||||
return c.TreeManager.GetTree(ctx, spaceId, treeId)
|
return c.TreeManager.GetTree(ctx, spaceId, treeId)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *commonGetter) getReservedObject(id string) syncobjectgetter.SyncObject {
|
func (c *objectManager) getReservedObject(id string) syncobjectgetter.SyncObject {
|
||||||
for _, obj := range c.reservedObjects {
|
for _, obj := range c.reservedObjects {
|
||||||
if obj != nil && obj.Id() == id {
|
if obj != nil && obj.Id() == id {
|
||||||
return obj
|
return obj
|
||||||
|
@ -46,7 +67,7 @@ func (c *commonGetter) getReservedObject(id string) syncobjectgetter.SyncObject
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *commonGetter) GetObject(ctx context.Context, objectId string) (obj syncobjectgetter.SyncObject, err error) {
|
func (c *objectManager) GetObject(ctx context.Context, objectId string) (obj syncobjectgetter.SyncObject, err error) {
|
||||||
if c.spaceIsClosed.Load() {
|
if c.spaceIsClosed.Load() {
|
||||||
return nil, ErrSpaceClosed
|
return nil, ErrSpaceClosed
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package commonspace
|
package config
|
||||||
|
|
||||||
type ConfigGetter interface {
|
type ConfigGetter interface {
|
||||||
GetSpace() Config
|
GetSpace() Config
|
150
commonspace/deletionstate/deletionstate.go
Normal file
150
commonspace/deletionstate/deletionstate.go
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
//go:generate mockgen -destination mock_settingsstate/mock_settingsstate.go github.com/anyproto/any-sync/commonspace/settings/settingsstate ObjectDeletionState,StateBuilder,ChangeFactory
|
||||||
|
package deletionstate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/anyproto/any-sync/app"
|
||||||
|
"github.com/anyproto/any-sync/app/logger"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/spacestate"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = logger.NewNamed(CName)
|
||||||
|
|
||||||
|
const CName = "common.commonspace.deletionstate"
|
||||||
|
|
||||||
|
type StateUpdateObserver func(ids []string)
|
||||||
|
|
||||||
|
type ObjectDeletionState interface {
|
||||||
|
app.Component
|
||||||
|
AddObserver(observer StateUpdateObserver)
|
||||||
|
Add(ids map[string]struct{})
|
||||||
|
GetQueued() (ids []string)
|
||||||
|
Delete(id string) (err error)
|
||||||
|
Exists(id string) bool
|
||||||
|
Filter(ids []string) (filtered []string)
|
||||||
|
}
|
||||||
|
|
||||||
|
type objectDeletionState struct {
|
||||||
|
sync.RWMutex
|
||||||
|
log logger.CtxLogger
|
||||||
|
queued map[string]struct{}
|
||||||
|
deleted map[string]struct{}
|
||||||
|
stateUpdateObservers []StateUpdateObserver
|
||||||
|
storage spacestorage.SpaceStorage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *objectDeletionState) Init(a *app.App) (err error) {
|
||||||
|
st.storage = a.MustComponent(spacestate.CName).(*spacestate.SpaceState).SpaceStorage
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *objectDeletionState) Name() (name string) {
|
||||||
|
return CName
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewObjectDeletionState() ObjectDeletionState {
|
||||||
|
return &objectDeletionState{
|
||||||
|
log: log,
|
||||||
|
queued: map[string]struct{}{},
|
||||||
|
deleted: map[string]struct{}{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *objectDeletionState) AddObserver(observer StateUpdateObserver) {
|
||||||
|
st.Lock()
|
||||||
|
defer st.Unlock()
|
||||||
|
st.stateUpdateObservers = append(st.stateUpdateObservers, observer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *objectDeletionState) Add(ids map[string]struct{}) {
|
||||||
|
var added []string
|
||||||
|
st.Lock()
|
||||||
|
defer func() {
|
||||||
|
st.Unlock()
|
||||||
|
for _, ob := range st.stateUpdateObservers {
|
||||||
|
ob(added)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for id := range ids {
|
||||||
|
if _, exists := st.deleted[id]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, exists := st.queued[id]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var status string
|
||||||
|
status, err := st.storage.TreeDeletedStatus(id)
|
||||||
|
if err != nil {
|
||||||
|
st.log.Warn("failed to get deleted status", zap.String("treeId", id), zap.Error(err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch status {
|
||||||
|
case spacestorage.TreeDeletedStatusQueued:
|
||||||
|
st.queued[id] = struct{}{}
|
||||||
|
case spacestorage.TreeDeletedStatusDeleted:
|
||||||
|
st.deleted[id] = struct{}{}
|
||||||
|
default:
|
||||||
|
err := st.storage.SetTreeDeletedStatus(id, spacestorage.TreeDeletedStatusQueued)
|
||||||
|
if err != nil {
|
||||||
|
st.log.Warn("failed to set deleted status", zap.String("treeId", id), zap.Error(err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
st.queued[id] = struct{}{}
|
||||||
|
}
|
||||||
|
added = append(added, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *objectDeletionState) GetQueued() (ids []string) {
|
||||||
|
st.RLock()
|
||||||
|
defer st.RUnlock()
|
||||||
|
ids = make([]string, 0, len(st.queued))
|
||||||
|
for id := range st.queued {
|
||||||
|
ids = append(ids, id)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *objectDeletionState) Delete(id string) (err error) {
|
||||||
|
st.Lock()
|
||||||
|
defer st.Unlock()
|
||||||
|
delete(st.queued, id)
|
||||||
|
st.deleted[id] = struct{}{}
|
||||||
|
err = st.storage.SetTreeDeletedStatus(id, spacestorage.TreeDeletedStatusDeleted)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *objectDeletionState) Exists(id string) bool {
|
||||||
|
st.RLock()
|
||||||
|
defer st.RUnlock()
|
||||||
|
return st.exists(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *objectDeletionState) Filter(ids []string) (filtered []string) {
|
||||||
|
st.RLock()
|
||||||
|
defer st.RUnlock()
|
||||||
|
for _, id := range ids {
|
||||||
|
if !st.exists(id) {
|
||||||
|
filtered = append(filtered, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *objectDeletionState) exists(id string) bool {
|
||||||
|
if _, exists := st.deleted[id]; exists {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, exists := st.queued[id]; exists {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -26,26 +26,17 @@ type DiffSyncer interface {
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDiffSyncer(
|
func newDiffSyncer(hs *headSync) DiffSyncer {
|
||||||
spaceId string,
|
|
||||||
diff ldiff.Diff,
|
|
||||||
peerManager peermanager.PeerManager,
|
|
||||||
cache treemanager.TreeManager,
|
|
||||||
storage spacestorage.SpaceStorage,
|
|
||||||
clientFactory spacesyncproto.ClientFactory,
|
|
||||||
syncStatus syncstatus.StatusUpdater,
|
|
||||||
credentialProvider credentialprovider.CredentialProvider,
|
|
||||||
log logger.CtxLogger) DiffSyncer {
|
|
||||||
return &diffSyncer{
|
return &diffSyncer{
|
||||||
diff: diff,
|
diff: hs.diff,
|
||||||
spaceId: spaceId,
|
spaceId: hs.spaceId,
|
||||||
treeManager: cache,
|
treeManager: hs.treeManager,
|
||||||
storage: storage,
|
storage: hs.storage,
|
||||||
peerManager: peerManager,
|
peerManager: hs.peerManager,
|
||||||
clientFactory: clientFactory,
|
clientFactory: spacesyncproto.ClientFactoryFunc(spacesyncproto.NewDRPCSpaceSyncClient),
|
||||||
credentialProvider: credentialProvider,
|
credentialProvider: hs.credentialProvider,
|
||||||
log: log,
|
log: log,
|
||||||
syncStatus: syncStatus,
|
syncStatus: hs.syncStatus,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
//go:generate mockgen -destination mock_headsync/mock_headsync.go github.com/anyproto/any-sync/commonspace/headsync DiffSyncer
|
|
||||||
package headsync
|
package headsync
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/anyproto/any-sync/app"
|
||||||
"github.com/anyproto/any-sync/app/ldiff"
|
"github.com/anyproto/any-sync/app/ldiff"
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
"github.com/anyproto/any-sync/app/logger"
|
||||||
|
config2 "github.com/anyproto/any-sync/commonspace/config"
|
||||||
"github.com/anyproto/any-sync/commonspace/credentialprovider"
|
"github.com/anyproto/any-sync/commonspace/credentialprovider"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/headsync"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
"github.com/anyproto/any-sync/commonspace/peermanager"
|
||||||
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/spacestate"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||||
|
@ -17,109 +20,114 @@ import (
|
||||||
"github.com/anyproto/any-sync/util/periodicsync"
|
"github.com/anyproto/any-sync/util/periodicsync"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
"strings"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var log = logger.NewNamed(CName)
|
||||||
|
|
||||||
|
const CName = "common.commonspace.headsync"
|
||||||
|
|
||||||
type TreeHeads struct {
|
type TreeHeads struct {
|
||||||
Id string
|
Id string
|
||||||
Heads []string
|
Heads []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type HeadSync interface {
|
type HeadSync interface {
|
||||||
Init(objectIds []string, deletionState settingsstate.ObjectDeletionState)
|
|
||||||
|
|
||||||
UpdateHeads(id string, heads []string)
|
UpdateHeads(id string, heads []string)
|
||||||
HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error)
|
HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error)
|
||||||
RemoveObjects(ids []string)
|
RemoveObjects(ids []string)
|
||||||
AllIds() []string
|
AllIds() []string
|
||||||
DebugAllHeads() (res []TreeHeads)
|
DebugAllHeads() (res []TreeHeads)
|
||||||
|
|
||||||
Close() (err error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type headSync struct {
|
type headSync struct {
|
||||||
spaceId string
|
spaceId string
|
||||||
periodicSync periodicsync.PeriodicSync
|
|
||||||
storage spacestorage.SpaceStorage
|
|
||||||
diff ldiff.Diff
|
|
||||||
log logger.CtxLogger
|
|
||||||
syncer DiffSyncer
|
|
||||||
configuration nodeconf.NodeConf
|
|
||||||
spaceIsDeleted *atomic.Bool
|
spaceIsDeleted *atomic.Bool
|
||||||
|
syncPeriod int
|
||||||
|
|
||||||
syncPeriod int
|
periodicSync periodicsync.PeriodicSync
|
||||||
|
storage spacestorage.SpaceStorage
|
||||||
|
diff ldiff.Diff
|
||||||
|
log logger.CtxLogger
|
||||||
|
syncer headsync.DiffSyncer
|
||||||
|
configuration nodeconf.NodeConf
|
||||||
|
peerManager peermanager.PeerManager
|
||||||
|
treeManager treemanager.TreeManager
|
||||||
|
credentialProvider credentialprovider.CredentialProvider
|
||||||
|
syncStatus syncstatus.StatusProvider
|
||||||
|
deletionState settingsstate.ObjectDeletionState
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHeadSync(
|
func New() *headSync {
|
||||||
spaceId string,
|
return &headSync{}
|
||||||
spaceIsDeleted *atomic.Bool,
|
}
|
||||||
syncPeriod int,
|
|
||||||
configuration nodeconf.NodeConf,
|
|
||||||
storage spacestorage.SpaceStorage,
|
|
||||||
peerManager peermanager.PeerManager,
|
|
||||||
cache treemanager.TreeManager,
|
|
||||||
syncStatus syncstatus.StatusUpdater,
|
|
||||||
credentialProvider credentialprovider.CredentialProvider,
|
|
||||||
log logger.CtxLogger) HeadSync {
|
|
||||||
|
|
||||||
diff := ldiff.New(16, 16)
|
func (h *headSync) Init(a *app.App) (err error) {
|
||||||
l := log.With(zap.String("spaceId", spaceId))
|
shared := a.MustComponent(spacestate.CName).(*spacestate.SpaceState)
|
||||||
factory := spacesyncproto.ClientFactoryFunc(spacesyncproto.NewDRPCSpaceSyncClient)
|
cfg := a.MustComponent("cfg").(config2.ConfigGetter)
|
||||||
syncer := newDiffSyncer(spaceId, diff, peerManager, cache, storage, factory, syncStatus, credentialProvider, l)
|
h.spaceId = shared.SpaceId
|
||||||
|
h.spaceIsDeleted = shared.SpaceIsDeleted
|
||||||
|
h.syncPeriod = cfg.GetSpace().SyncPeriod
|
||||||
|
h.configuration = a.MustComponent(nodeconf.CName).(nodeconf.NodeConf)
|
||||||
|
h.log = log.With(zap.String("spaceId", h.spaceId))
|
||||||
|
h.storage = a.MustComponent("spacestorage").(spacestorage.SpaceStorage)
|
||||||
|
h.diff = ldiff.New(16, 16)
|
||||||
|
h.peerManager = a.MustComponent(peermanager.CName).(peermanager.PeerManager)
|
||||||
|
h.credentialProvider = a.MustComponent(credentialprovider.CName).(credentialprovider.CredentialProvider)
|
||||||
|
h.syncStatus = a.MustComponent(syncstatus.CName).(syncstatus.StatusProvider)
|
||||||
|
h.treeManager = a.MustComponent(treemanager.CName).(treemanager.TreeManager)
|
||||||
|
h.deletionState = a.MustComponent("deletionstate").(settingsstate.ObjectDeletionState)
|
||||||
|
h.syncer = newDiffSyncer(h)
|
||||||
sync := func(ctx context.Context) (err error) {
|
sync := func(ctx context.Context) (err error) {
|
||||||
// for clients cancelling the sync process
|
// for clients cancelling the sync process
|
||||||
if spaceIsDeleted.Load() && !configuration.IsResponsible(spaceId) {
|
if h.spaceIsDeleted.Load() && !h.configuration.IsResponsible(h.spaceId) {
|
||||||
return spacesyncproto.ErrSpaceIsDeleted
|
return spacesyncproto.ErrSpaceIsDeleted
|
||||||
}
|
}
|
||||||
return syncer.Sync(ctx)
|
return h.syncer.Sync(ctx)
|
||||||
}
|
|
||||||
periodicSync := periodicsync.NewPeriodicSync(syncPeriod, time.Minute, sync, l)
|
|
||||||
|
|
||||||
return &headSync{
|
|
||||||
spaceId: spaceId,
|
|
||||||
storage: storage,
|
|
||||||
syncer: syncer,
|
|
||||||
periodicSync: periodicSync,
|
|
||||||
diff: diff,
|
|
||||||
log: log,
|
|
||||||
syncPeriod: syncPeriod,
|
|
||||||
configuration: configuration,
|
|
||||||
spaceIsDeleted: spaceIsDeleted,
|
|
||||||
}
|
}
|
||||||
|
h.periodicSync = periodicsync.NewPeriodicSync(h.syncPeriod, time.Minute, sync, h.log)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *headSync) Init(objectIds []string, deletionState settingsstate.ObjectDeletionState) {
|
func (h *headSync) Name() (name string) {
|
||||||
d.fillDiff(objectIds)
|
return CName
|
||||||
d.syncer.Init(deletionState)
|
|
||||||
d.periodicSync.Run()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *headSync) HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error) {
|
func (h *headSync) Run(ctx context.Context) (err error) {
|
||||||
if d.spaceIsDeleted.Load() {
|
initialIds, err := h.storage.StoredIds()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.fillDiff(initialIds)
|
||||||
|
h.periodicSync.Run()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *headSync) HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error) {
|
||||||
|
if h.spaceIsDeleted.Load() {
|
||||||
peerId, err := peer.CtxPeerId(ctx)
|
peerId, err := peer.CtxPeerId(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// stop receiving all request for sync from clients
|
// stop receiving all request for sync from clients
|
||||||
if !slices.Contains(d.configuration.NodeIds(d.spaceId), peerId) {
|
if !slices.Contains(h.configuration.NodeIds(h.spaceId), peerId) {
|
||||||
return nil, spacesyncproto.ErrSpaceIsDeleted
|
return nil, spacesyncproto.ErrSpaceIsDeleted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return HandleRangeRequest(ctx, d.diff, req)
|
return HandleRangeRequest(ctx, h.diff, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *headSync) UpdateHeads(id string, heads []string) {
|
func (h *headSync) UpdateHeads(id string, heads []string) {
|
||||||
d.syncer.UpdateHeads(id, heads)
|
h.syncer.UpdateHeads(id, heads)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *headSync) AllIds() []string {
|
func (h *headSync) AllIds() []string {
|
||||||
return d.diff.Ids()
|
return h.diff.Ids()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *headSync) DebugAllHeads() (res []TreeHeads) {
|
func (h *headSync) DebugAllHeads() (res []TreeHeads) {
|
||||||
els := d.diff.Elements()
|
els := h.diff.Elements()
|
||||||
for _, el := range els {
|
for _, el := range els {
|
||||||
idHead := TreeHeads{
|
idHead := TreeHeads{
|
||||||
Id: el.Id,
|
Id: el.Id,
|
||||||
|
@ -130,19 +138,19 @@ func (d *headSync) DebugAllHeads() (res []TreeHeads) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *headSync) RemoveObjects(ids []string) {
|
func (h *headSync) RemoveObjects(ids []string) {
|
||||||
d.syncer.RemoveObjects(ids)
|
h.syncer.RemoveObjects(ids)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *headSync) Close() (err error) {
|
func (h *headSync) Close(ctx context.Context) (err error) {
|
||||||
d.periodicSync.Close()
|
h.periodicSync.Close()
|
||||||
return d.syncer.Close()
|
return h.syncer.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *headSync) fillDiff(objectIds []string) {
|
func (h *headSync) fillDiff(objectIds []string) {
|
||||||
var els = make([]ldiff.Element, 0, len(objectIds))
|
var els = make([]ldiff.Element, 0, len(objectIds))
|
||||||
for _, id := range objectIds {
|
for _, id := range objectIds {
|
||||||
st, err := d.storage.TreeStorage(id)
|
st, err := h.storage.TreeStorage(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -155,32 +163,8 @@ func (d *headSync) fillDiff(objectIds []string) {
|
||||||
Head: concatStrings(heads),
|
Head: concatStrings(heads),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
d.diff.Set(els...)
|
h.diff.Set(els...)
|
||||||
if err := d.storage.WriteSpaceHash(d.diff.Hash()); err != nil {
|
if err := h.storage.WriteSpaceHash(h.diff.Hash()); err != nil {
|
||||||
d.log.Error("can't write space hash", zap.Error(err))
|
h.log.Error("can't write space hash", zap.Error(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func concatStrings(strs []string) string {
|
|
||||||
var (
|
|
||||||
b strings.Builder
|
|
||||||
totalLen int
|
|
||||||
)
|
|
||||||
for _, s := range strs {
|
|
||||||
totalLen += len(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Grow(totalLen)
|
|
||||||
for _, s := range strs {
|
|
||||||
b.WriteString(s)
|
|
||||||
}
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitString(str string) (res []string) {
|
|
||||||
const cidLen = 59
|
|
||||||
for i := 0; i < len(str); i += cidLen {
|
|
||||||
res = append(res, str[i:i+cidLen])
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
27
commonspace/headsync/util.go
Normal file
27
commonspace/headsync/util.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package headsync
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
func concatStrings(strs []string) string {
|
||||||
|
var (
|
||||||
|
b strings.Builder
|
||||||
|
totalLen int
|
||||||
|
)
|
||||||
|
for _, s := range strs {
|
||||||
|
totalLen += len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Grow(totalLen)
|
||||||
|
for _, s := range strs {
|
||||||
|
b.WriteString(s)
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitString(str string) (res []string) {
|
||||||
|
const cidLen = 59
|
||||||
|
for i := 0; i < len(str); i += cidLen {
|
||||||
|
res = append(res, str[i:i+cidLen])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ package synctree
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/objectsync/syncclient"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
"github.com/anyproto/any-sync/app/logger"
|
||||||
|
@ -11,7 +12,6 @@ import (
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
|
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||||
|
@ -44,7 +44,7 @@ type SyncTree interface {
|
||||||
type syncTree struct {
|
type syncTree struct {
|
||||||
objecttree.ObjectTree
|
objecttree.ObjectTree
|
||||||
synchandler.SyncHandler
|
synchandler.SyncHandler
|
||||||
syncClient objectsync.SyncClient
|
syncClient syncclient.SyncClient
|
||||||
syncStatus syncstatus.StatusUpdater
|
syncStatus syncstatus.StatusUpdater
|
||||||
notifiable HeadNotifiable
|
notifiable HeadNotifiable
|
||||||
listener updatelistener.UpdateListener
|
listener updatelistener.UpdateListener
|
||||||
|
@ -61,7 +61,7 @@ type ResponsiblePeersGetter interface {
|
||||||
|
|
||||||
type BuildDeps struct {
|
type BuildDeps struct {
|
||||||
SpaceId string
|
SpaceId string
|
||||||
SyncClient objectsync.SyncClient
|
SyncClient syncclient.SyncClient
|
||||||
Configuration nodeconf.NodeConf
|
Configuration nodeconf.NodeConf
|
||||||
HeadNotifiable HeadNotifiable
|
HeadNotifiable HeadNotifiable
|
||||||
Listener updatelistener.UpdateListener
|
Listener updatelistener.UpdateListener
|
||||||
|
@ -119,7 +119,7 @@ func buildSyncTree(ctx context.Context, sendUpdate bool, deps BuildDeps) (t Sync
|
||||||
if sendUpdate {
|
if sendUpdate {
|
||||||
headUpdate := syncTree.syncClient.CreateHeadUpdate(t, nil)
|
headUpdate := syncTree.syncClient.CreateHeadUpdate(t, nil)
|
||||||
// send to everybody, because everybody should know that the node or client got new tree
|
// send to everybody, because everybody should know that the node or client got new tree
|
||||||
syncTree.syncClient.Broadcast(ctx, headUpdate)
|
syncTree.syncClient.Broadcast(headUpdate)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -156,7 +156,7 @@ func (s *syncTree) AddContent(ctx context.Context, content objecttree.SignableCh
|
||||||
}
|
}
|
||||||
s.syncStatus.HeadsChange(s.Id(), res.Heads)
|
s.syncStatus.HeadsChange(s.Id(), res.Heads)
|
||||||
headUpdate := s.syncClient.CreateHeadUpdate(s, res.Added)
|
headUpdate := s.syncClient.CreateHeadUpdate(s, res.Added)
|
||||||
s.syncClient.Broadcast(ctx, headUpdate)
|
s.syncClient.Broadcast(headUpdate)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,7 +183,7 @@ func (s *syncTree) AddRawChanges(ctx context.Context, changesPayload objecttree.
|
||||||
s.notifiable.UpdateHeads(s.Id(), res.Heads)
|
s.notifiable.UpdateHeads(s.Id(), res.Heads)
|
||||||
}
|
}
|
||||||
headUpdate := s.syncClient.CreateHeadUpdate(s, res.Added)
|
headUpdate := s.syncClient.CreateHeadUpdate(s, res.Added)
|
||||||
s.syncClient.Broadcast(ctx, headUpdate)
|
s.syncClient.Broadcast(headUpdate)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -239,7 +239,7 @@ func (s *syncTree) SyncWithPeer(ctx context.Context, peerId string) (err error)
|
||||||
s.Lock()
|
s.Lock()
|
||||||
defer s.Unlock()
|
defer s.Unlock()
|
||||||
headUpdate := s.syncClient.CreateHeadUpdate(s, nil)
|
headUpdate := s.syncClient.CreateHeadUpdate(s, nil)
|
||||||
return s.syncClient.SendWithReply(ctx, peerId, headUpdate.RootChange.Id, headUpdate, "")
|
return s.syncClient.SendUpdate(peerId, headUpdate.RootChange.Id, headUpdate)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *syncTree) afterBuild() {
|
func (s *syncTree) afterBuild() {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync"
|
"github.com/anyproto/any-sync/commonspace/objectsync"
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync/mock_objectsync"
|
"github.com/anyproto/any-sync/commonspace/objectsync/mock_objectsync"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/objectsync/syncclient"
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||||
"github.com/anyproto/any-sync/nodeconf"
|
"github.com/anyproto/any-sync/nodeconf"
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
|
@ -18,7 +19,7 @@ import (
|
||||||
|
|
||||||
type syncTreeMatcher struct {
|
type syncTreeMatcher struct {
|
||||||
objTree objecttree.ObjectTree
|
objTree objecttree.ObjectTree
|
||||||
client objectsync.SyncClient
|
client syncclient.SyncClient
|
||||||
listener updatelistener.UpdateListener
|
listener updatelistener.UpdateListener
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,8 +35,8 @@ func (s syncTreeMatcher) String() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func syncClientFuncCreator(client objectsync.SyncClient) func(spaceId string, factory objectsync.RequestFactory, objectSync objectsync.ObjectSync, configuration nodeconf.NodeConf) objectsync.SyncClient {
|
func syncClientFuncCreator(client syncclient.SyncClient) func(spaceId string, factory syncclient.RequestFactory, objectSync objectsync.ObjectSync, configuration nodeconf.NodeConf) syncclient.SyncClient {
|
||||||
return func(spaceId string, factory objectsync.RequestFactory, objectSync objectsync.ObjectSync, configuration nodeconf.NodeConf) objectsync.SyncClient {
|
return func(spaceId string, factory syncclient.RequestFactory, objectSync objectsync.ObjectSync, configuration nodeconf.NodeConf) syncclient.SyncClient {
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync"
|
"github.com/anyproto/any-sync/commonspace/objectsync/syncclient"
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||||
|
@ -16,7 +16,7 @@ import (
|
||||||
|
|
||||||
type syncTreeHandler struct {
|
type syncTreeHandler struct {
|
||||||
objTree objecttree.ObjectTree
|
objTree objecttree.ObjectTree
|
||||||
syncClient objectsync.SyncClient
|
syncClient syncclient.SyncClient
|
||||||
syncStatus syncstatus.StatusUpdater
|
syncStatus syncstatus.StatusUpdater
|
||||||
handlerLock sync.Mutex
|
handlerLock sync.Mutex
|
||||||
spaceId string
|
spaceId string
|
||||||
|
@ -25,7 +25,7 @@ type syncTreeHandler struct {
|
||||||
|
|
||||||
const maxQueueSize = 5
|
const maxQueueSize = 5
|
||||||
|
|
||||||
func newSyncTreeHandler(spaceId string, objTree objecttree.ObjectTree, syncClient objectsync.SyncClient, syncStatus syncstatus.StatusUpdater) synchandler.SyncHandler {
|
func newSyncTreeHandler(spaceId string, objTree objecttree.ObjectTree, syncClient syncclient.SyncClient, syncStatus syncstatus.StatusUpdater) synchandler.SyncHandler {
|
||||||
return &syncTreeHandler{
|
return &syncTreeHandler{
|
||||||
objTree: objTree,
|
objTree: objTree,
|
||||||
syncClient: syncClient,
|
syncClient: syncClient,
|
||||||
|
@ -119,7 +119,7 @@ func (s *syncTreeHandler) handleHeadUpdate(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.syncClient.SendWithReply(ctx, senderId, treeId, fullRequest, replyId)
|
return s.syncClient.QueueRequest(ctx, senderId, treeId, fullRequest, replyId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.alreadyHasHeads(objTree, update.Heads) {
|
if s.alreadyHasHeads(objTree, update.Heads) {
|
||||||
|
@ -143,7 +143,7 @@ func (s *syncTreeHandler) handleHeadUpdate(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.syncClient.SendWithReply(ctx, senderId, treeId, fullRequest, replyId)
|
return s.syncClient.QueueRequest(ctx, senderId, treeId, fullRequest, replyId)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *syncTreeHandler) handleFullSyncRequest(
|
func (s *syncTreeHandler) handleFullSyncRequest(
|
||||||
|
@ -169,7 +169,7 @@ func (s *syncTreeHandler) handleFullSyncRequest(
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorCtx(ctx, "full sync request finished with error", zap.Error(err))
|
log.ErrorCtx(ctx, "full sync request finished with error", zap.Error(err))
|
||||||
s.syncClient.SendWithReply(ctx, senderId, treeId, treechangeproto.WrapError(treechangeproto.ErrFullSync, header), replyId)
|
s.syncClient.QueueRequest(ctx, senderId, treeId, treechangeproto.WrapError(treechangeproto.ErrFullSync, header), replyId)
|
||||||
return
|
return
|
||||||
} else if fullResponse != nil {
|
} else if fullResponse != nil {
|
||||||
cnt := fullResponse.Content.GetFullSyncResponse()
|
cnt := fullResponse.Content.GetFullSyncResponse()
|
||||||
|
@ -192,7 +192,7 @@ func (s *syncTreeHandler) handleFullSyncRequest(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.syncClient.SendWithReply(ctx, senderId, treeId, fullResponse, replyId)
|
return s.syncClient.QueueRequest(ctx, senderId, treeId, fullResponse, replyId)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *syncTreeHandler) handleFullSyncResponse(
|
func (s *syncTreeHandler) handleFullSyncResponse(
|
||||||
|
|
|
@ -3,8 +3,8 @@ package synctree
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync/mock_objectsync"
|
"github.com/anyproto/any-sync/commonspace/objectsync/mock_objectsync"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/objectsync/syncclient"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ func TestSyncHandler_HandleHeadUpdate(t *testing.T) {
|
||||||
SnapshotPath: []string{"h1"},
|
SnapshotPath: []string{"h1"},
|
||||||
}
|
}
|
||||||
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
objectMsg, _ := syncclient.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
|
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).Times(2)
|
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).Times(2)
|
||||||
|
@ -139,7 +139,7 @@ func TestSyncHandler_HandleHeadUpdate(t *testing.T) {
|
||||||
SnapshotPath: []string{"h1"},
|
SnapshotPath: []string{"h1"},
|
||||||
}
|
}
|
||||||
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
objectMsg, _ := syncclient.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
|
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
|
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
|
||||||
|
@ -172,7 +172,7 @@ func TestSyncHandler_HandleHeadUpdate(t *testing.T) {
|
||||||
SnapshotPath: []string{"h1"},
|
SnapshotPath: []string{"h1"},
|
||||||
}
|
}
|
||||||
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
objectMsg, _ := syncclient.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
|
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h1"}).AnyTimes()
|
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h1"}).AnyTimes()
|
||||||
|
@ -193,7 +193,7 @@ func TestSyncHandler_HandleHeadUpdate(t *testing.T) {
|
||||||
SnapshotPath: []string{"h1"},
|
SnapshotPath: []string{"h1"},
|
||||||
}
|
}
|
||||||
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
objectMsg, _ := syncclient.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
|
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
|
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
|
||||||
|
@ -218,7 +218,7 @@ func TestSyncHandler_HandleHeadUpdate(t *testing.T) {
|
||||||
SnapshotPath: []string{"h1"},
|
SnapshotPath: []string{"h1"},
|
||||||
}
|
}
|
||||||
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
objectMsg, _ := syncclient.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
|
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h1"}).AnyTimes()
|
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h1"}).AnyTimes()
|
||||||
|
@ -251,7 +251,7 @@ func TestSyncHandler_HandleFullSyncRequest(t *testing.T) {
|
||||||
SnapshotPath: []string{"h1"},
|
SnapshotPath: []string{"h1"},
|
||||||
}
|
}
|
||||||
treeMsg := treechangeproto.WrapFullRequest(fullSyncRequest, chWithId)
|
treeMsg := treechangeproto.WrapFullRequest(fullSyncRequest, chWithId)
|
||||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
objectMsg, _ := syncclient.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
|
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||||
fx.objectTreeMock.EXPECT().Header().Return(nil)
|
fx.objectTreeMock.EXPECT().Header().Return(nil)
|
||||||
|
@ -284,7 +284,7 @@ func TestSyncHandler_HandleFullSyncRequest(t *testing.T) {
|
||||||
SnapshotPath: []string{"h1"},
|
SnapshotPath: []string{"h1"},
|
||||||
}
|
}
|
||||||
treeMsg := treechangeproto.WrapFullRequest(fullSyncRequest, chWithId)
|
treeMsg := treechangeproto.WrapFullRequest(fullSyncRequest, chWithId)
|
||||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
objectMsg, _ := syncclient.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
|
|
||||||
fx.objectTreeMock.EXPECT().
|
fx.objectTreeMock.EXPECT().
|
||||||
Id().AnyTimes().Return(treeId)
|
Id().AnyTimes().Return(treeId)
|
||||||
|
@ -313,7 +313,7 @@ func TestSyncHandler_HandleFullSyncRequest(t *testing.T) {
|
||||||
SnapshotPath: []string{"h1"},
|
SnapshotPath: []string{"h1"},
|
||||||
}
|
}
|
||||||
treeMsg := treechangeproto.WrapFullRequest(fullSyncRequest, chWithId)
|
treeMsg := treechangeproto.WrapFullRequest(fullSyncRequest, chWithId)
|
||||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
objectMsg, _ := syncclient.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
objectMsg.RequestId = replyId
|
objectMsg.RequestId = replyId
|
||||||
|
|
||||||
fx.objectTreeMock.EXPECT().
|
fx.objectTreeMock.EXPECT().
|
||||||
|
@ -340,7 +340,7 @@ func TestSyncHandler_HandleFullSyncRequest(t *testing.T) {
|
||||||
SnapshotPath: []string{"h1"},
|
SnapshotPath: []string{"h1"},
|
||||||
}
|
}
|
||||||
treeMsg := treechangeproto.WrapFullRequest(fullSyncRequest, chWithId)
|
treeMsg := treechangeproto.WrapFullRequest(fullSyncRequest, chWithId)
|
||||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
objectMsg, _ := syncclient.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
|
|
||||||
fx.objectTreeMock.EXPECT().
|
fx.objectTreeMock.EXPECT().
|
||||||
Id().AnyTimes().Return(treeId)
|
Id().AnyTimes().Return(treeId)
|
||||||
|
@ -381,7 +381,7 @@ func TestSyncHandler_HandleFullSyncResponse(t *testing.T) {
|
||||||
SnapshotPath: []string{"h1"},
|
SnapshotPath: []string{"h1"},
|
||||||
}
|
}
|
||||||
treeMsg := treechangeproto.WrapFullResponse(fullSyncResponse, chWithId)
|
treeMsg := treechangeproto.WrapFullResponse(fullSyncResponse, chWithId)
|
||||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, replyId)
|
objectMsg, _ := syncclient.MarshallTreeMessage(treeMsg, "spaceId", treeId, replyId)
|
||||||
|
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||||
fx.objectTreeMock.EXPECT().
|
fx.objectTreeMock.EXPECT().
|
||||||
|
@ -414,7 +414,7 @@ func TestSyncHandler_HandleFullSyncResponse(t *testing.T) {
|
||||||
SnapshotPath: []string{"h1"},
|
SnapshotPath: []string{"h1"},
|
||||||
}
|
}
|
||||||
treeMsg := treechangeproto.WrapFullResponse(fullSyncResponse, chWithId)
|
treeMsg := treechangeproto.WrapFullResponse(fullSyncResponse, chWithId)
|
||||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, replyId)
|
objectMsg, _ := syncclient.MarshallTreeMessage(treeMsg, "spaceId", treeId, replyId)
|
||||||
|
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||||
fx.objectTreeMock.EXPECT().
|
fx.objectTreeMock.EXPECT().
|
||||||
|
|
|
@ -47,7 +47,7 @@ func (t treeRemoteGetter) getPeers(ctx context.Context) (peerIds []string, err e
|
||||||
|
|
||||||
func (t treeRemoteGetter) treeRequest(ctx context.Context, peerId string) (msg *treechangeproto.TreeSyncMessage, err error) {
|
func (t treeRemoteGetter) treeRequest(ctx context.Context, peerId string) (msg *treechangeproto.TreeSyncMessage, err error) {
|
||||||
newTreeRequest := t.deps.SyncClient.CreateNewTreeRequest()
|
newTreeRequest := t.deps.SyncClient.CreateNewTreeRequest()
|
||||||
resp, err := t.deps.SyncClient.SendSync(ctx, peerId, t.treeId, newTreeRequest)
|
resp, err := t.deps.SyncClient.SendRequest(ctx, peerId, t.treeId, newTreeRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync"
|
"github.com/anyproto/any-sync/commonspace/objectsync/syncclient"
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||||
|
@ -89,14 +89,14 @@ type testSyncHandler struct {
|
||||||
peerId string
|
peerId string
|
||||||
aclList list.AclList
|
aclList list.AclList
|
||||||
log *messageLog
|
log *messageLog
|
||||||
syncClient objectsync.SyncClient
|
syncClient syncclient.SyncClient
|
||||||
builder objecttree.BuildObjectTreeFunc
|
builder objecttree.BuildObjectTreeFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
// createSyncHandler creates a sync handler when a tree is already created
|
// createSyncHandler creates a sync handler when a tree is already created
|
||||||
func createSyncHandler(peerId, spaceId string, objTree objecttree.ObjectTree, log *messageLog) *testSyncHandler {
|
func createSyncHandler(peerId, spaceId string, objTree objecttree.ObjectTree, log *messageLog) *testSyncHandler {
|
||||||
factory := objectsync.NewRequestFactory()
|
factory := syncclient.NewRequestFactory()
|
||||||
syncClient := objectsync.NewSyncClient(spaceId, newTestMessagePool(peerId, log), factory)
|
syncClient := syncclient.NewSyncClient(spaceId, newTestMessagePool(peerId, log), factory)
|
||||||
netTree := &broadcastTree{
|
netTree := &broadcastTree{
|
||||||
ObjectTree: objTree,
|
ObjectTree: objTree,
|
||||||
SyncClient: syncClient,
|
SyncClient: syncClient,
|
||||||
|
@ -107,8 +107,8 @@ func createSyncHandler(peerId, spaceId string, objTree objecttree.ObjectTree, lo
|
||||||
|
|
||||||
// createEmptySyncHandler creates a sync handler when the tree will be provided later (this emulates the situation when we have no tree)
|
// createEmptySyncHandler creates a sync handler when the tree will be provided later (this emulates the situation when we have no tree)
|
||||||
func createEmptySyncHandler(peerId, spaceId string, builder objecttree.BuildObjectTreeFunc, aclList list.AclList, log *messageLog) *testSyncHandler {
|
func createEmptySyncHandler(peerId, spaceId string, builder objecttree.BuildObjectTreeFunc, aclList list.AclList, log *messageLog) *testSyncHandler {
|
||||||
factory := objectsync.NewRequestFactory()
|
factory := syncclient.NewRequestFactory()
|
||||||
syncClient := objectsync.NewSyncClient(spaceId, newTestMessagePool(peerId, log), factory)
|
syncClient := syncclient.NewSyncClient(spaceId, newTestMessagePool(peerId, log), factory)
|
||||||
|
|
||||||
batcher := mb.New[protocolMsg](0)
|
batcher := mb.New[protocolMsg](0)
|
||||||
return &testSyncHandler{
|
return &testSyncHandler{
|
||||||
|
@ -140,9 +140,9 @@ func (h *testSyncHandler) HandleMessage(ctx context.Context, senderId string, re
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if unmarshalled.Content.GetFullSyncResponse() == nil {
|
if unmarshalled.Content.GetFullSyncResponse() == nil {
|
||||||
newTreeRequest := objectsync.NewRequestFactory().CreateNewTreeRequest()
|
newTreeRequest := syncclient.NewRequestFactory().CreateNewTreeRequest()
|
||||||
var objMsg *spacesyncproto.ObjectSyncMessage
|
var objMsg *spacesyncproto.ObjectSyncMessage
|
||||||
objMsg, err = objectsync.MarshallTreeMessage(newTreeRequest, request.SpaceId, request.ObjectId, "")
|
objMsg, err = syncclient.MarshallTreeMessage(newTreeRequest, request.SpaceId, request.ObjectId, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -167,8 +167,8 @@ func (h *testSyncHandler) HandleMessage(ctx context.Context, senderId string, re
|
||||||
}
|
}
|
||||||
h.SyncHandler = newSyncTreeHandler(request.SpaceId, netTree, h.syncClient, syncstatus.NewNoOpSyncStatus())
|
h.SyncHandler = newSyncTreeHandler(request.SpaceId, netTree, h.syncClient, syncstatus.NewNoOpSyncStatus())
|
||||||
var objMsg *spacesyncproto.ObjectSyncMessage
|
var objMsg *spacesyncproto.ObjectSyncMessage
|
||||||
newTreeRequest := objectsync.NewRequestFactory().CreateHeadUpdate(netTree, res.Added)
|
newTreeRequest := syncclient.NewRequestFactory().CreateHeadUpdate(netTree, res.Added)
|
||||||
objMsg, err = objectsync.MarshallTreeMessage(newTreeRequest, request.SpaceId, request.ObjectId, "")
|
objMsg, err = syncclient.MarshallTreeMessage(newTreeRequest, request.SpaceId, request.ObjectId, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -278,7 +278,7 @@ func (m *testMessagePool) SendSync(ctx context.Context, peerId string, message *
|
||||||
// it is a simplified version of SyncTree which is easier to use in the test environment
|
// it is a simplified version of SyncTree which is easier to use in the test environment
|
||||||
type broadcastTree struct {
|
type broadcastTree struct {
|
||||||
objecttree.ObjectTree
|
objecttree.ObjectTree
|
||||||
objectsync.SyncClient
|
syncclient.SyncClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *broadcastTree) AddRawChanges(ctx context.Context, changes objecttree.RawChangesPayload) (objecttree.AddResult, error) {
|
func (b *broadcastTree) AddRawChanges(ctx context.Context, changes objecttree.RawChangesPayload) (objecttree.AddResult, error) {
|
||||||
|
|
|
@ -1,142 +0,0 @@
|
||||||
package objectsync
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LastUsage interface {
|
|
||||||
LastUsage() time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// MessagePool can be made generic to work with different streams
|
|
||||||
type MessagePool interface {
|
|
||||||
LastUsage
|
|
||||||
synchandler.SyncHandler
|
|
||||||
peermanager.PeerManager
|
|
||||||
SendSync(ctx context.Context, peerId string, message *spacesyncproto.ObjectSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type MessageHandler func(ctx context.Context, senderId string, message *spacesyncproto.ObjectSyncMessage) (err error)
|
|
||||||
|
|
||||||
type responseWaiter struct {
|
|
||||||
ch chan *spacesyncproto.ObjectSyncMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
type messagePool struct {
|
|
||||||
sync.Mutex
|
|
||||||
peermanager.PeerManager
|
|
||||||
messageHandler MessageHandler
|
|
||||||
waiters map[string]responseWaiter
|
|
||||||
waitersMx sync.Mutex
|
|
||||||
counter atomic.Uint64
|
|
||||||
lastUsage atomic.Int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMessagePool(peerManager peermanager.PeerManager, messageHandler MessageHandler) MessagePool {
|
|
||||||
s := &messagePool{
|
|
||||||
PeerManager: peerManager,
|
|
||||||
messageHandler: messageHandler,
|
|
||||||
waiters: make(map[string]responseWaiter),
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *messagePool) SendSync(ctx context.Context, peerId string, msg *spacesyncproto.ObjectSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) {
|
|
||||||
s.updateLastUsage()
|
|
||||||
if _, ok := ctx.Deadline(); !ok {
|
|
||||||
var cancel context.CancelFunc
|
|
||||||
ctx, cancel = context.WithTimeout(ctx, time.Minute)
|
|
||||||
defer cancel()
|
|
||||||
}
|
|
||||||
newCounter := s.counter.Add(1)
|
|
||||||
msg.RequestId = genReplyKey(peerId, msg.ObjectId, newCounter)
|
|
||||||
log.InfoCtx(ctx, "mpool sendSync", zap.String("requestId", msg.RequestId))
|
|
||||||
s.waitersMx.Lock()
|
|
||||||
waiter := responseWaiter{
|
|
||||||
ch: make(chan *spacesyncproto.ObjectSyncMessage, 1),
|
|
||||||
}
|
|
||||||
s.waiters[msg.RequestId] = waiter
|
|
||||||
s.waitersMx.Unlock()
|
|
||||||
|
|
||||||
err = s.SendPeer(ctx, peerId, msg)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
s.waitersMx.Lock()
|
|
||||||
delete(s.waiters, msg.RequestId)
|
|
||||||
s.waitersMx.Unlock()
|
|
||||||
|
|
||||||
log.With(zap.String("requestId", msg.RequestId)).DebugCtx(ctx, "time elapsed when waiting")
|
|
||||||
err = fmt.Errorf("sendSync context error: %v", ctx.Err())
|
|
||||||
case reply = <-waiter.ch:
|
|
||||||
// success
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *messagePool) SendPeer(ctx context.Context, peerId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
|
||||||
s.updateLastUsage()
|
|
||||||
return s.PeerManager.SendPeer(ctx, peerId, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *messagePool) Broadcast(ctx context.Context, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
|
||||||
s.updateLastUsage()
|
|
||||||
return s.PeerManager.Broadcast(ctx, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *messagePool) HandleMessage(ctx context.Context, senderId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
|
||||||
s.updateLastUsage()
|
|
||||||
if msg.ReplyId != "" {
|
|
||||||
log.InfoCtx(ctx, "mpool receive reply", zap.String("replyId", msg.ReplyId))
|
|
||||||
// we got reply, send it to waiter
|
|
||||||
if s.stopWaiter(msg) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.WarnCtx(ctx, "reply id does not exist", zap.String("replyId", msg.ReplyId))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return s.messageHandler(ctx, senderId, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *messagePool) LastUsage() time.Time {
|
|
||||||
return time.Unix(s.lastUsage.Load(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *messagePool) updateLastUsage() {
|
|
||||||
s.lastUsage.Store(time.Now().Unix())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *messagePool) stopWaiter(msg *spacesyncproto.ObjectSyncMessage) bool {
|
|
||||||
s.waitersMx.Lock()
|
|
||||||
waiter, exists := s.waiters[msg.ReplyId]
|
|
||||||
if exists {
|
|
||||||
delete(s.waiters, msg.ReplyId)
|
|
||||||
s.waitersMx.Unlock()
|
|
||||||
waiter.ch <- msg
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
s.waitersMx.Unlock()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func genReplyKey(peerId, treeId string, counter uint64) string {
|
|
||||||
b := &strings.Builder{}
|
|
||||||
b.WriteString(peerId)
|
|
||||||
b.WriteString(".")
|
|
||||||
b.WriteString(treeId)
|
|
||||||
b.WriteString(".")
|
|
||||||
b.WriteString(strconv.FormatUint(counter, 36))
|
|
||||||
return b.String()
|
|
||||||
}
|
|
|
@ -4,15 +4,19 @@ package objectsync
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anyproto/any-sync/app"
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/objectsync/syncclient"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/spacestate"
|
||||||
|
"github.com/anyproto/any-sync/metric"
|
||||||
|
"github.com/anyproto/any-sync/net/peer"
|
||||||
|
"github.com/anyproto/any-sync/util/multiqueue"
|
||||||
|
"github.com/cheggaaa/mb/v3"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
"github.com/anyproto/any-sync/app/logger"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/syncobjectgetter"
|
"github.com/anyproto/any-sync/commonspace/object/syncobjectgetter"
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
"github.com/anyproto/any-sync/nodeconf"
|
"github.com/anyproto/any-sync/nodeconf"
|
||||||
|
@ -20,63 +24,127 @@ import (
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
var log = logger.NewNamed("common.commonspace.objectsync")
|
const CName = "common.commonspace.objectsync"
|
||||||
|
|
||||||
|
var log = logger.NewNamed(CName)
|
||||||
|
|
||||||
type ObjectSync interface {
|
type ObjectSync interface {
|
||||||
LastUsage
|
app.ComponentRunnable
|
||||||
synchandler.SyncHandler
|
}
|
||||||
SyncClient() SyncClient
|
|
||||||
|
|
||||||
Close() (err error)
|
type HandleMessage struct {
|
||||||
|
Id uint64
|
||||||
|
ReceiveTime time.Time
|
||||||
|
StartHandlingTime time.Time
|
||||||
|
Deadline time.Time
|
||||||
|
SenderId string
|
||||||
|
Message *spacesyncproto.ObjectSyncMessage
|
||||||
|
PeerCtx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m HandleMessage) LogFields(fields ...zap.Field) []zap.Field {
|
||||||
|
return append(fields,
|
||||||
|
metric.SpaceId(m.Message.SpaceId),
|
||||||
|
metric.ObjectId(m.Message.ObjectId),
|
||||||
|
metric.QueueDur(m.StartHandlingTime.Sub(m.ReceiveTime)),
|
||||||
|
metric.TotalDur(time.Since(m.ReceiveTime)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type objectSync struct {
|
type objectSync struct {
|
||||||
spaceId string
|
spaceId string
|
||||||
|
|
||||||
messagePool MessagePool
|
syncClient syncclient.SyncClient
|
||||||
syncClient SyncClient
|
|
||||||
objectGetter syncobjectgetter.SyncObjectGetter
|
objectGetter syncobjectgetter.SyncObjectGetter
|
||||||
configuration nodeconf.NodeConf
|
configuration nodeconf.NodeConf
|
||||||
spaceStorage spacestorage.SpaceStorage
|
spaceStorage spacestorage.SpaceStorage
|
||||||
|
metric metric.Metric
|
||||||
|
|
||||||
syncCtx context.Context
|
|
||||||
cancelSync context.CancelFunc
|
|
||||||
spaceIsDeleted *atomic.Bool
|
spaceIsDeleted *atomic.Bool
|
||||||
|
handleQueue multiqueue.MultiQueue[HandleMessage]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewObjectSync(
|
func (s *objectSync) Init(a *app.App) (err error) {
|
||||||
spaceId string,
|
s.syncClient = a.MustComponent(syncclient.CName).(syncclient.SyncClient)
|
||||||
spaceIsDeleted *atomic.Bool,
|
s.objectGetter = a.MustComponent(treemanager.CName).(treemanager.TreeManager).(syncobjectgetter.SyncObjectGetter)
|
||||||
configuration nodeconf.NodeConf,
|
s.configuration = a.MustComponent(nodeconf.CName).(nodeconf.NodeConf)
|
||||||
peerManager peermanager.PeerManager,
|
sharedData := a.MustComponent(spacestate.CName).(*spacestate.SpaceState)
|
||||||
objectGetter syncobjectgetter.SyncObjectGetter,
|
s.metric = a.MustComponent(metric.CName).(metric.Metric)
|
||||||
storage spacestorage.SpaceStorage) ObjectSync {
|
s.spaceIsDeleted = sharedData.SpaceIsDeleted
|
||||||
syncCtx, cancel := context.WithCancel(context.Background())
|
s.spaceId = sharedData.SpaceId
|
||||||
os := &objectSync{
|
s.handleQueue = multiqueue.New[HandleMessage](s.processHandleMessage, 100)
|
||||||
objectGetter: objectGetter,
|
return nil
|
||||||
spaceStorage: storage,
|
}
|
||||||
spaceId: spaceId,
|
|
||||||
syncCtx: syncCtx,
|
func (s *objectSync) Name() (name string) {
|
||||||
cancelSync: cancel,
|
return CName
|
||||||
spaceIsDeleted: spaceIsDeleted,
|
}
|
||||||
configuration: configuration,
|
|
||||||
|
func (s *objectSync) Run(ctx context.Context) (err error) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *objectSync) Close(ctx context.Context) (err error) {
|
||||||
|
return s.handleQueue.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewObjectSync() ObjectSync {
|
||||||
|
return &objectSync{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *objectSync) HandleMessage(ctx context.Context, hm HandleMessage) (err error) {
|
||||||
|
threadId := hm.Message.ObjectId
|
||||||
|
hm.ReceiveTime = time.Now()
|
||||||
|
if hm.Message.ReplyId != "" {
|
||||||
|
threadId += hm.Message.ReplyId
|
||||||
|
defer func() {
|
||||||
|
_ = s.handleQueue.CloseThread(threadId)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
if hm.PeerCtx == nil {
|
||||||
|
hm.PeerCtx = ctx
|
||||||
|
}
|
||||||
|
err = s.handleQueue.Add(ctx, threadId, hm)
|
||||||
|
if err == mb.ErrOverflowed {
|
||||||
|
log.InfoCtx(ctx, "queue overflowed", zap.String("spaceId", s.spaceId), zap.String("objectId", threadId))
|
||||||
|
// skip overflowed error
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
os.messagePool = newMessagePool(peerManager, os.handleMessage)
|
|
||||||
os.syncClient = NewSyncClient(spaceId, os.messagePool, NewRequestFactory())
|
|
||||||
return os
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *objectSync) Close() (err error) {
|
|
||||||
s.cancelSync()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *objectSync) LastUsage() time.Time {
|
func (s *objectSync) processHandleMessage(msg HandleMessage) {
|
||||||
return s.messagePool.LastUsage()
|
var err error
|
||||||
}
|
msg.StartHandlingTime = time.Now()
|
||||||
|
ctx := peer.CtxWithPeerId(context.Background(), msg.SenderId)
|
||||||
|
ctx = logger.CtxWithFields(ctx, zap.Uint64("msgId", msg.Id), zap.String("senderId", msg.SenderId))
|
||||||
|
defer func() {
|
||||||
|
if s.metric == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.metric.RequestLog(msg.PeerCtx, "space.streamOp", msg.LogFields(
|
||||||
|
zap.Error(err),
|
||||||
|
)...)
|
||||||
|
}()
|
||||||
|
|
||||||
func (s *objectSync) HandleMessage(ctx context.Context, senderId string, message *spacesyncproto.ObjectSyncMessage) (err error) {
|
if !msg.Deadline.IsZero() {
|
||||||
return s.messagePool.HandleMessage(ctx, senderId, message)
|
now := time.Now()
|
||||||
|
if now.After(msg.Deadline) {
|
||||||
|
log.InfoCtx(ctx, "skip message: deadline exceed")
|
||||||
|
err = context.DeadlineExceeded
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
ctx, cancel = context.WithDeadline(ctx, msg.Deadline)
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
if err = s.handleMessage(ctx, msg.SenderId, msg.Message); err != nil {
|
||||||
|
if msg.Message.ObjectId != "" {
|
||||||
|
// cleanup thread on error
|
||||||
|
_ = s.handleQueue.CloseThread(msg.Message.ObjectId)
|
||||||
|
}
|
||||||
|
log.InfoCtx(ctx, "handleMessage error", zap.Error(err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *objectSync) handleMessage(ctx context.Context, senderId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
func (s *objectSync) handleMessage(ctx context.Context, senderId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||||
|
@ -88,70 +156,16 @@ func (s *objectSync) handleMessage(ctx context.Context, senderId string, msg *sp
|
||||||
log = log.With(zap.Bool("isDeleted", true))
|
log = log.With(zap.Bool("isDeleted", true))
|
||||||
// preventing sync with other clients if they are not just syncing the settings tree
|
// preventing sync with other clients if they are not just syncing the settings tree
|
||||||
if !slices.Contains(s.configuration.NodeIds(s.spaceId), senderId) && msg.ObjectId != s.spaceStorage.SpaceSettingsId() {
|
if !slices.Contains(s.configuration.NodeIds(s.spaceId), senderId) && msg.ObjectId != s.spaceStorage.SpaceSettingsId() {
|
||||||
s.unmarshallSendError(ctx, msg, spacesyncproto.ErrUnexpected, senderId, msg.ObjectId)
|
|
||||||
return fmt.Errorf("can't perform operation with object, space is deleted")
|
return fmt.Errorf("can't perform operation with object, space is deleted")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.DebugCtx(ctx, "handling message")
|
|
||||||
hasTree, err := s.spaceStorage.HasTree(msg.ObjectId)
|
|
||||||
if err != nil {
|
|
||||||
s.unmarshallSendError(ctx, msg, spacesyncproto.ErrUnexpected, senderId, msg.ObjectId)
|
|
||||||
return fmt.Errorf("falied to execute get operation on storage has tree: %w", err)
|
|
||||||
}
|
|
||||||
// in this case we will try to get it from remote, unless the sender also sent us the same request :-)
|
|
||||||
if !hasTree {
|
|
||||||
treeMsg := &treechangeproto.TreeSyncMessage{}
|
|
||||||
err = proto.Unmarshal(msg.Payload, treeMsg)
|
|
||||||
if err != nil {
|
|
||||||
s.sendError(ctx, nil, spacesyncproto.ErrUnexpected, senderId, msg.ObjectId, msg.RequestId)
|
|
||||||
return fmt.Errorf("failed to unmarshall tree sync message: %w", err)
|
|
||||||
}
|
|
||||||
// this means that we don't have the tree locally and therefore can't return it
|
|
||||||
if s.isEmptyFullSyncRequest(treeMsg) {
|
|
||||||
err = treechangeproto.ErrGetTree
|
|
||||||
s.sendError(ctx, nil, treechangeproto.ErrGetTree, senderId, msg.ObjectId, msg.RequestId)
|
|
||||||
return fmt.Errorf("failed to get tree from storage on full sync: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
obj, err := s.objectGetter.GetObject(ctx, msg.ObjectId)
|
obj, err := s.objectGetter.GetObject(ctx, msg.ObjectId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: write tests for object sync https://linear.app/anytype/issue/GO-1299/write-tests-for-commonspaceobjectsync
|
|
||||||
s.unmarshallSendError(ctx, msg, spacesyncproto.ErrUnexpected, senderId, msg.ObjectId)
|
|
||||||
return fmt.Errorf("failed to get object from cache: %w", err)
|
return fmt.Errorf("failed to get object from cache: %w", err)
|
||||||
}
|
}
|
||||||
// TODO: unmarshall earlier
|
|
||||||
err = obj.HandleMessage(ctx, senderId, msg)
|
err = obj.HandleMessage(ctx, senderId, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.unmarshallSendError(ctx, msg, spacesyncproto.ErrUnexpected, senderId, msg.ObjectId)
|
|
||||||
return fmt.Errorf("failed to handle message: %w", err)
|
return fmt.Errorf("failed to handle message: %w", err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *objectSync) SyncClient() SyncClient {
|
|
||||||
return s.syncClient
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *objectSync) unmarshallSendError(ctx context.Context, msg *spacesyncproto.ObjectSyncMessage, respErr error, senderId, objectId string) {
|
|
||||||
unmarshalled := &treechangeproto.TreeSyncMessage{}
|
|
||||||
err := proto.Unmarshal(msg.Payload, unmarshalled)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.sendError(ctx, unmarshalled.RootChange, respErr, senderId, objectId, msg.RequestId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *objectSync) sendError(ctx context.Context, root *treechangeproto.RawTreeChangeWithId, respErr error, senderId, objectId, replyId string) {
|
|
||||||
// we don't send errors if have no reply id, this can lead to bugs and also nobody needs this error
|
|
||||||
if replyId == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resp := treechangeproto.WrapError(respErr, root)
|
|
||||||
if err := s.syncClient.SendWithReply(ctx, senderId, objectId, resp, replyId); err != nil {
|
|
||||||
log.InfoCtx(ctx, "failed to send error to client")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *objectSync) isEmptyFullSyncRequest(msg *treechangeproto.TreeSyncMessage) bool {
|
|
||||||
return msg.GetContent().GetFullSyncRequest() != nil && len(msg.GetContent().GetFullSyncRequest().GetHeads()) == 0
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
package objectsync
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SyncClient interface {
|
|
||||||
RequestFactory
|
|
||||||
Broadcast(ctx context.Context, msg *treechangeproto.TreeSyncMessage)
|
|
||||||
SendWithReply(ctx context.Context, peerId, objectId string, msg *treechangeproto.TreeSyncMessage, replyId string) (err error)
|
|
||||||
SendSync(ctx context.Context, peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error)
|
|
||||||
MessagePool() MessagePool
|
|
||||||
}
|
|
||||||
|
|
||||||
type syncClient struct {
|
|
||||||
RequestFactory
|
|
||||||
spaceId string
|
|
||||||
messagePool MessagePool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSyncClient(
|
|
||||||
spaceId string,
|
|
||||||
messagePool MessagePool,
|
|
||||||
factory RequestFactory) SyncClient {
|
|
||||||
return &syncClient{
|
|
||||||
messagePool: messagePool,
|
|
||||||
RequestFactory: factory,
|
|
||||||
spaceId: spaceId,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncClient) Broadcast(ctx context.Context, msg *treechangeproto.TreeSyncMessage) {
|
|
||||||
objMsg, err := MarshallTreeMessage(msg, s.spaceId, msg.RootChange.Id, "")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = s.messagePool.Broadcast(ctx, objMsg)
|
|
||||||
if err != nil {
|
|
||||||
log.DebugCtx(ctx, "broadcast error", zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncClient) SendSync(ctx context.Context, peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) {
|
|
||||||
objMsg, err := MarshallTreeMessage(msg, s.spaceId, objectId, "")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return s.messagePool.SendSync(ctx, peerId, objMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncClient) SendWithReply(ctx context.Context, peerId, objectId string, msg *treechangeproto.TreeSyncMessage, replyId string) (err error) {
|
|
||||||
objMsg, err := MarshallTreeMessage(msg, s.spaceId, objectId, replyId)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return s.messagePool.SendPeer(ctx, peerId, objMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncClient) MessagePool() MessagePool {
|
|
||||||
return s.messagePool
|
|
||||||
}
|
|
||||||
|
|
||||||
func MarshallTreeMessage(message *treechangeproto.TreeSyncMessage, spaceId, objectId, replyId string) (objMsg *spacesyncproto.ObjectSyncMessage, err error) {
|
|
||||||
payload, err := message.Marshal()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
objMsg = &spacesyncproto.ObjectSyncMessage{
|
|
||||||
ReplyId: replyId,
|
|
||||||
Payload: payload,
|
|
||||||
ObjectId: objectId,
|
|
||||||
SpaceId: spaceId,
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
package objectsync
|
package syncclient
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
98
commonspace/objectsync/syncclient/syncclient.go
Normal file
98
commonspace/objectsync/syncclient/syncclient.go
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
package syncclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/anyproto/any-sync/app"
|
||||||
|
"github.com/anyproto/any-sync/app/logger"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/requestsender"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/spacestate"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/streamsender"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CName = "common.objectsync.syncclient"
|
||||||
|
|
||||||
|
var log = logger.NewNamed(CName)
|
||||||
|
|
||||||
|
type SyncClient interface {
|
||||||
|
app.Component
|
||||||
|
RequestFactory
|
||||||
|
Broadcast(msg *treechangeproto.TreeSyncMessage)
|
||||||
|
SendUpdate(peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (err error)
|
||||||
|
QueueRequest(peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (err error)
|
||||||
|
SendRequest(ctx context.Context, peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type syncClient struct {
|
||||||
|
RequestFactory
|
||||||
|
spaceId string
|
||||||
|
requestSender requestsender.RequestSender
|
||||||
|
streamSender streamsender.StreamSender
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSyncClient() SyncClient {
|
||||||
|
return &syncClient{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncClient) Init(a *app.App) (err error) {
|
||||||
|
sharedState := a.MustComponent(spacestate.CName).(*spacestate.SpaceState)
|
||||||
|
s.spaceId = sharedState.SpaceId
|
||||||
|
s.requestSender = a.MustComponent(requestsender.CName).(requestsender.RequestSender)
|
||||||
|
s.streamSender = a.MustComponent(streamsender.CName).(streamsender.StreamSender)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncClient) Name() (name string) {
|
||||||
|
return CName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncClient) Broadcast(msg *treechangeproto.TreeSyncMessage) {
|
||||||
|
objMsg, err := MarshallTreeMessage(msg, s.spaceId, msg.RootChange.Id, "")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = s.streamSender.Broadcast(objMsg)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("broadcast error", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncClient) SendUpdate(peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (err error) {
|
||||||
|
objMsg, err := MarshallTreeMessage(msg, s.spaceId, objectId, "")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return s.streamSender.SendPeer(peerId, objMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncClient) SendRequest(ctx context.Context, peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) {
|
||||||
|
objMsg, err := MarshallTreeMessage(msg, s.spaceId, objectId, "")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return s.requestSender.SendRequest(ctx, peerId, objMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncClient) QueueRequest(peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (err error) {
|
||||||
|
objMsg, err := MarshallTreeMessage(msg, s.spaceId, objectId, "")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return s.requestSender.QueueRequest(peerId, objMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MarshallTreeMessage(message *treechangeproto.TreeSyncMessage, spaceId, objectId, replyId string) (objMsg *spacesyncproto.ObjectSyncMessage, err error) {
|
||||||
|
payload, err := message.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
objMsg = &spacesyncproto.ObjectSyncMessage{
|
||||||
|
ReplyId: replyId,
|
||||||
|
Payload: payload,
|
||||||
|
ObjectId: objectId,
|
||||||
|
SpaceId: spaceId,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
191
commonspace/objecttreebuilder/treebuilder.go
Normal file
191
commonspace/objecttreebuilder/treebuilder.go
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
package objecttreebuilder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"github.com/anyproto/any-sync/app"
|
||||||
|
"github.com/anyproto/any-sync/app/logger"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/headsync"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/tree/synctree"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/objectsync/syncclient"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/peermanager"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/spacestate"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||||
|
"github.com/anyproto/any-sync/nodeconf"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BuildTreeOpts struct {
|
||||||
|
Listener updatelistener.UpdateListener
|
||||||
|
WaitTreeRemoteSync bool
|
||||||
|
TreeBuilder objecttree.BuildObjectTreeFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
const CName = "common.commonspace.objecttreebuilder"
|
||||||
|
|
||||||
|
var log = logger.NewNamed(CName)
|
||||||
|
var ErrSpaceClosed = errors.New("space is closed")
|
||||||
|
|
||||||
|
type HistoryTreeOpts struct {
|
||||||
|
BeforeId string
|
||||||
|
Include bool
|
||||||
|
BuildFullTree bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type TreeBuilder interface {
|
||||||
|
app.Component
|
||||||
|
BuildTree(ctx context.Context, id string, opts BuildTreeOpts) (t objecttree.ObjectTree, err error)
|
||||||
|
BuildHistoryTree(ctx context.Context, id string, opts HistoryTreeOpts) (t objecttree.HistoryTree, err error)
|
||||||
|
CreateTree(ctx context.Context, payload objecttree.ObjectTreeCreatePayload) (res treestorage.TreeStorageCreatePayload, err error)
|
||||||
|
PutTree(ctx context.Context, payload treestorage.TreeStorageCreatePayload, listener updatelistener.UpdateListener) (t objecttree.ObjectTree, err error)
|
||||||
|
SetOnCloseHandler(handler func(id string))
|
||||||
|
}
|
||||||
|
|
||||||
|
type treeBuilder struct {
|
||||||
|
syncClient syncclient.SyncClient
|
||||||
|
configuration nodeconf.NodeConf
|
||||||
|
headsNotifiable synctree.HeadNotifiable
|
||||||
|
peerManager peermanager.PeerManager
|
||||||
|
spaceStorage spacestorage.SpaceStorage
|
||||||
|
syncStatus syncstatus.StatusUpdater
|
||||||
|
|
||||||
|
log logger.CtxLogger
|
||||||
|
builder objecttree.BuildObjectTreeFunc
|
||||||
|
spaceId string
|
||||||
|
aclList list.AclList
|
||||||
|
treesUsed *atomic.Int32
|
||||||
|
isClosed *atomic.Bool
|
||||||
|
onClose func(id string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *treeBuilder) Init(a *app.App) (err error) {
|
||||||
|
state := a.MustComponent(spacestate.CName).(*spacestate.SpaceState)
|
||||||
|
t.spaceId = state.SpaceId
|
||||||
|
t.aclList = state.AclList
|
||||||
|
t.isClosed = state.SpaceIsClosed
|
||||||
|
t.spaceStorage = state.SpaceStorage
|
||||||
|
t.treesUsed = state.TreesUsed
|
||||||
|
t.builder = state.TreeBuilderFunc
|
||||||
|
t.configuration = a.MustComponent(nodeconf.CName).(nodeconf.NodeConf)
|
||||||
|
t.headsNotifiable = a.MustComponent(headsync.CName).(headsync.HeadSync)
|
||||||
|
t.spaceStorage = state.SpaceStorage
|
||||||
|
t.syncStatus = a.MustComponent(syncstatus.CName).(syncstatus.StatusUpdater)
|
||||||
|
t.peerManager = a.MustComponent(peermanager.CName).(peermanager.PeerManager)
|
||||||
|
t.log = log.With(zap.String("spaceId", t.spaceId))
|
||||||
|
t.onClose = state.Actions.OnObjectDelete
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *treeBuilder) Name() (name string) {
|
||||||
|
return CName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *treeBuilder) SetOnCloseHandler(handler func(id string)) {
|
||||||
|
t.onClose = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *treeBuilder) BuildTree(ctx context.Context, id string, opts BuildTreeOpts) (ot objecttree.ObjectTree, err error) {
|
||||||
|
if t.isClosed.Load() {
|
||||||
|
// TODO: change to real error
|
||||||
|
err = ErrSpaceClosed
|
||||||
|
return
|
||||||
|
}
|
||||||
|
treeBuilder := opts.TreeBuilder
|
||||||
|
if treeBuilder == nil {
|
||||||
|
treeBuilder = t.builder
|
||||||
|
}
|
||||||
|
deps := synctree.BuildDeps{
|
||||||
|
SpaceId: t.spaceId,
|
||||||
|
SyncClient: t.syncClient,
|
||||||
|
Configuration: t.configuration,
|
||||||
|
HeadNotifiable: t.headsNotifiable,
|
||||||
|
Listener: opts.Listener,
|
||||||
|
AclList: t.aclList,
|
||||||
|
SpaceStorage: t.spaceStorage,
|
||||||
|
OnClose: t.onClose,
|
||||||
|
SyncStatus: t.syncStatus,
|
||||||
|
WaitTreeRemoteSync: opts.WaitTreeRemoteSync,
|
||||||
|
PeerGetter: t.peerManager,
|
||||||
|
BuildObjectTree: treeBuilder,
|
||||||
|
}
|
||||||
|
t.treesUsed.Add(1)
|
||||||
|
t.log.Debug("incrementing counter", zap.String("id", id), zap.Int32("trees", t.treesUsed.Load()))
|
||||||
|
if ot, err = synctree.BuildSyncTreeOrGetRemote(ctx, id, deps); err != nil {
|
||||||
|
t.treesUsed.Add(-1)
|
||||||
|
t.log.Debug("decrementing counter, load failed", zap.String("id", id), zap.Int32("trees", t.treesUsed.Load()), zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *treeBuilder) BuildHistoryTree(ctx context.Context, id string, opts HistoryTreeOpts) (ot objecttree.HistoryTree, err error) {
|
||||||
|
if t.isClosed.Load() {
|
||||||
|
// TODO: change to real error
|
||||||
|
err = ErrSpaceClosed
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
params := objecttree.HistoryTreeParams{
|
||||||
|
AclList: t.aclList,
|
||||||
|
BeforeId: opts.BeforeId,
|
||||||
|
IncludeBeforeId: opts.Include,
|
||||||
|
BuildFullTree: opts.BuildFullTree,
|
||||||
|
}
|
||||||
|
params.TreeStorage, err = t.spaceStorage.TreeStorage(id)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return objecttree.BuildHistoryTree(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *treeBuilder) CreateTree(ctx context.Context, payload objecttree.ObjectTreeCreatePayload) (res treestorage.TreeStorageCreatePayload, err error) {
|
||||||
|
if t.isClosed.Load() {
|
||||||
|
err = ErrSpaceClosed
|
||||||
|
return
|
||||||
|
}
|
||||||
|
root, err := objecttree.CreateObjectTreeRoot(payload, t.aclList)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res = treestorage.TreeStorageCreatePayload{
|
||||||
|
RootRawChange: root,
|
||||||
|
Changes: []*treechangeproto.RawTreeChangeWithId{root},
|
||||||
|
Heads: []string{root.Id},
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *treeBuilder) PutTree(ctx context.Context, payload treestorage.TreeStorageCreatePayload, listener updatelistener.UpdateListener) (ot objecttree.ObjectTree, err error) {
|
||||||
|
if t.isClosed.Load() {
|
||||||
|
err = ErrSpaceClosed
|
||||||
|
return
|
||||||
|
}
|
||||||
|
deps := synctree.BuildDeps{
|
||||||
|
SpaceId: t.spaceId,
|
||||||
|
SyncClient: t.syncClient,
|
||||||
|
Configuration: t.configuration,
|
||||||
|
HeadNotifiable: t.headsNotifiable,
|
||||||
|
Listener: listener,
|
||||||
|
AclList: t.aclList,
|
||||||
|
SpaceStorage: t.spaceStorage,
|
||||||
|
OnClose: t.onClose,
|
||||||
|
SyncStatus: t.syncStatus,
|
||||||
|
PeerGetter: t.peerManager,
|
||||||
|
BuildObjectTree: t.builder,
|
||||||
|
}
|
||||||
|
ot, err = synctree.PutSyncTree(ctx, payload, deps)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.treesUsed.Add(1)
|
||||||
|
t.log.Debug("incrementing counter", zap.String("id", payload.RootRawChange.Id), zap.Int32("trees", t.treesUsed.Load()))
|
||||||
|
return
|
||||||
|
}
|
45
commonspace/requestsender/requestsender.go
Normal file
45
commonspace/requestsender/requestsender.go
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
package requestsender
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/anyproto/any-sync/app"
|
||||||
|
"github.com/anyproto/any-sync/app/logger"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CName = "common.commonspace.requestsender"
|
||||||
|
|
||||||
|
var log = logger.NewNamed(CName)
|
||||||
|
|
||||||
|
type RequestSender interface {
|
||||||
|
app.ComponentRunnable
|
||||||
|
SendRequest(ctx context.Context, peerId string, msg *spacesyncproto.ObjectSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error)
|
||||||
|
QueueRequest(peerId string, msg *spacesyncproto.ObjectSyncMessage) (err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type requestSender struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *requestSender) Init(a *app.App) (err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *requestSender) Name() (name string) {
|
||||||
|
return CName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *requestSender) Run(ctx context.Context) (err error) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *requestSender) Close(ctx context.Context) (err error) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *requestSender) SendRequest(ctx context.Context, peerId string, msg *spacesyncproto.ObjectSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *requestSender) QueueRequest(peerId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -2,9 +2,9 @@ package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/deletionstate"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||||
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
@ -15,11 +15,11 @@ type Deleter interface {
|
||||||
|
|
||||||
type deleter struct {
|
type deleter struct {
|
||||||
st spacestorage.SpaceStorage
|
st spacestorage.SpaceStorage
|
||||||
state settingsstate.ObjectDeletionState
|
state deletionstate.ObjectDeletionState
|
||||||
getter treemanager.TreeManager
|
getter treemanager.TreeManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDeleter(st spacestorage.SpaceStorage, state settingsstate.ObjectDeletionState, getter treemanager.TreeManager) Deleter {
|
func newDeleter(st spacestorage.SpaceStorage, state deletionstate.ObjectDeletionState, getter treemanager.TreeManager) Deleter {
|
||||||
return &deleter{st, state, getter}
|
return &deleter{st, state, getter}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/deletionstate"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||||
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
@ -20,7 +21,7 @@ func newDeletionManager(
|
||||||
settingsId string,
|
settingsId string,
|
||||||
isResponsible bool,
|
isResponsible bool,
|
||||||
treeManager treemanager.TreeManager,
|
treeManager treemanager.TreeManager,
|
||||||
deletionState settingsstate.ObjectDeletionState,
|
deletionState deletionstate.ObjectDeletionState,
|
||||||
provider SpaceIdsProvider,
|
provider SpaceIdsProvider,
|
||||||
onSpaceDelete func()) DeletionManager {
|
onSpaceDelete func()) DeletionManager {
|
||||||
return &deletionManager{
|
return &deletionManager{
|
||||||
|
@ -35,7 +36,7 @@ func newDeletionManager(
|
||||||
}
|
}
|
||||||
|
|
||||||
type deletionManager struct {
|
type deletionManager struct {
|
||||||
deletionState settingsstate.ObjectDeletionState
|
deletionState deletionstate.ObjectDeletionState
|
||||||
provider SpaceIdsProvider
|
provider SpaceIdsProvider
|
||||||
treeManager treemanager.TreeManager
|
treeManager treemanager.TreeManager
|
||||||
spaceId string
|
spaceId string
|
||||||
|
|
|
@ -1,328 +1,88 @@
|
||||||
//go:generate mockgen -destination mock_settings/mock_settings.go github.com/anyproto/any-sync/commonspace/settings DeletionManager,Deleter,SpaceIdsProvider
|
|
||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/accountservice"
|
"github.com/anyproto/any-sync/accountservice"
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
"github.com/anyproto/any-sync/app"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/deletionstate"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/headsync"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree"
|
"github.com/anyproto/any-sync/commonspace/object/tree/synctree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
|
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||||
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
"github.com/anyproto/any-sync/commonspace/objecttreebuilder"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/spacestate"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
|
||||||
"github.com/anyproto/any-sync/nodeconf"
|
"github.com/anyproto/any-sync/nodeconf"
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var log = logger.NewNamed("common.commonspace.settings")
|
const CName = "common.commonspace.settings"
|
||||||
|
|
||||||
type SettingsObject interface {
|
type Settings interface {
|
||||||
synctree.SyncTree
|
app.ComponentRunnable
|
||||||
Init(ctx context.Context) (err error)
|
|
||||||
DeleteObject(id string) (err error)
|
|
||||||
DeleteSpace(ctx context.Context, raw *treechangeproto.RawTreeChangeWithId) (err error)
|
|
||||||
SpaceDeleteRawChange() (raw *treechangeproto.RawTreeChangeWithId, err error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
type settings struct {
|
||||||
ErrDeleteSelf = errors.New("cannot delete self")
|
account accountservice.Service
|
||||||
ErrAlreadyDeleted = errors.New("the object is already deleted")
|
treeManager treemanager.TreeManager
|
||||||
ErrObjDoesNotExist = errors.New("the object does not exist")
|
storage spacestorage.SpaceStorage
|
||||||
ErrCantDeleteSpace = errors.New("not able to delete space")
|
configuration nodeconf.NodeConf
|
||||||
)
|
deletionState deletionstate.ObjectDeletionState
|
||||||
|
headsync headsync.HeadSync
|
||||||
|
spaceActions spacestate.SpaceActions
|
||||||
|
treeBuilder objecttreebuilder.TreeBuilder
|
||||||
|
|
||||||
var (
|
settingsObject SettingsObject
|
||||||
DoSnapshot = objecttree.DoSnapshot
|
|
||||||
buildHistoryTree = func(objTree objecttree.ObjectTree) (objecttree.ReadableObjectTree, error) {
|
|
||||||
return objecttree.BuildHistoryTree(objecttree.HistoryTreeParams{
|
|
||||||
TreeStorage: objTree.Storage(),
|
|
||||||
AclList: objTree.AclList(),
|
|
||||||
BuildFullTree: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type BuildTreeFunc func(ctx context.Context, id string, listener updatelistener.UpdateListener) (t synctree.SyncTree, err error)
|
|
||||||
|
|
||||||
type Deps struct {
|
|
||||||
BuildFunc BuildTreeFunc
|
|
||||||
Account accountservice.Service
|
|
||||||
TreeManager treemanager.TreeManager
|
|
||||||
Store spacestorage.SpaceStorage
|
|
||||||
Configuration nodeconf.NodeConf
|
|
||||||
DeletionState settingsstate.ObjectDeletionState
|
|
||||||
Provider SpaceIdsProvider
|
|
||||||
OnSpaceDelete func()
|
|
||||||
// testing dependencies
|
|
||||||
builder settingsstate.StateBuilder
|
|
||||||
del Deleter
|
|
||||||
delManager DeletionManager
|
|
||||||
changeFactory settingsstate.ChangeFactory
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type settingsObject struct {
|
func (s *settings) Run(ctx context.Context) (err error) {
|
||||||
synctree.SyncTree
|
return s.settingsObject.Init(ctx)
|
||||||
account accountservice.Service
|
|
||||||
spaceId string
|
|
||||||
treeManager treemanager.TreeManager
|
|
||||||
store spacestorage.SpaceStorage
|
|
||||||
builder settingsstate.StateBuilder
|
|
||||||
buildFunc BuildTreeFunc
|
|
||||||
loop *deleteLoop
|
|
||||||
|
|
||||||
state *settingsstate.State
|
|
||||||
deletionState settingsstate.ObjectDeletionState
|
|
||||||
deletionManager DeletionManager
|
|
||||||
changeFactory settingsstate.ChangeFactory
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSettingsObject(deps Deps, spaceId string) (obj SettingsObject) {
|
func (s *settings) Close(ctx context.Context) (err error) {
|
||||||
var (
|
return s.settingsObject.Close()
|
||||||
deleter Deleter
|
|
||||||
deletionManager DeletionManager
|
|
||||||
builder settingsstate.StateBuilder
|
|
||||||
changeFactory settingsstate.ChangeFactory
|
|
||||||
)
|
|
||||||
if deps.del == nil {
|
|
||||||
deleter = newDeleter(deps.Store, deps.DeletionState, deps.TreeManager)
|
|
||||||
} else {
|
|
||||||
deleter = deps.del
|
|
||||||
}
|
|
||||||
if deps.delManager == nil {
|
|
||||||
deletionManager = newDeletionManager(
|
|
||||||
spaceId,
|
|
||||||
deps.Store.SpaceSettingsId(),
|
|
||||||
deps.Configuration.IsResponsible(spaceId),
|
|
||||||
deps.TreeManager,
|
|
||||||
deps.DeletionState,
|
|
||||||
deps.Provider,
|
|
||||||
deps.OnSpaceDelete)
|
|
||||||
} else {
|
|
||||||
deletionManager = deps.delManager
|
|
||||||
}
|
|
||||||
if deps.builder == nil {
|
|
||||||
builder = settingsstate.NewStateBuilder()
|
|
||||||
} else {
|
|
||||||
builder = deps.builder
|
|
||||||
}
|
|
||||||
if deps.changeFactory == nil {
|
|
||||||
changeFactory = settingsstate.NewChangeFactory()
|
|
||||||
} else {
|
|
||||||
changeFactory = deps.changeFactory
|
|
||||||
}
|
|
||||||
|
|
||||||
loop := newDeleteLoop(func() {
|
|
||||||
deleter.Delete()
|
|
||||||
})
|
|
||||||
deps.DeletionState.AddObserver(func(ids []string) {
|
|
||||||
loop.notify()
|
|
||||||
})
|
|
||||||
|
|
||||||
s := &settingsObject{
|
|
||||||
loop: loop,
|
|
||||||
spaceId: spaceId,
|
|
||||||
account: deps.Account,
|
|
||||||
deletionState: deps.DeletionState,
|
|
||||||
treeManager: deps.TreeManager,
|
|
||||||
store: deps.Store,
|
|
||||||
buildFunc: deps.BuildFunc,
|
|
||||||
builder: builder,
|
|
||||||
deletionManager: deletionManager,
|
|
||||||
changeFactory: changeFactory,
|
|
||||||
}
|
|
||||||
obj = s
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *settingsObject) updateIds(tr objecttree.ObjectTree) {
|
func (s *settings) Init(a *app.App) (err error) {
|
||||||
var err error
|
s.account = a.MustComponent(accountservice.CName).(accountservice.Service)
|
||||||
s.state, err = s.builder.Build(tr, s.state)
|
s.treeManager = a.MustComponent(treemanager.CName).(treemanager.TreeManager)
|
||||||
if err != nil {
|
s.headsync = a.MustComponent(headsync.CName).(headsync.HeadSync)
|
||||||
log.Error("failed to build state", zap.Error(err))
|
s.configuration = a.MustComponent(nodeconf.CName).(nodeconf.NodeConf)
|
||||||
return
|
s.deletionState = a.MustComponent(deletionstate.CName).(deletionstate.ObjectDeletionState)
|
||||||
}
|
s.treeBuilder = a.MustComponent(objecttreebuilder.CName).(objecttreebuilder.TreeBuilder)
|
||||||
log.Debug("updating object state", zap.String("deleted by", s.state.DeleterId))
|
|
||||||
if err = s.deletionManager.UpdateState(context.Background(), s.state); err != nil {
|
|
||||||
log.Error("failed to update state", zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update is called as part of UpdateListener interface
|
sharedState := a.MustComponent(spacestate.CName).(*spacestate.SpaceState)
|
||||||
func (s *settingsObject) Update(tr objecttree.ObjectTree) {
|
s.spaceActions = sharedState.Actions
|
||||||
s.updateIds(tr)
|
s.storage = sharedState.SpaceStorage
|
||||||
}
|
|
||||||
|
|
||||||
// Rebuild is called as part of UpdateListener interface (including when the object is built for the first time, e.g. on Init call)
|
deps := Deps{
|
||||||
func (s *settingsObject) Rebuild(tr objecttree.ObjectTree) {
|
BuildFunc: func(ctx context.Context, id string, listener updatelistener.UpdateListener) (t synctree.SyncTree, err error) {
|
||||||
// at initial build "s" may not contain the object tree, so it is safer to provide it from the function parameter
|
res, err := s.treeBuilder.BuildTree(ctx, id, objecttreebuilder.BuildTreeOpts{
|
||||||
s.state = nil
|
Listener: listener,
|
||||||
s.updateIds(tr)
|
WaitTreeRemoteSync: false,
|
||||||
}
|
// space settings document should not have empty data
|
||||||
|
TreeBuilder: objecttree.BuildObjectTree,
|
||||||
func (s *settingsObject) Init(ctx context.Context) (err error) {
|
})
|
||||||
settingsId := s.store.SpaceSettingsId()
|
log.Debug("building settings tree", zap.String("id", id), zap.String("spaceId", sharedState.SpaceId))
|
||||||
log.Debug("space settings id", zap.String("id", settingsId))
|
if err != nil {
|
||||||
s.SyncTree, err = s.buildFunc(ctx, settingsId, s)
|
return
|
||||||
if err != nil {
|
}
|
||||||
return
|
t = res.(synctree.SyncTree)
|
||||||
}
|
|
||||||
// TODO: remove this check when everybody updates
|
|
||||||
if err = s.checkHistoryState(ctx); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.loop.Run()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *settingsObject) checkHistoryState(ctx context.Context) (err error) {
|
|
||||||
historyTree, err := buildHistoryTree(s.SyncTree)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fullState, err := s.builder.Build(historyTree, nil)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(fullState.DeletedIds) != len(s.state.DeletedIds) {
|
|
||||||
log.WarnCtx(ctx, "state does not have all deleted ids",
|
|
||||||
zap.Int("fullstate ids", len(fullState.DeletedIds)),
|
|
||||||
zap.Int("state ids", len(fullState.DeletedIds)))
|
|
||||||
s.state = fullState
|
|
||||||
err = s.deletionManager.UpdateState(context.Background(), s.state)
|
|
||||||
if err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
},
|
||||||
|
Account: s.account,
|
||||||
|
TreeManager: s.treeManager,
|
||||||
|
Store: s.storage,
|
||||||
|
Configuration: s.configuration,
|
||||||
|
DeletionState: s.deletionState,
|
||||||
|
Provider: s.headsync,
|
||||||
|
OnSpaceDelete: s.spaceActions.OnSpaceDelete,
|
||||||
}
|
}
|
||||||
return
|
s.settingsObject = NewSettingsObject(deps, sharedState.SpaceId)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *settingsObject) Close() error {
|
func (s *settings) Name() (name string) {
|
||||||
s.loop.Close()
|
return CName
|
||||||
return s.SyncTree.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *settingsObject) DeleteSpace(ctx context.Context, raw *treechangeproto.RawTreeChangeWithId) (err error) {
|
|
||||||
s.Lock()
|
|
||||||
defer s.Unlock()
|
|
||||||
defer func() {
|
|
||||||
log.Debug("finished adding delete change", zap.Error(err))
|
|
||||||
}()
|
|
||||||
err = s.verifyDeleteSpace(raw)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
res, err := s.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
|
||||||
NewHeads: []string{raw.Id},
|
|
||||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{raw},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !slices.Contains(res.Heads, raw.Id) {
|
|
||||||
err = ErrCantDeleteSpace
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *settingsObject) SpaceDeleteRawChange() (raw *treechangeproto.RawTreeChangeWithId, err error) {
|
|
||||||
accountData := s.account.Account()
|
|
||||||
data, err := s.changeFactory.CreateSpaceDeleteChange(accountData.PeerId, s.state, false)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return s.PrepareChange(objecttree.SignableChangeContent{
|
|
||||||
Data: data,
|
|
||||||
Key: accountData.SignKey,
|
|
||||||
IsSnapshot: false,
|
|
||||||
IsEncrypted: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *settingsObject) DeleteObject(id string) (err error) {
|
|
||||||
s.Lock()
|
|
||||||
defer s.Unlock()
|
|
||||||
if s.Id() == id {
|
|
||||||
err = ErrDeleteSelf
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if s.state.Exists(id) {
|
|
||||||
err = ErrAlreadyDeleted
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
_, err = s.store.TreeStorage(id)
|
|
||||||
if err != nil {
|
|
||||||
err = ErrObjDoesNotExist
|
|
||||||
return
|
|
||||||
}
|
|
||||||
isSnapshot := DoSnapshot(s.Len())
|
|
||||||
res, err := s.changeFactory.CreateObjectDeleteChange(id, s.state, isSnapshot)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.addContent(res, isSnapshot)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *settingsObject) verifyDeleteSpace(raw *treechangeproto.RawTreeChangeWithId) (err error) {
|
|
||||||
data, err := s.UnpackChange(raw)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return verifyDeleteContent(data, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *settingsObject) addContent(data []byte, isSnapshot bool) (err error) {
|
|
||||||
accountData := s.account.Account()
|
|
||||||
res, err := s.AddContent(context.Background(), objecttree.SignableChangeContent{
|
|
||||||
Data: data,
|
|
||||||
Key: accountData.SignKey,
|
|
||||||
IsSnapshot: isSnapshot,
|
|
||||||
IsEncrypted: false,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if res.Mode == objecttree.Rebuild {
|
|
||||||
s.Rebuild(s)
|
|
||||||
} else {
|
|
||||||
s.Update(s)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func VerifyDeleteChange(raw *treechangeproto.RawTreeChangeWithId, identity crypto.PubKey, peerId string) (err error) {
|
|
||||||
changeBuilder := objecttree.NewChangeBuilder(crypto.NewKeyStorage(), nil)
|
|
||||||
res, err := changeBuilder.Unmarshall(raw, true)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !res.Identity.Equals(identity) {
|
|
||||||
return fmt.Errorf("incorrect identity")
|
|
||||||
}
|
|
||||||
return verifyDeleteContent(res.Data, peerId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyDeleteContent(data []byte, peerId string) (err error) {
|
|
||||||
content := &spacesyncproto.SettingsData{}
|
|
||||||
err = proto.Unmarshal(data, content)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(content.GetContent()) != 1 ||
|
|
||||||
content.GetContent()[0].GetSpaceDelete() == nil ||
|
|
||||||
(peerId == "" && content.GetContent()[0].GetSpaceDelete().GetDeleterPeerId() == "") ||
|
|
||||||
(peerId != "" && content.GetContent()[0].GetSpaceDelete().GetDeleterPeerId() != peerId) {
|
|
||||||
return fmt.Errorf("incorrect delete change payload")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
329
commonspace/settings/settingsobject.go
Normal file
329
commonspace/settings/settingsobject.go
Normal file
|
@ -0,0 +1,329 @@
|
||||||
|
//go:generate mockgen -destination mock_settings/mock_settings.go github.com/anyproto/any-sync/commonspace/settings DeletionManager,Deleter,SpaceIdsProvider
|
||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/deletionstate"
|
||||||
|
"github.com/anyproto/any-sync/util/crypto"
|
||||||
|
|
||||||
|
"github.com/anyproto/any-sync/accountservice"
|
||||||
|
"github.com/anyproto/any-sync/app/logger"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/tree/synctree"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
|
"github.com/anyproto/any-sync/nodeconf"
|
||||||
|
"github.com/gogo/protobuf/proto"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = logger.NewNamed("common.commonspace.settings")
|
||||||
|
|
||||||
|
type SettingsObject interface {
|
||||||
|
synctree.SyncTree
|
||||||
|
Init(ctx context.Context) (err error)
|
||||||
|
DeleteObject(id string) (err error)
|
||||||
|
DeleteSpace(ctx context.Context, raw *treechangeproto.RawTreeChangeWithId) (err error)
|
||||||
|
SpaceDeleteRawChange() (raw *treechangeproto.RawTreeChangeWithId, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrDeleteSelf = errors.New("cannot delete self")
|
||||||
|
ErrAlreadyDeleted = errors.New("the object is already deleted")
|
||||||
|
ErrObjDoesNotExist = errors.New("the object does not exist")
|
||||||
|
ErrCantDeleteSpace = errors.New("not able to delete space")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
DoSnapshot = objecttree.DoSnapshot
|
||||||
|
buildHistoryTree = func(objTree objecttree.ObjectTree) (objecttree.ReadableObjectTree, error) {
|
||||||
|
return objecttree.BuildHistoryTree(objecttree.HistoryTreeParams{
|
||||||
|
TreeStorage: objTree.Storage(),
|
||||||
|
AclList: objTree.AclList(),
|
||||||
|
BuildFullTree: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type BuildTreeFunc func(ctx context.Context, id string, listener updatelistener.UpdateListener) (t synctree.SyncTree, err error)
|
||||||
|
|
||||||
|
type Deps struct {
|
||||||
|
BuildFunc BuildTreeFunc
|
||||||
|
Account accountservice.Service
|
||||||
|
TreeManager treemanager.TreeManager
|
||||||
|
Store spacestorage.SpaceStorage
|
||||||
|
Configuration nodeconf.NodeConf
|
||||||
|
DeletionState deletionstate.ObjectDeletionState
|
||||||
|
Provider SpaceIdsProvider
|
||||||
|
OnSpaceDelete func()
|
||||||
|
// testing dependencies
|
||||||
|
builder settingsstate.StateBuilder
|
||||||
|
del Deleter
|
||||||
|
delManager DeletionManager
|
||||||
|
changeFactory settingsstate.ChangeFactory
|
||||||
|
}
|
||||||
|
|
||||||
|
type settingsObject struct {
|
||||||
|
synctree.SyncTree
|
||||||
|
account accountservice.Service
|
||||||
|
spaceId string
|
||||||
|
treeManager treemanager.TreeManager
|
||||||
|
store spacestorage.SpaceStorage
|
||||||
|
builder settingsstate.StateBuilder
|
||||||
|
buildFunc BuildTreeFunc
|
||||||
|
loop *deleteLoop
|
||||||
|
|
||||||
|
state *settingsstate.State
|
||||||
|
deletionState deletionstate.ObjectDeletionState
|
||||||
|
deletionManager DeletionManager
|
||||||
|
changeFactory settingsstate.ChangeFactory
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSettingsObject(deps Deps, spaceId string) (obj SettingsObject) {
|
||||||
|
var (
|
||||||
|
deleter Deleter
|
||||||
|
deletionManager DeletionManager
|
||||||
|
builder settingsstate.StateBuilder
|
||||||
|
changeFactory settingsstate.ChangeFactory
|
||||||
|
)
|
||||||
|
if deps.del == nil {
|
||||||
|
deleter = newDeleter(deps.Store, deps.DeletionState, deps.TreeManager)
|
||||||
|
} else {
|
||||||
|
deleter = deps.del
|
||||||
|
}
|
||||||
|
if deps.delManager == nil {
|
||||||
|
deletionManager = newDeletionManager(
|
||||||
|
spaceId,
|
||||||
|
deps.Store.SpaceSettingsId(),
|
||||||
|
deps.Configuration.IsResponsible(spaceId),
|
||||||
|
deps.TreeManager,
|
||||||
|
deps.DeletionState,
|
||||||
|
deps.Provider,
|
||||||
|
deps.OnSpaceDelete)
|
||||||
|
} else {
|
||||||
|
deletionManager = deps.delManager
|
||||||
|
}
|
||||||
|
if deps.builder == nil {
|
||||||
|
builder = settingsstate.NewStateBuilder()
|
||||||
|
} else {
|
||||||
|
builder = deps.builder
|
||||||
|
}
|
||||||
|
if deps.changeFactory == nil {
|
||||||
|
changeFactory = settingsstate.NewChangeFactory()
|
||||||
|
} else {
|
||||||
|
changeFactory = deps.changeFactory
|
||||||
|
}
|
||||||
|
|
||||||
|
loop := newDeleteLoop(func() {
|
||||||
|
deleter.Delete()
|
||||||
|
})
|
||||||
|
deps.DeletionState.AddObserver(func(ids []string) {
|
||||||
|
loop.notify()
|
||||||
|
})
|
||||||
|
|
||||||
|
s := &settingsObject{
|
||||||
|
loop: loop,
|
||||||
|
spaceId: spaceId,
|
||||||
|
account: deps.Account,
|
||||||
|
deletionState: deps.DeletionState,
|
||||||
|
treeManager: deps.TreeManager,
|
||||||
|
store: deps.Store,
|
||||||
|
buildFunc: deps.BuildFunc,
|
||||||
|
builder: builder,
|
||||||
|
deletionManager: deletionManager,
|
||||||
|
changeFactory: changeFactory,
|
||||||
|
}
|
||||||
|
obj = s
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *settingsObject) updateIds(tr objecttree.ObjectTree) {
|
||||||
|
var err error
|
||||||
|
s.state, err = s.builder.Build(tr, s.state)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("failed to build state", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debug("updating object state", zap.String("deleted by", s.state.DeleterId))
|
||||||
|
if err = s.deletionManager.UpdateState(context.Background(), s.state); err != nil {
|
||||||
|
log.Error("failed to update state", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update is called as part of UpdateListener interface
|
||||||
|
func (s *settingsObject) Update(tr objecttree.ObjectTree) {
|
||||||
|
s.updateIds(tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuild is called as part of UpdateListener interface (including when the object is built for the first time, e.g. on Init call)
|
||||||
|
func (s *settingsObject) Rebuild(tr objecttree.ObjectTree) {
|
||||||
|
// at initial build "s" may not contain the object tree, so it is safer to provide it from the function parameter
|
||||||
|
s.state = nil
|
||||||
|
s.updateIds(tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *settingsObject) Init(ctx context.Context) (err error) {
|
||||||
|
settingsId := s.store.SpaceSettingsId()
|
||||||
|
log.Debug("space settings id", zap.String("id", settingsId))
|
||||||
|
s.SyncTree, err = s.buildFunc(ctx, settingsId, s)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO: remove this check when everybody updates
|
||||||
|
if err = s.checkHistoryState(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.loop.Run()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *settingsObject) checkHistoryState(ctx context.Context) (err error) {
|
||||||
|
historyTree, err := buildHistoryTree(s.SyncTree)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fullState, err := s.builder.Build(historyTree, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(fullState.DeletedIds) != len(s.state.DeletedIds) {
|
||||||
|
log.WarnCtx(ctx, "state does not have all deleted ids",
|
||||||
|
zap.Int("fullstate ids", len(fullState.DeletedIds)),
|
||||||
|
zap.Int("state ids", len(fullState.DeletedIds)))
|
||||||
|
s.state = fullState
|
||||||
|
err = s.deletionManager.UpdateState(context.Background(), s.state)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *settingsObject) Close() error {
|
||||||
|
s.loop.Close()
|
||||||
|
return s.SyncTree.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *settingsObject) DeleteSpace(ctx context.Context, raw *treechangeproto.RawTreeChangeWithId) (err error) {
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
defer func() {
|
||||||
|
log.Debug("finished adding delete change", zap.Error(err))
|
||||||
|
}()
|
||||||
|
err = s.verifyDeleteSpace(raw)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res, err := s.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
||||||
|
NewHeads: []string{raw.Id},
|
||||||
|
RawChanges: []*treechangeproto.RawTreeChangeWithId{raw},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !slices.Contains(res.Heads, raw.Id) {
|
||||||
|
err = ErrCantDeleteSpace
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *settingsObject) SpaceDeleteRawChange() (raw *treechangeproto.RawTreeChangeWithId, err error) {
|
||||||
|
accountData := s.account.Account()
|
||||||
|
data, err := s.changeFactory.CreateSpaceDeleteChange(accountData.PeerId, s.state, false)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return s.PrepareChange(objecttree.SignableChangeContent{
|
||||||
|
Data: data,
|
||||||
|
Key: accountData.SignKey,
|
||||||
|
IsSnapshot: false,
|
||||||
|
IsEncrypted: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *settingsObject) DeleteObject(id string) (err error) {
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
if s.Id() == id {
|
||||||
|
err = ErrDeleteSelf
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.state.Exists(id) {
|
||||||
|
err = ErrAlreadyDeleted
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, err = s.store.TreeStorage(id)
|
||||||
|
if err != nil {
|
||||||
|
err = ErrObjDoesNotExist
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isSnapshot := DoSnapshot(s.Len())
|
||||||
|
res, err := s.changeFactory.CreateObjectDeleteChange(id, s.state, isSnapshot)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.addContent(res, isSnapshot)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *settingsObject) verifyDeleteSpace(raw *treechangeproto.RawTreeChangeWithId) (err error) {
|
||||||
|
data, err := s.UnpackChange(raw)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return verifyDeleteContent(data, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *settingsObject) addContent(data []byte, isSnapshot bool) (err error) {
|
||||||
|
accountData := s.account.Account()
|
||||||
|
res, err := s.AddContent(context.Background(), objecttree.SignableChangeContent{
|
||||||
|
Data: data,
|
||||||
|
Key: accountData.SignKey,
|
||||||
|
IsSnapshot: isSnapshot,
|
||||||
|
IsEncrypted: false,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if res.Mode == objecttree.Rebuild {
|
||||||
|
s.Rebuild(s)
|
||||||
|
} else {
|
||||||
|
s.Update(s)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func VerifyDeleteChange(raw *treechangeproto.RawTreeChangeWithId, identity crypto.PubKey, peerId string) (err error) {
|
||||||
|
changeBuilder := objecttree.NewChangeBuilder(crypto.NewKeyStorage(), nil)
|
||||||
|
res, err := changeBuilder.Unmarshall(raw, true)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !res.Identity.Equals(identity) {
|
||||||
|
return fmt.Errorf("incorrect identity")
|
||||||
|
}
|
||||||
|
return verifyDeleteContent(res.Data, peerId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyDeleteContent(data []byte, peerId string) (err error) {
|
||||||
|
content := &spacesyncproto.SettingsData{}
|
||||||
|
err = proto.Unmarshal(data, content)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(content.GetContent()) != 1 ||
|
||||||
|
content.GetContent()[0].GetSpaceDelete() == nil ||
|
||||||
|
(peerId == "" && content.GetContent()[0].GetSpaceDelete().GetDeleterPeerId() == "") ||
|
||||||
|
(peerId != "" && content.GetContent()[0].GetSpaceDelete().GetDeleterPeerId() != peerId) {
|
||||||
|
return fmt.Errorf("incorrect delete change payload")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
//go:generate mockgen -destination mock_settingsstate/mock_settingsstate.go github.com/anyproto/any-sync/commonspace/settings/settingsstate ObjectDeletionState,StateBuilder,ChangeFactory
|
||||||
package settingsstate
|
package settingsstate
|
||||||
|
|
||||||
import "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
import "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
|
|
|
@ -130,7 +130,7 @@ type space struct {
|
||||||
headSync headsync.HeadSync
|
headSync headsync.HeadSync
|
||||||
syncStatus syncstatus.StatusUpdater
|
syncStatus syncstatus.StatusUpdater
|
||||||
storage spacestorage.SpaceStorage
|
storage spacestorage.SpaceStorage
|
||||||
treeManager *commonGetter
|
treeManager *objectManager
|
||||||
account accountservice.Service
|
account accountservice.Service
|
||||||
aclList *syncacl.SyncAcl
|
aclList *syncacl.SyncAcl
|
||||||
configuration nodeconf.NodeConf
|
configuration nodeconf.NodeConf
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"github.com/anyproto/any-sync/accountservice"
|
"github.com/anyproto/any-sync/accountservice"
|
||||||
"github.com/anyproto/any-sync/app"
|
"github.com/anyproto/any-sync/app"
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
"github.com/anyproto/any-sync/app/logger"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/config"
|
||||||
"github.com/anyproto/any-sync/commonspace/credentialprovider"
|
"github.com/anyproto/any-sync/commonspace/credentialprovider"
|
||||||
"github.com/anyproto/any-sync/commonspace/headsync"
|
"github.com/anyproto/any-sync/commonspace/headsync"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||||
|
@ -45,7 +46,7 @@ type SpaceService interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type spaceService struct {
|
type spaceService struct {
|
||||||
config Config
|
config config.Config
|
||||||
account accountservice.Service
|
account accountservice.Service
|
||||||
configurationService nodeconf.Service
|
configurationService nodeconf.Service
|
||||||
storageProvider spacestorage.SpaceStorageProvider
|
storageProvider spacestorage.SpaceStorageProvider
|
||||||
|
@ -54,10 +55,11 @@ type spaceService struct {
|
||||||
treeManager treemanager.TreeManager
|
treeManager treemanager.TreeManager
|
||||||
pool pool.Pool
|
pool pool.Pool
|
||||||
metric metric.Metric
|
metric metric.Metric
|
||||||
|
app *app.App
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *spaceService) Init(a *app.App) (err error) {
|
func (s *spaceService) Init(a *app.App) (err error) {
|
||||||
s.config = a.MustComponent("config").(ConfigGetter).GetSpace()
|
s.config = a.MustComponent("config").(config.ConfigGetter).GetSpace()
|
||||||
s.account = a.MustComponent(accountservice.CName).(accountservice.Service)
|
s.account = a.MustComponent(accountservice.CName).(accountservice.Service)
|
||||||
s.storageProvider = a.MustComponent(spacestorage.CName).(spacestorage.SpaceStorageProvider)
|
s.storageProvider = a.MustComponent(spacestorage.CName).(spacestorage.SpaceStorageProvider)
|
||||||
s.configurationService = a.MustComponent(nodeconf.CName).(nodeconf.Service)
|
s.configurationService = a.MustComponent(nodeconf.CName).(nodeconf.Service)
|
||||||
|
@ -149,7 +151,7 @@ func (s *spaceService) NewSpace(ctx context.Context, id string) (Space, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
spaceIsDeleted.Swap(isDeleted)
|
spaceIsDeleted.Swap(isDeleted)
|
||||||
getter := newCommonGetter(st.Id(), s.treeManager, spaceIsClosed)
|
getter := NewObjectManager(st.Id(), s.treeManager, spaceIsClosed)
|
||||||
syncStatus := syncstatus.NewNoOpSyncStatus()
|
syncStatus := syncstatus.NewNoOpSyncStatus()
|
||||||
// this will work only for clients, not the best solution, but...
|
// this will work only for clients, not the best solution, but...
|
||||||
if !lastConfiguration.IsResponsible(st.Id()) {
|
if !lastConfiguration.IsResponsible(st.Id()) {
|
||||||
|
|
35
commonspace/spacestate/shareddata.go
Normal file
35
commonspace/spacestate/shareddata.go
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package spacestate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/anyproto/any-sync/app"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CName = "common.commonspace.shareddata"
|
||||||
|
|
||||||
|
type SpaceActions interface {
|
||||||
|
OnObjectDelete(id string)
|
||||||
|
OnSpaceDelete()
|
||||||
|
}
|
||||||
|
|
||||||
|
type SpaceState struct {
|
||||||
|
SpaceId string
|
||||||
|
SpaceIsDeleted *atomic.Bool
|
||||||
|
SpaceIsClosed *atomic.Bool
|
||||||
|
TreesUsed *atomic.Int32
|
||||||
|
AclList list.AclList
|
||||||
|
SpaceStorage spacestorage.SpaceStorage
|
||||||
|
TreeBuilderFunc objecttree.BuildObjectTreeFunc
|
||||||
|
Actions SpaceActions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SpaceState) Init(a *app.App) (err error) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SpaceState) Name() (name string) {
|
||||||
|
return CName
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import (
|
||||||
accountService "github.com/anyproto/any-sync/accountservice"
|
accountService "github.com/anyproto/any-sync/accountservice"
|
||||||
"github.com/anyproto/any-sync/app"
|
"github.com/anyproto/any-sync/app"
|
||||||
"github.com/anyproto/any-sync/app/ocache"
|
"github.com/anyproto/any-sync/app/ocache"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/config"
|
||||||
"github.com/anyproto/any-sync/commonspace/credentialprovider"
|
"github.com/anyproto/any-sync/commonspace/credentialprovider"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||||
|
@ -205,8 +206,8 @@ func (m *mockConfig) Name() (name string) {
|
||||||
return "config"
|
return "config"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockConfig) GetSpace() Config {
|
func (m *mockConfig) GetSpace() config.Config {
|
||||||
return Config{
|
return config.Config{
|
||||||
GCTTL: 60,
|
GCTTL: 60,
|
||||||
SyncPeriod: 20,
|
SyncPeriod: 20,
|
||||||
KeepTreeDataInMemory: true,
|
KeepTreeDataInMemory: true,
|
||||||
|
|
14
commonspace/streamsender/streamsender.go
Normal file
14
commonspace/streamsender/streamsender.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package streamsender
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/anyproto/any-sync/app"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CName = "common.commonspace.streamsender"
|
||||||
|
|
||||||
|
type StreamSender interface {
|
||||||
|
app.ComponentRunnable
|
||||||
|
SendPeer(peerId string, msg *spacesyncproto.ObjectSyncMessage) (err error)
|
||||||
|
Broadcast(msg *spacesyncproto.ObjectSyncMessage) (err error)
|
||||||
|
}
|
|
@ -1,9 +1,32 @@
|
||||||
package syncstatus
|
package syncstatus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/anyproto/any-sync/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewNoOpSyncStatus() StatusProvider {
|
||||||
|
return &noOpSyncStatus{}
|
||||||
|
}
|
||||||
|
|
||||||
type noOpSyncStatus struct{}
|
type noOpSyncStatus struct{}
|
||||||
|
|
||||||
func NewNoOpSyncStatus() StatusUpdater {
|
func (n *noOpSyncStatus) Init(a *app.App) (err error) {
|
||||||
return &noOpSyncStatus{}
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *noOpSyncStatus) Name() (name string) {
|
||||||
|
return CName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *noOpSyncStatus) Watch(treeId string) (err error) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *noOpSyncStatus) Unwatch(treeId string) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *noOpSyncStatus) SetUpdateReceiver(updater UpdateReceiver) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *noOpSyncStatus) HeadsChange(treeId string, heads []string) {
|
func (n *noOpSyncStatus) HeadsChange(treeId string, heads []string) {
|
||||||
|
@ -22,9 +45,10 @@ func (n *noOpSyncStatus) StateCounter() uint64 {
|
||||||
func (n *noOpSyncStatus) RemoveAllExcept(senderId string, differentRemoteIds []string, stateCounter uint64) {
|
func (n *noOpSyncStatus) RemoveAllExcept(senderId string, differentRemoteIds []string, stateCounter uint64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *noOpSyncStatus) Run() {
|
func (n *noOpSyncStatus) Run(ctx context.Context) error {
|
||||||
}
|
return nil
|
||||||
|
}
|
||||||
func (n *noOpSyncStatus) Close() error {
|
|
||||||
|
func (n *noOpSyncStatus) Close(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ package syncstatus
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/anyproto/any-sync/app"
|
||||||
|
"github.com/anyproto/any-sync/commonspace/spacestate"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -20,7 +22,9 @@ const (
|
||||||
syncTimeout = time.Second
|
syncTimeout = time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
var log = logger.NewNamed("common.commonspace.syncstatus")
|
var log = logger.NewNamed(CName)
|
||||||
|
|
||||||
|
const CName = "common.commonspace.syncstatus"
|
||||||
|
|
||||||
type UpdateReceiver interface {
|
type UpdateReceiver interface {
|
||||||
UpdateTree(ctx context.Context, treeId string, status SyncStatus) (err error)
|
UpdateTree(ctx context.Context, treeId string, status SyncStatus) (err error)
|
||||||
|
@ -34,9 +38,6 @@ type StatusUpdater interface {
|
||||||
SetNodesOnline(senderId string, online bool)
|
SetNodesOnline(senderId string, online bool)
|
||||||
StateCounter() uint64
|
StateCounter() uint64
|
||||||
RemoveAllExcept(senderId string, differentRemoteIds []string, stateCounter uint64)
|
RemoveAllExcept(senderId string, differentRemoteIds []string, stateCounter uint64)
|
||||||
|
|
||||||
Run()
|
|
||||||
Close() error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type StatusWatcher interface {
|
type StatusWatcher interface {
|
||||||
|
@ -46,6 +47,7 @@ type StatusWatcher interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type StatusProvider interface {
|
type StatusProvider interface {
|
||||||
|
app.ComponentRunnable
|
||||||
StatusUpdater
|
StatusUpdater
|
||||||
StatusWatcher
|
StatusWatcher
|
||||||
}
|
}
|
||||||
|
@ -89,35 +91,27 @@ type syncStatusProvider struct {
|
||||||
updateTimeout time.Duration
|
updateTimeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
type SyncStatusDeps struct {
|
func NewSyncStatusProvider() StatusProvider {
|
||||||
UpdateIntervalSecs int
|
|
||||||
UpdateTimeout time.Duration
|
|
||||||
Configuration nodeconf.NodeConf
|
|
||||||
Storage spacestorage.SpaceStorage
|
|
||||||
}
|
|
||||||
|
|
||||||
func DefaultDeps(configuration nodeconf.NodeConf, store spacestorage.SpaceStorage) SyncStatusDeps {
|
|
||||||
return SyncStatusDeps{
|
|
||||||
UpdateIntervalSecs: syncUpdateInterval,
|
|
||||||
UpdateTimeout: syncTimeout,
|
|
||||||
Configuration: configuration,
|
|
||||||
Storage: store,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSyncStatusProvider(spaceId string, deps SyncStatusDeps) StatusProvider {
|
|
||||||
return &syncStatusProvider{
|
return &syncStatusProvider{
|
||||||
spaceId: spaceId,
|
treeHeads: map[string]treeHeadsEntry{},
|
||||||
treeHeads: map[string]treeHeadsEntry{},
|
watchers: map[string]struct{}{},
|
||||||
watchers: map[string]struct{}{},
|
|
||||||
updateIntervalSecs: deps.UpdateIntervalSecs,
|
|
||||||
updateTimeout: deps.UpdateTimeout,
|
|
||||||
configuration: deps.Configuration,
|
|
||||||
storage: deps.Storage,
|
|
||||||
stateCounter: 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *syncStatusProvider) Init(a *app.App) (err error) {
|
||||||
|
sharedState := a.MustComponent(spacestate.CName).(*spacestate.SpaceState)
|
||||||
|
s.updateIntervalSecs = syncUpdateInterval
|
||||||
|
s.updateTimeout = syncTimeout
|
||||||
|
s.configuration = a.MustComponent(nodeconf.CName).(nodeconf.NodeConf)
|
||||||
|
s.storage = sharedState.SpaceStorage
|
||||||
|
s.spaceId = sharedState.SpaceId
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncStatusProvider) Name() (name string) {
|
||||||
|
return CName
|
||||||
|
}
|
||||||
|
|
||||||
func (s *syncStatusProvider) SetUpdateReceiver(updater UpdateReceiver) {
|
func (s *syncStatusProvider) SetUpdateReceiver(updater UpdateReceiver) {
|
||||||
s.Lock()
|
s.Lock()
|
||||||
defer s.Unlock()
|
defer s.Unlock()
|
||||||
|
@ -125,13 +119,14 @@ func (s *syncStatusProvider) SetUpdateReceiver(updater UpdateReceiver) {
|
||||||
s.updateReceiver = updater
|
s.updateReceiver = updater
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *syncStatusProvider) Run() {
|
func (s *syncStatusProvider) Run(ctx context.Context) error {
|
||||||
s.periodicSync = periodicsync.NewPeriodicSync(
|
s.periodicSync = periodicsync.NewPeriodicSync(
|
||||||
s.updateIntervalSecs,
|
s.updateIntervalSecs,
|
||||||
s.updateTimeout,
|
s.updateTimeout,
|
||||||
s.update,
|
s.update,
|
||||||
log)
|
log)
|
||||||
s.periodicSync.Run()
|
s.periodicSync.Run()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *syncStatusProvider) HeadsChange(treeId string, heads []string) {
|
func (s *syncStatusProvider) HeadsChange(treeId string, heads []string) {
|
||||||
|
@ -257,11 +252,6 @@ func (s *syncStatusProvider) Unwatch(treeId string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *syncStatusProvider) Close() (err error) {
|
|
||||||
s.periodicSync.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncStatusProvider) StateCounter() uint64 {
|
func (s *syncStatusProvider) StateCounter() uint64 {
|
||||||
s.Lock()
|
s.Lock()
|
||||||
defer s.Unlock()
|
defer s.Unlock()
|
||||||
|
@ -292,6 +282,11 @@ func (s *syncStatusProvider) RemoveAllExcept(senderId string, differentRemoteIds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *syncStatusProvider) Close(ctx context.Context) error {
|
||||||
|
s.periodicSync.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *syncStatusProvider) isSenderResponsible(senderId string) bool {
|
func (s *syncStatusProvider) isSenderResponsible(senderId string) bool {
|
||||||
return slices.Contains(s.configuration.NodeIds(s.spaceId), senderId)
|
return slices.Contains(s.configuration.NodeIds(s.spaceId), senderId)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue