Client badger WIP
This commit is contained in:
parent
998e00d245
commit
bc9f0c19b7
62
client/account/service.go
Normal file
62
client/account/service.go
Normal file
@ -0,0 +1,62 @@
|
||||
package account
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
commonaccount "github.com/anytypeio/go-anytype-infrastructure-experiments/common/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/config"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
)
|
||||
|
||||
type service struct {
|
||||
accountData *account.AccountData
|
||||
peerId string
|
||||
}
|
||||
|
||||
func (s *service) Account() *account.AccountData {
|
||||
return s.accountData
|
||||
}
|
||||
|
||||
func New() app.Component {
|
||||
return &service{}
|
||||
}
|
||||
|
||||
func (s *service) Init(a *app.App) (err error) {
|
||||
acc := a.MustComponent(config.CName).(commonaccount.ConfigGetter).GetAccount()
|
||||
|
||||
decodedEncryptionKey, err := keys.DecodeKeyFromString(
|
||||
acc.EncryptionKey,
|
||||
encryptionkey.NewEncryptionRsaPrivKeyFromBytes,
|
||||
nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
decodedSigningKey, err := keys.DecodeKeyFromString(
|
||||
acc.SigningKey,
|
||||
signingkey.NewSigningEd25519PrivKeyFromBytes,
|
||||
nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
identity, err := decodedSigningKey.GetPublic().Raw()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.accountData = &account.AccountData{
|
||||
Identity: identity,
|
||||
SignKey: decodedSigningKey,
|
||||
EncKey: decodedEncryptionKey,
|
||||
}
|
||||
s.peerId = acc.PeerId
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Name() (name string) {
|
||||
return commonaccount.CName
|
||||
}
|
||||
76
client/badgerprovider/helpers.go
Normal file
76
client/badgerprovider/helpers.go
Normal file
@ -0,0 +1,76 @@
|
||||
package badgerprovider
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/dgraph-io/badger/v3"
|
||||
)
|
||||
|
||||
var ErrIncorrectKey = errors.New("the key is incorrect")
|
||||
|
||||
func Has(db *badger.DB, key []byte) (has bool, err error) {
|
||||
err = db.View(func(txn *badger.Txn) error {
|
||||
_, err := txn.Get(key)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
has = true
|
||||
return
|
||||
}
|
||||
|
||||
func Put(db *badger.DB, key, value []byte) (err error) {
|
||||
return db.Update(func(txn *badger.Txn) error {
|
||||
return txn.Set(key, value)
|
||||
})
|
||||
}
|
||||
|
||||
func Get(db *badger.DB, key []byte) (value []byte, err error) {
|
||||
err = db.View(func(txn *badger.Txn) error {
|
||||
item, err := txn.Get(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value, err = item.ValueCopy(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func GetKeySuffix(txn *badger.Txn, keyPrefix []byte) (suffix []byte, err error) {
|
||||
iter := txn.NewIterator(badger.IteratorOptions{Prefix: keyPrefix})
|
||||
iter.Next()
|
||||
if !iter.Valid() {
|
||||
err = badger.ErrKeyNotFound
|
||||
return
|
||||
}
|
||||
|
||||
suffix = iter.Item().Key()
|
||||
if len(suffix) <= len(keyPrefix)+1 {
|
||||
err = ErrIncorrectKey
|
||||
return
|
||||
}
|
||||
suffix = suffix[len(keyPrefix)+1:]
|
||||
return
|
||||
}
|
||||
|
||||
func GetKeySuffixAndValue(txn *badger.Txn, keyPrefix []byte) (suffix []byte, value []byte, err error) {
|
||||
iter := txn.NewIterator(badger.IteratorOptions{Prefix: keyPrefix})
|
||||
iter.Next()
|
||||
if !iter.Valid() {
|
||||
err = badger.ErrKeyNotFound
|
||||
return
|
||||
}
|
||||
|
||||
suffix = iter.Item().Key()
|
||||
if len(suffix) <= len(keyPrefix)+1 {
|
||||
err = ErrIncorrectKey
|
||||
return
|
||||
}
|
||||
suffix = suffix[len(keyPrefix)+1:]
|
||||
value, err = iter.Item().ValueCopy(value)
|
||||
return
|
||||
}
|
||||
41
client/badgerprovider/service.go
Normal file
41
client/badgerprovider/service.go
Normal file
@ -0,0 +1,41 @@
|
||||
package badgerprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/config"
|
||||
"github.com/dgraph-io/badger/v3"
|
||||
)
|
||||
|
||||
type BadgerProvider interface {
|
||||
app.ComponentRunnable
|
||||
Badger() *badger.DB
|
||||
}
|
||||
|
||||
var CName = "client.badgerprovider"
|
||||
|
||||
type service struct {
|
||||
db *badger.DB
|
||||
}
|
||||
|
||||
func (s *service) Init(a *app.App) (err error) {
|
||||
cfg := a.MustComponent(config.CName).(*config.Config)
|
||||
s.db, err = badger.Open(badger.DefaultOptions(cfg.Storage.Path))
|
||||
return
|
||||
}
|
||||
|
||||
func (s *service) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
|
||||
func (s *service) Badger() *badger.DB {
|
||||
return s.db
|
||||
}
|
||||
|
||||
func (s *service) Run(ctx context.Context) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (s *service) Close(ctx context.Context) (err error) {
|
||||
return s.db.Close()
|
||||
}
|
||||
86
client/clientspace/clientcache/treecache.go
Normal file
86
client/clientspace/clientcache/treecache.go
Normal file
@ -0,0 +1,86 @@
|
||||
package clientcache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/client/clientspace"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/cache"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/node/nodespace"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/ocache"
|
||||
"time"
|
||||
)
|
||||
|
||||
var log = logger.NewNamed("treecache")
|
||||
var ErrCacheObjectWithoutTree = errors.New("cache object contains no tree")
|
||||
|
||||
type ctxKey int
|
||||
|
||||
const spaceKey ctxKey = 0
|
||||
|
||||
type treeCache struct {
|
||||
gcttl int
|
||||
cache ocache.OCache
|
||||
clientService clientspace.Service
|
||||
}
|
||||
|
||||
func New(ttl int) cache.TreeCache {
|
||||
return &treeCache{
|
||||
gcttl: ttl,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *treeCache) Run(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *treeCache) Close(ctx context.Context) (err error) {
|
||||
return c.cache.Close()
|
||||
}
|
||||
|
||||
func (c *treeCache) Init(a *app.App) (err error) {
|
||||
c.clientService = a.MustComponent(nodespace.CName).(nodespace.Service)
|
||||
c.cache = ocache.New(
|
||||
func(ctx context.Context, id string) (value ocache.Object, err error) {
|
||||
spaceId := ctx.Value(spaceKey).(string)
|
||||
space, err := c.clientService.GetSpace(ctx, spaceId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return space.BuildTree(ctx, id, nil)
|
||||
},
|
||||
ocache.WithLogger(log.Sugar()),
|
||||
ocache.WithGCPeriod(time.Minute),
|
||||
ocache.WithTTL(time.Duration(c.gcttl)*time.Second),
|
||||
ocache.WithRefCounter(false),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *treeCache) Name() (name string) {
|
||||
return cache.CName
|
||||
}
|
||||
|
||||
func (c *treeCache) GetTree(ctx context.Context, spaceId, id string) (res cache.TreeResult, err error) {
|
||||
var cacheRes ocache.Object
|
||||
ctx = context.WithValue(ctx, spaceKey, spaceId)
|
||||
cacheRes, err = c.cache.Get(ctx, id)
|
||||
if err != nil {
|
||||
return cache.TreeResult{}, err
|
||||
}
|
||||
|
||||
treeContainer, ok := cacheRes.(cache.TreeContainer)
|
||||
if !ok {
|
||||
err = ErrCacheObjectWithoutTree
|
||||
return
|
||||
}
|
||||
|
||||
res = cache.TreeResult{
|
||||
Release: func() {
|
||||
c.cache.Release(id)
|
||||
},
|
||||
TreeContainer: treeContainer,
|
||||
}
|
||||
return
|
||||
}
|
||||
58
client/clientspace/rpchandler.go
Normal file
58
client/clientspace/rpchandler.go
Normal file
@ -0,0 +1,58 @@
|
||||
package clientspace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/cache"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
|
||||
)
|
||||
|
||||
type rpcHandler struct {
|
||||
s *service
|
||||
}
|
||||
|
||||
func (r *rpcHandler) PushSpace(ctx context.Context, req *spacesyncproto.PushSpaceRequest) (resp *spacesyncproto.PushSpaceResponse, err error) {
|
||||
_, err = r.s.GetSpace(ctx, req.SpaceHeader.Id)
|
||||
if err == nil {
|
||||
err = spacesyncproto.ErrSpaceExists
|
||||
return
|
||||
}
|
||||
if err != cache.ErrSpaceNotFound {
|
||||
err = spacesyncproto.ErrUnexpected
|
||||
return
|
||||
}
|
||||
|
||||
payload := storage.SpaceStorageCreatePayload{
|
||||
RecWithId: req.AclRoot,
|
||||
SpaceHeaderWithId: req.SpaceHeader,
|
||||
}
|
||||
_, err = r.s.spaceStorageProvider.CreateSpaceStorage(payload)
|
||||
if err != nil {
|
||||
err = spacesyncproto.ErrUnexpected
|
||||
if err == storage.ErrSpaceStorageExists {
|
||||
err = spacesyncproto.ErrSpaceExists
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *rpcHandler) HeadSync(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (*spacesyncproto.HeadSyncResponse, error) {
|
||||
sp, err := r.s.GetSpace(ctx, req.SpaceId)
|
||||
if err != nil {
|
||||
return nil, spacesyncproto.ErrSpaceMissing
|
||||
}
|
||||
return sp.SpaceSyncRpc().HeadSync(ctx, req)
|
||||
}
|
||||
|
||||
func (r *rpcHandler) Stream(stream spacesyncproto.DRPCSpace_StreamStream) error {
|
||||
msg, err := stream.Recv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sp, err := r.s.GetSpace(stream.Context(), msg.SpaceId)
|
||||
if err != nil {
|
||||
return spacesyncproto.ErrSpaceMissing
|
||||
}
|
||||
return sp.SpaceSyncRpc().Stream(stream)
|
||||
}
|
||||
74
client/clientspace/service.go
Normal file
74
client/clientspace/service.go
Normal file
@ -0,0 +1,74 @@
|
||||
package clientspace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/rpc/server"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/config"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/ocache"
|
||||
"time"
|
||||
)
|
||||
|
||||
const CName = "client.clientspace"
|
||||
|
||||
var log = logger.NewNamed(CName)
|
||||
|
||||
func New() Service {
|
||||
return &service{}
|
||||
}
|
||||
|
||||
type Service interface {
|
||||
GetSpace(ctx context.Context, id string) (commonspace.Space, error)
|
||||
app.ComponentRunnable
|
||||
}
|
||||
|
||||
type service struct {
|
||||
conf config.Space
|
||||
spaceCache ocache.OCache
|
||||
commonSpace commonspace.Service
|
||||
spaceStorageProvider storage.SpaceStorageProvider
|
||||
}
|
||||
|
||||
func (s *service) Init(a *app.App) (err error) {
|
||||
s.conf = a.MustComponent(config.CName).(*config.Config).Space
|
||||
s.commonSpace = a.MustComponent(commonspace.CName).(commonspace.Service)
|
||||
s.spaceStorageProvider = a.MustComponent(storage.CName).(storage.SpaceStorageProvider)
|
||||
s.spaceCache = ocache.New(
|
||||
func(ctx context.Context, id string) (value ocache.Object, err error) {
|
||||
return s.commonSpace.GetSpace(ctx, id)
|
||||
},
|
||||
ocache.WithLogger(log.Sugar()),
|
||||
ocache.WithGCPeriod(time.Minute),
|
||||
ocache.WithTTL(time.Duration(s.conf.GCTTL)*time.Second),
|
||||
ocache.WithRefCounter(false),
|
||||
)
|
||||
return spacesyncproto.DRPCRegisterSpace(a.MustComponent(server.CName).(server.DRPCServer), &rpcHandler{s})
|
||||
}
|
||||
|
||||
func (s *service) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
|
||||
func (s *service) Run(ctx context.Context) (err error) {
|
||||
go func() {
|
||||
time.Sleep(time.Second * 5)
|
||||
_, _ = s.GetSpace(ctx, "testDSpace")
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
||||
func (s *service) GetSpace(ctx context.Context, id string) (commonspace.Space, error) {
|
||||
v, err := s.spaceCache.Get(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v.(commonspace.Space), nil
|
||||
}
|
||||
|
||||
func (s *service) Close(ctx context.Context) (err error) {
|
||||
return s.spaceCache.Close()
|
||||
}
|
||||
91
client/storage/keys.go
Normal file
91
client/storage/keys.go
Normal file
@ -0,0 +1,91 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
type aclKeys struct {
|
||||
spaceId string
|
||||
rootKey []byte
|
||||
headKey []byte
|
||||
}
|
||||
|
||||
func newACLKeys(spaceId string) aclKeys {
|
||||
return aclKeys{
|
||||
spaceId: spaceId,
|
||||
rootKey: joinStringsToBytes("space", spaceId, "a", "rootId"),
|
||||
headKey: joinStringsToBytes("space", spaceId, "a", "headId"),
|
||||
}
|
||||
}
|
||||
|
||||
func (a aclKeys) HeadIdKey() []byte {
|
||||
return a.headKey
|
||||
}
|
||||
|
||||
func (a aclKeys) RootIdKey() []byte {
|
||||
return a.rootKey
|
||||
}
|
||||
|
||||
func (a aclKeys) RawRecordKey(id string) []byte {
|
||||
return joinStringsToBytes("space", a.spaceId, "a", id)
|
||||
}
|
||||
|
||||
type treeKeys struct {
|
||||
id string
|
||||
spaceId string
|
||||
headsKey []byte
|
||||
rootKey []byte
|
||||
}
|
||||
|
||||
func newTreeKeys(spaceId, id string) treeKeys {
|
||||
return treeKeys{
|
||||
id: id,
|
||||
spaceId: spaceId,
|
||||
headsKey: joinStringsToBytes("space", spaceId, "t", id, "heads"),
|
||||
rootKey: joinStringsToBytes("space", spaceId, "t", id),
|
||||
}
|
||||
}
|
||||
|
||||
func (t treeKeys) HeadsKey() []byte {
|
||||
return t.headsKey
|
||||
}
|
||||
|
||||
func (t treeKeys) RootIdKey() []byte {
|
||||
return t.rootKey
|
||||
}
|
||||
|
||||
func (t treeKeys) RawChangeKey(id string) []byte {
|
||||
return joinStringsToBytes("space", t.spaceId, "t", t.id, id)
|
||||
}
|
||||
|
||||
type spaceKeys struct {
|
||||
headerKey []byte
|
||||
}
|
||||
|
||||
func newSpaceKeys(spaceId string) spaceKeys {
|
||||
return spaceKeys{headerKey: joinStringsToBytes("space", spaceId)}
|
||||
}
|
||||
|
||||
func (s spaceKeys) HeaderKey() []byte {
|
||||
return s.headerKey
|
||||
}
|
||||
|
||||
func joinStringsToBytes(strs ...string) []byte {
|
||||
var (
|
||||
b bytes.Buffer
|
||||
totalLen int
|
||||
)
|
||||
for _, s := range strs {
|
||||
totalLen += len(s)
|
||||
}
|
||||
// adding separators
|
||||
totalLen += len(strs) - 1
|
||||
b.Grow(totalLen)
|
||||
for idx, s := range strs {
|
||||
if idx > 0 {
|
||||
b.WriteString("/")
|
||||
}
|
||||
b.WriteString(s)
|
||||
}
|
||||
return b.Bytes()
|
||||
}
|
||||
131
client/storage/liststorage.go
Normal file
131
client/storage/liststorage.go
Normal file
@ -0,0 +1,131 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
provider "github.com/anytypeio/go-anytype-infrastructure-experiments/client/badgerprovider"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclrecordproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
||||
"github.com/dgraph-io/badger/v3"
|
||||
)
|
||||
|
||||
var ErrIncorrectKey = errors.New("key format is incorrect")
|
||||
|
||||
type listStorage struct {
|
||||
db *badger.DB
|
||||
keys aclKeys
|
||||
id string
|
||||
root *aclrecordproto.RawACLRecordWithId
|
||||
}
|
||||
|
||||
func newListStorage(spaceId string, db *badger.DB, txn *badger.Txn) (ls storage.ListStorage, err error) {
|
||||
keys := newACLKeys(spaceId)
|
||||
rootId, err := provider.GetKeySuffix(txn, keys.RootIdKey())
|
||||
if err != nil {
|
||||
if err == badger.ErrKeyNotFound {
|
||||
err = storage.ErrUnknownACLId
|
||||
}
|
||||
return
|
||||
}
|
||||
stringId := string(rootId)
|
||||
rootItem, err := txn.Get(keys.RawRecordKey(stringId))
|
||||
if err != nil {
|
||||
if err == badger.ErrKeyNotFound {
|
||||
err = storage.ErrUnknownACLId
|
||||
}
|
||||
return
|
||||
}
|
||||
var value []byte
|
||||
value, err = rootItem.ValueCopy(value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rootWithId := &aclrecordproto.RawACLRecordWithId{
|
||||
Payload: value,
|
||||
Id: stringId,
|
||||
}
|
||||
|
||||
ls = &listStorage{
|
||||
db: db,
|
||||
keys: aclKeys{},
|
||||
id: stringId,
|
||||
root: rootWithId,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func createListStorage(spaceId string, db *badger.DB, txn *badger.Txn, root *aclrecordproto.RawACLRecordWithId) (ls storage.ListStorage, err error) {
|
||||
keys := newACLKeys(spaceId)
|
||||
_, err = provider.GetKeySuffix(txn, keys.RootIdKey())
|
||||
if err != nil && err != badger.ErrKeyNotFound {
|
||||
return
|
||||
}
|
||||
if err == nil {
|
||||
err = storage.ErrACLExists
|
||||
return
|
||||
}
|
||||
aclRootKey := append(keys.RootIdKey(), '/')
|
||||
aclRootKey = append(aclRootKey, []byte(root.Id)...)
|
||||
|
||||
err = txn.Set(aclRootKey, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = txn.Set(keys.HeadIdKey(), []byte(root.Id))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = txn.Set(keys.RawRecordKey(root.Id), root.Payload)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ls = &listStorage{
|
||||
db: db,
|
||||
keys: aclKeys{},
|
||||
id: root.Id,
|
||||
root: root,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *listStorage) ID() (string, error) {
|
||||
return l.id, nil
|
||||
}
|
||||
|
||||
func (l *listStorage) Root() (*aclrecordproto.RawACLRecordWithId, error) {
|
||||
return l.root, nil
|
||||
}
|
||||
|
||||
func (l *listStorage) Head() (head string, err error) {
|
||||
bytes, err := provider.Get(l.db, l.keys.HeadIdKey())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
head = string(bytes)
|
||||
return
|
||||
}
|
||||
|
||||
func (l *listStorage) GetRawRecord(ctx context.Context, id string) (raw *aclrecordproto.RawACLRecordWithId, err error) {
|
||||
res, err := l.db.Get(l.keys.RawRecordKey(id))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if res == nil {
|
||||
err = storage.ErrUnknownRecord
|
||||
return
|
||||
}
|
||||
|
||||
raw = &aclrecordproto.RawACLRecordWithId{
|
||||
Payload: res,
|
||||
Id: id,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *listStorage) AddRawRecord(ctx context.Context, rec *aclrecordproto.RawACLRecordWithId) error {
|
||||
return l.db.Put([]byte(rec.Id), rec.Payload)
|
||||
}
|
||||
159
client/storage/spacestorage.go
Normal file
159
client/storage/spacestorage.go
Normal file
@ -0,0 +1,159 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"github.com/akrylysov/pogreb"
|
||||
provider "github.com/anytypeio/go-anytype-infrastructure-experiments/client/badgerprovider"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
|
||||
spacestorage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
||||
"github.com/dgraph-io/badger/v3"
|
||||
"path"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var defPogrebOptions = &pogreb.Options{
|
||||
BackgroundCompactionInterval: time.Minute * 5,
|
||||
}
|
||||
|
||||
type spaceStorage struct {
|
||||
objDb *pogreb.DB
|
||||
keys spaceKeys
|
||||
aclStorage storage.ListStorage
|
||||
header *spacesyncproto.RawSpaceHeaderWithId
|
||||
mx sync.Mutex
|
||||
}
|
||||
|
||||
func newSpaceStorage(objDb *badger.DB, spaceId string) (store spacestorage.SpaceStorage, err error) {
|
||||
keys := newSpaceKeys(spaceId)
|
||||
err = objDb.Update(func(txn *badger.Txn) error {
|
||||
header, err := txn.Get(keys.HeaderKey())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
})
|
||||
has, err := provider.Has(obj)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !has {
|
||||
err = spacestorage.ErrSpaceStorageMissing
|
||||
return
|
||||
}
|
||||
|
||||
header, err := objDb.Get(keys.HeaderKey())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if header == nil {
|
||||
err = spacestorage.ErrSpaceStorageMissing
|
||||
return
|
||||
}
|
||||
|
||||
aclStorage, err := newListStorage(objDb)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
store = &spaceStorage{
|
||||
objDb: objDb,
|
||||
keys: keys,
|
||||
header: &spacesyncproto.RawSpaceHeaderWithId{
|
||||
RawHeader: header,
|
||||
Id: spaceId,
|
||||
},
|
||||
aclStorage: aclStorage,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func createSpaceStorage(rootPath string, payload spacestorage.SpaceStorageCreatePayload) (store spacestorage.SpaceStorage, err error) {
|
||||
dbPath := path.Join(rootPath, payload.SpaceHeaderWithId.Id)
|
||||
db, err := pogreb.Open(dbPath, defPogrebOptions)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
db.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
keys := newSpaceKeys(payload.SpaceHeaderWithId.Id)
|
||||
has, err := db.Has(keys.SpaceIdKey())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if has {
|
||||
err = spacesyncproto.ErrSpaceExists
|
||||
return
|
||||
}
|
||||
|
||||
aclStorage, err := createListStorage(db, payload.RecWithId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = db.Put(keys.HeaderKey(), payload.SpaceHeaderWithId.RawHeader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = db.Put(keys.SpaceIdKey(), []byte(payload.SpaceHeaderWithId.Id))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
store = &spaceStorage{
|
||||
objDb: db,
|
||||
keys: keys,
|
||||
aclStorage: aclStorage,
|
||||
header: payload.SpaceHeaderWithId,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *spaceStorage) TreeStorage(id string) (storage.TreeStorage, error) {
|
||||
return newTreeStorage(s.objDb, id)
|
||||
}
|
||||
|
||||
func (s *spaceStorage) CreateTreeStorage(payload storage.TreeStorageCreatePayload) (ts storage.TreeStorage, err error) {
|
||||
// we have mutex here, so we prevent overwriting the heads of a tree on concurrent creation
|
||||
s.mx.Lock()
|
||||
defer s.mx.Unlock()
|
||||
|
||||
return createTreeStorage(s.objDb, payload)
|
||||
}
|
||||
|
||||
func (s *spaceStorage) ACLStorage() (storage.ListStorage, error) {
|
||||
return s.aclStorage, nil
|
||||
}
|
||||
|
||||
func (s *spaceStorage) SpaceHeader() (header *spacesyncproto.RawSpaceHeaderWithId, err error) {
|
||||
return s.header, nil
|
||||
}
|
||||
|
||||
func (s *spaceStorage) StoredIds() (ids []string, err error) {
|
||||
index := s.objDb.Items()
|
||||
|
||||
key, val, err := index.Next()
|
||||
for err == nil {
|
||||
strKey := string(key)
|
||||
if isRootIdKey(strKey) {
|
||||
ids = append(ids, string(val))
|
||||
}
|
||||
key, val, err = index.Next()
|
||||
}
|
||||
|
||||
if err != pogreb.ErrIterationDone {
|
||||
return
|
||||
}
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
|
||||
func (s *spaceStorage) Close() (err error) {
|
||||
return s.objDb.Close()
|
||||
}
|
||||
34
client/storage/storageservice.go
Normal file
34
client/storage/storageservice.go
Normal file
@ -0,0 +1,34 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/client/badgerprovider"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
|
||||
"github.com/dgraph-io/badger/v3"
|
||||
)
|
||||
|
||||
type storageService struct {
|
||||
db *badger.DB
|
||||
}
|
||||
|
||||
func New() storage.SpaceStorageProvider {
|
||||
return &storageService{}
|
||||
}
|
||||
|
||||
func (s *storageService) Init(a *app.App) (err error) {
|
||||
provider := a.MustComponent(badgerprovider.CName).(badgerprovider.BadgerProvider)
|
||||
s.db = provider.Badger()
|
||||
return
|
||||
}
|
||||
|
||||
func (s *storageService) Name() (name string) {
|
||||
return storage.CName
|
||||
}
|
||||
|
||||
func (s *storageService) SpaceStorage(id string) (storage.SpaceStorage, error) {
|
||||
return newSpaceStorage(s.rootPath, id)
|
||||
}
|
||||
|
||||
func (s *storageService) CreateSpaceStorage(payload storage.SpaceStorageCreatePayload) (storage.SpaceStorage, error) {
|
||||
return createSpaceStorage(s.rootPath, payload)
|
||||
}
|
||||
181
client/storage/treestorage.go
Normal file
181
client/storage/treestorage.go
Normal file
@ -0,0 +1,181 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"github.com/akrylysov/pogreb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treechangeproto"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type treeStorage struct {
|
||||
db *pogreb.DB
|
||||
keys treeKeys
|
||||
id string
|
||||
root *treechangeproto.RawTreeChangeWithId
|
||||
}
|
||||
|
||||
func newTreeStorage(db *pogreb.DB, treeId string) (ts storage.TreeStorage, err error) {
|
||||
keys := newTreeKeys(treeId)
|
||||
has, err := db.Has(keys.RootIdKey())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !has {
|
||||
err = storage.ErrUnknownTreeId
|
||||
return
|
||||
}
|
||||
|
||||
heads, err := db.Get(keys.HeadsKey())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if heads == nil {
|
||||
err = storage.ErrUnknownTreeId
|
||||
return
|
||||
}
|
||||
|
||||
root, err := db.Get(keys.RawChangeKey(treeId))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if root == nil {
|
||||
err = storage.ErrUnknownTreeId
|
||||
return
|
||||
}
|
||||
|
||||
rootWithId := &treechangeproto.RawTreeChangeWithId{
|
||||
RawChange: root,
|
||||
Id: treeId,
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ts = &treeStorage{
|
||||
db: db,
|
||||
keys: keys,
|
||||
id: treeId,
|
||||
root: rootWithId,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func createTreeStorage(db *pogreb.DB, payload storage.TreeStorageCreatePayload) (ts storage.TreeStorage, err error) {
|
||||
keys := newTreeKeys(payload.TreeId)
|
||||
has, err := db.Has(keys.RootIdKey())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if has {
|
||||
err = storage.ErrTreeExists
|
||||
return
|
||||
}
|
||||
|
||||
heads := createHeadsPayload(payload.Heads)
|
||||
|
||||
for _, ch := range payload.Changes {
|
||||
err = db.Put(keys.RawChangeKey(ch.Id), ch.GetRawChange())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = db.Put(keys.RawChangeKey(payload.RootRawChange.Id), payload.RootRawChange.GetRawChange())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = db.Put(keys.HeadsKey(), heads)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = db.Put(keys.RootIdKey(), []byte(payload.RootRawChange.Id))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ts = &treeStorage{
|
||||
db: db,
|
||||
keys: keys,
|
||||
id: payload.RootRawChange.Id,
|
||||
root: payload.RootRawChange,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (t *treeStorage) ID() (string, error) {
|
||||
return t.id, nil
|
||||
}
|
||||
|
||||
func (t *treeStorage) Root() (raw *treechangeproto.RawTreeChangeWithId, err error) {
|
||||
return t.root, nil
|
||||
}
|
||||
|
||||
func (t *treeStorage) Heads() (heads []string, err error) {
|
||||
headsBytes, err := t.db.Get(t.keys.HeadsKey())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if heads == nil {
|
||||
err = storage.ErrUnknownTreeId
|
||||
return
|
||||
}
|
||||
heads = parseHeads(headsBytes)
|
||||
return
|
||||
}
|
||||
|
||||
func (t *treeStorage) SetHeads(heads []string) (err error) {
|
||||
payload := createHeadsPayload(heads)
|
||||
return t.db.Put(t.keys.HeadsKey(), payload)
|
||||
}
|
||||
|
||||
func (t *treeStorage) AddRawChange(change *treechangeproto.RawTreeChangeWithId) (err error) {
|
||||
return t.db.Put([]byte(change.Id), change.RawChange)
|
||||
}
|
||||
|
||||
func (t *treeStorage) GetRawChange(ctx context.Context, id string) (raw *treechangeproto.RawTreeChangeWithId, err error) {
|
||||
res, err := t.db.Get(t.keys.RawChangeKey(id))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if res == nil {
|
||||
err = storage.ErrUnkownChange
|
||||
}
|
||||
|
||||
raw = &treechangeproto.RawTreeChangeWithId{
|
||||
RawChange: res,
|
||||
Id: id,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (t *treeStorage) HasChange(ctx context.Context, id string) (bool, error) {
|
||||
return t.db.Has(t.keys.RawChangeKey(id))
|
||||
}
|
||||
|
||||
func parseHeads(headsPayload []byte) []string {
|
||||
return strings.Split(string(headsPayload), "/")
|
||||
}
|
||||
|
||||
func createHeadsPayload(heads []string) []byte {
|
||||
var (
|
||||
b bytes.Buffer
|
||||
totalLen int
|
||||
)
|
||||
for _, s := range heads {
|
||||
totalLen += len(s)
|
||||
}
|
||||
// adding separators
|
||||
totalLen += len(heads) - 1
|
||||
b.Grow(totalLen)
|
||||
for idx, s := range heads {
|
||||
if idx > 0 {
|
||||
b.WriteString("/")
|
||||
}
|
||||
b.WriteString(s)
|
||||
}
|
||||
return b.Bytes()
|
||||
}
|
||||
3
go.mod
3
go.mod
@ -71,10 +71,13 @@ require (
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
|
||||
golang.org/x/image v0.0.0-20200119044424-58c23975cae1 // indirect
|
||||
golang.org/x/mod v0.4.2 // indirect
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/tools v0.1.5 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@ -229,6 +229,7 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -283,6 +284,7 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@ -26,14 +26,12 @@ func (a aclKeys) RawRecordKey(id string) []byte {
|
||||
type treeKeys struct {
|
||||
id string
|
||||
headsKey []byte
|
||||
rootKey []byte
|
||||
}
|
||||
|
||||
func newTreeKeys(id string) treeKeys {
|
||||
return treeKeys{
|
||||
id: id,
|
||||
headsKey: joinStringsToBytes("t", id, "heads"),
|
||||
rootKey: joinStringsToBytes("t", id, "rootId"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,10 +39,6 @@ func (t treeKeys) HeadsKey() []byte {
|
||||
return t.headsKey
|
||||
}
|
||||
|
||||
func (t treeKeys) RootIdKey() []byte {
|
||||
return t.rootKey
|
||||
}
|
||||
|
||||
func (t treeKeys) RawChangeKey(id string) []byte {
|
||||
return joinStringsToBytes("t", t.id, id)
|
||||
}
|
||||
|
||||
@ -16,15 +16,6 @@ type listStorage struct {
|
||||
|
||||
func newListStorage(db *pogreb.DB) (ls storage.ListStorage, err error) {
|
||||
keys := aclKeys{}
|
||||
head, err := db.Get(keys.HeadIdKey())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if head == nil {
|
||||
err = storage.ErrUnknownACLId
|
||||
return
|
||||
}
|
||||
|
||||
rootId, err := db.Get(keys.RootIdKey())
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
@ -18,15 +18,6 @@ type treeStorage struct {
|
||||
|
||||
func newTreeStorage(db *pogreb.DB, treeId string) (ts storage.TreeStorage, err error) {
|
||||
keys := newTreeKeys(treeId)
|
||||
has, err := db.Has(keys.RootIdKey())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !has {
|
||||
err = storage.ErrUnknownTreeId
|
||||
return
|
||||
}
|
||||
|
||||
heads, err := db.Get(keys.HeadsKey())
|
||||
if err != nil {
|
||||
return
|
||||
@ -64,7 +55,7 @@ func newTreeStorage(db *pogreb.DB, treeId string) (ts storage.TreeStorage, err e
|
||||
|
||||
func createTreeStorage(db *pogreb.DB, payload storage.TreeStorageCreatePayload) (ts storage.TreeStorage, err error) {
|
||||
keys := newTreeKeys(payload.TreeId)
|
||||
has, err := db.Has(keys.RootIdKey())
|
||||
has, err := db.Has(keys.HeadsKey())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -92,11 +83,6 @@ func createTreeStorage(db *pogreb.DB, payload storage.TreeStorageCreatePayload)
|
||||
return
|
||||
}
|
||||
|
||||
err = db.Put(keys.RootIdKey(), []byte(payload.RootRawChange.Id))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ts = &treeStorage{
|
||||
db: db,
|
||||
keys: keys,
|
||||
|
||||
@ -63,6 +63,15 @@ message ACLUserAdd {
|
||||
ACLUserPermissions permissions = 4;
|
||||
}
|
||||
|
||||
// signing accept key
|
||||
// rsa encryption key -> read keys
|
||||
|
||||
// accept key, encrypt key, invite id
|
||||
// GetSpace(id) -> ... (space header + acl root) -> diff
|
||||
// Join(ACLJoinRecord) -> Ok
|
||||
|
||||
//
|
||||
|
||||
message ACLUserInvite {
|
||||
bytes acceptPublicKey = 1;
|
||||
bytes encryptPublicKey = 2;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user