mirror of
https://github.com/anyproto/anytype-heart.git
synced 2025-06-10 18:10:49 +09:00
GO-3490 Merge branch 'go-3170-check-objects-and-files-are-synced-with-responsible-peers-in' of github.com:anyproto/anytype-heart into go-3490-add-relations-with-sync-status-to-objects-to-show-object
# Conflicts: # .mockery.yaml # core/syncstatus/objectsyncstatus/syncstatus.go # core/syncstatus/objectsyncstatus/syncstatus_test.go # core/syncstatus/spacesyncstatus/filestate.go # core/syncstatus/spacesyncstatus/objectstate.go # core/syncstatus/spacesyncstatus/spacestatus.go
This commit is contained in:
commit
1a583b0567
86 changed files with 4417 additions and 2079 deletions
|
@ -165,6 +165,9 @@ packages:
|
|||
github.com/anyproto/anytype-heart/core/block/object/idresolver:
|
||||
interfaces:
|
||||
Resolver:
|
||||
github.com/anyproto/anytype-heart/util/linkpreview:
|
||||
interfaces:
|
||||
LinkPreview:
|
||||
github.com/anyproto/anytype-heart/core/block/object/treesyncer:
|
||||
interfaces:
|
||||
Updater:
|
||||
|
@ -172,7 +175,7 @@ packages:
|
|||
SyncDetailsUpdater:
|
||||
github.com/anyproto/anytype-heart/core/syncstatus/objectsyncstatus:
|
||||
interfaces:
|
||||
Updater:
|
||||
SpaceStatusUpdater:
|
||||
UpdateReceiver:
|
||||
config:
|
||||
dir: "{{.InterfaceDir}}"
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
"github.com/gogo/protobuf/types"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/state"
|
||||
|
@ -240,7 +241,7 @@ func (s *service) ContentUpdaters(spaceID string, url string, parseBlock bool) (
|
|||
|
||||
updaters := make(chan func(contentBookmark *bookmark.ObjectContent), 1)
|
||||
|
||||
data, body, err := s.linkPreview.Fetch(ctx, url)
|
||||
data, body, isFile, err := s.linkPreview.Fetch(ctx, url)
|
||||
if err != nil {
|
||||
updaters <- func(c *bookmark.ObjectContent) {
|
||||
if c.BookmarkContent == nil {
|
||||
|
@ -312,6 +313,10 @@ func (s *service) ContentUpdaters(spaceID string, url string, parseBlock bool) (
|
|||
go func() {
|
||||
defer wg.Done()
|
||||
updaters <- func(c *bookmark.ObjectContent) {
|
||||
if isFile {
|
||||
s.handleFileBlock(c, url)
|
||||
return
|
||||
}
|
||||
blocks, _, err := anymark.HTMLToBlocks(body, url)
|
||||
if err != nil {
|
||||
log.Errorf("parse blocks: %s", err)
|
||||
|
@ -328,6 +333,19 @@ func (s *service) ContentUpdaters(spaceID string, url string, parseBlock bool) (
|
|||
return updaters, nil
|
||||
}
|
||||
|
||||
func (s *service) handleFileBlock(c *bookmark.ObjectContent, url string) {
|
||||
c.Blocks = append(
|
||||
c.Blocks,
|
||||
&model.Block{
|
||||
Id: bson.NewObjectId().Hex(),
|
||||
Content: &model.BlockContentOfFile{
|
||||
File: &model.BlockContentFile{
|
||||
Name: url,
|
||||
}},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (s *service) fetcher(spaceID string, blockID string, params bookmark.FetchParams) error {
|
||||
updaters, err := s.ContentUpdaters(spaceID, params.Url, false)
|
||||
if err != nil {
|
||||
|
|
67
core/block/bookmark/bookmark_service_test.go
Normal file
67
core/block/bookmark/bookmark_service_test.go
Normal file
|
@ -0,0 +1,67 @@
|
|||
package bookmark
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
"github.com/anyproto/anytype-heart/util/linkpreview/mock_linkpreview"
|
||||
)
|
||||
|
||||
func TestService_FetchBookmarkContent(t *testing.T) {
|
||||
t.Run("link to html page - create blocks", func(t *testing.T) {
|
||||
// given
|
||||
preview := mock_linkpreview.NewMockLinkPreview(t)
|
||||
preview.EXPECT().Fetch(mock.Anything, "http://test.com").Return(model.LinkPreview{}, []byte(testHtml), false, nil)
|
||||
|
||||
s := &service{linkPreview: preview}
|
||||
|
||||
// when
|
||||
updaters := s.FetchBookmarkContent("space", "http://test.com", true)
|
||||
|
||||
// then
|
||||
content := updaters()
|
||||
assert.Len(t, content.Blocks, 2)
|
||||
})
|
||||
t.Run("link to file - create one block with file", func(t *testing.T) {
|
||||
// given
|
||||
preview := mock_linkpreview.NewMockLinkPreview(t)
|
||||
preview.EXPECT().Fetch(mock.Anything, "http://test.com").Return(model.LinkPreview{}, nil, true, nil)
|
||||
|
||||
s := &service{linkPreview: preview}
|
||||
|
||||
// when
|
||||
updaters := s.FetchBookmarkContent("space", "http://test.com", true)
|
||||
|
||||
// then
|
||||
content := updaters()
|
||||
assert.Len(t, content.Blocks, 1)
|
||||
assert.NotNil(t, content.Blocks[0].GetFile())
|
||||
assert.Equal(t, "http://test.com", content.Blocks[0].GetFile().GetName())
|
||||
})
|
||||
t.Run("link to file - create one block with file, image is base64", func(t *testing.T) {
|
||||
// given
|
||||
preview := mock_linkpreview.NewMockLinkPreview(t)
|
||||
preview.EXPECT().Fetch(mock.Anything, "http://test.com").Return(model.LinkPreview{}, []byte(testHtmlBase64), false, nil)
|
||||
|
||||
s := &service{linkPreview: preview}
|
||||
|
||||
// when
|
||||
updaters := s.FetchBookmarkContent("space", "http://test.com", true)
|
||||
|
||||
// then
|
||||
content := updaters()
|
||||
assert.Len(t, content.Blocks, 1)
|
||||
assert.NotNil(t, content.Blocks[0].GetFile())
|
||||
})
|
||||
}
|
||||
|
||||
const testHtml = `<html><head>
|
||||
<title>Title</title>
|
||||
|
||||
Test
|
||||
</head></html>`
|
||||
|
||||
const testHtmlBase64 = "<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAApgAAAKYB3X3/OAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAANCSURBVEiJtZZPbBtFFMZ/M7ubXdtdb1xSFyeilBapySVU8h8OoFaooFSqiihIVIpQBKci6KEg9Q6H9kovIHoCIVQJJCKE1ENFjnAgcaSGC6rEnxBwA04Tx43t2FnvDAfjkNibxgHxnWb2e/u992bee7tCa00YFsffekFY+nUzFtjW0LrvjRXrCDIAaPLlW0nHL0SsZtVoaF98mLrx3pdhOqLtYPHChahZcYYO7KvPFxvRl5XPp1sN3adWiD1ZAqD6XYK1b/dvE5IWryTt2udLFedwc1+9kLp+vbbpoDh+6TklxBeAi9TL0taeWpdmZzQDry0AcO+jQ12RyohqqoYoo8RDwJrU+qXkjWtfi8Xxt58BdQuwQs9qC/afLwCw8tnQbqYAPsgxE1S6F3EAIXux2oQFKm0ihMsOF71dHYx+f3NND68ghCu1YIoePPQN1pGRABkJ6Bus96CutRZMydTl+TvuiRW1m3n0eDl0vRPcEysqdXn+jsQPsrHMquGeXEaY4Yk4wxWcY5V/9scqOMOVUFthatyTy8QyqwZ+kDURKoMWxNKr2EeqVKcTNOajqKoBgOE28U4tdQl5p5bwCw7BWquaZSzAPlwjlithJtp3pTImSqQRrb2Z8PHGigD4RZuNX6JYj6wj7O4TFLbCO/Mn/m8R+h6rYSUb3ekokRY6f/YukArN979jcW+V/S8g0eT/N3VN3kTqWbQ428m9/8k0P/1aIhF36PccEl6EhOcAUCrXKZXXWS3XKd2vc/TRBG9O5ELC17MmWubD2nKhUKZa26Ba2+D3P+4/MNCFwg59oWVeYhkzgN/JDR8deKBoD7Y+ljEjGZ0sosXVTvbc6RHirr2reNy1OXd6pJsQ+gqjk8VWFYmHrwBzW/n+uMPFiRwHB2I7ih8ciHFxIkd/3Omk5tCDV1t+2nNu5sxxpDFNx+huNhVT3/zMDz8usXC3ddaHBj1GHj/As08fwTS7Kt1HBTmyN29vdwAw+/wbwLVOJ3uAD1wi/dUH7Qei66PfyuRj4Ik9is+hglfbkbfR3cnZm7chlUWLdwmprtCohX4HUtlOcQjLYCu+fzGJH2QRKvP3UNz8bWk1qMxjGTOMThZ3kvgLI5AzFfo379UAAAAASUVORK5CYII=\">"
|
|
@ -239,7 +239,7 @@ func (s *Service) DeleteDataviewView(ctx session.Context, req pb.RpcBlockDatavie
|
|||
|
||||
func (s *Service) SetDataviewActiveView(ctx session.Context, req pb.RpcBlockDataviewViewSetActiveRequest) error {
|
||||
return cache.Do(s, req.ContextId, func(b dataview.Dataview) error {
|
||||
return b.SetActiveView(ctx, req.BlockId, req.ViewId, int(req.Limit), int(req.Offset))
|
||||
return b.SetActiveView(ctx, req.BlockId, req.ViewId)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/objectstore"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/logging"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
"github.com/anyproto/anytype-heart/util/badgerhelper"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
"github.com/anyproto/anytype-heart/util/slice"
|
||||
)
|
||||
|
@ -35,7 +36,7 @@ type Dataview interface {
|
|||
GetDataview(blockID string) (*model.BlockContentDataview, error)
|
||||
|
||||
DeleteView(ctx session.Context, blockId string, viewId string, showEvent bool) error
|
||||
SetActiveView(ctx session.Context, blockId string, activeViewId string, limit int, offset int) error
|
||||
SetActiveView(ctx session.Context, blockId string, activeViewId string) error
|
||||
CreateView(ctx session.Context, blockID string,
|
||||
view model.BlockContentDataviewView, source []string) (*model.BlockContentDataviewView, error)
|
||||
SetViewPosition(ctx session.Context, blockId string, viewId string, position uint32) error
|
||||
|
@ -55,6 +56,7 @@ func NewDataview(sb smartblock.SmartBlock, objectStore objectstore.ObjectStore)
|
|||
objectStore: objectStore,
|
||||
}
|
||||
sb.AddHook(dv.checkDVBlocks, smartblock.HookBeforeApply)
|
||||
sb.AddHook(dv.injectActiveViews, smartblock.HookBeforeApply)
|
||||
return dv
|
||||
}
|
||||
|
||||
|
@ -201,7 +203,7 @@ func (d *sdataview) UpdateView(ctx session.Context, blockID string, viewID strin
|
|||
return d.Apply(s, smartblock.NoEvent)
|
||||
}
|
||||
|
||||
func (d *sdataview) SetActiveView(ctx session.Context, id string, activeViewId string, limit int, offset int) error {
|
||||
func (d *sdataview) SetActiveView(ctx session.Context, id string, activeViewId string) error {
|
||||
s := d.NewStateCtx(ctx)
|
||||
|
||||
dvBlock, err := getDataviewBlock(s, id)
|
||||
|
@ -214,8 +216,12 @@ func (d *sdataview) SetActiveView(ctx session.Context, id string, activeViewId s
|
|||
}
|
||||
dvBlock.SetActiveView(activeViewId)
|
||||
|
||||
if err = d.objectStore.SetActiveView(d.Id(), id, activeViewId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.SmartBlock.CheckSubscriptions()
|
||||
return d.Apply(s)
|
||||
return d.Apply(s, smartblock.NoHooks)
|
||||
}
|
||||
|
||||
func (d *sdataview) SetViewPosition(ctx session.Context, blockId string, viewId string, position uint32) (err error) {
|
||||
|
@ -419,6 +425,34 @@ func (d *sdataview) checkDVBlocks(info smartblock.ApplyInfo) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func (d *sdataview) injectActiveViews(info smartblock.ApplyInfo) (err error) {
|
||||
s := info.State
|
||||
views, err := d.objectStore.GetActiveViews(d.Id())
|
||||
if badgerhelper.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
log.With("objectId", s.RootId()).Warnf("failed to get list of active views from store: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for blockId, viewId := range views {
|
||||
b := s.Pick(blockId)
|
||||
if b == nil {
|
||||
log.With("objectId", s.RootId()).Warnf("failed to get block '%s' to inject active view", blockId)
|
||||
continue
|
||||
}
|
||||
dv := b.Model().GetDataview()
|
||||
if dv == nil {
|
||||
log.With("objectId", s.RootId()).Warnf("block '%s' is not dataview, so cannot inject active view", blockId)
|
||||
continue
|
||||
}
|
||||
dv.ActiveView = viewId
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDataviewBlock(s *state.State, id string) (dataview.Block, error) {
|
||||
b := s.Get(id)
|
||||
if b == nil {
|
||||
|
|
|
@ -1,24 +1,52 @@
|
|||
package dataview
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/smartblock"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/smartblock/smarttest"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/state"
|
||||
"github.com/anyproto/anytype-heart/core/block/simple"
|
||||
"github.com/anyproto/anytype-heart/core/block/simple/dataview"
|
||||
"github.com/anyproto/anytype-heart/core/session"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/objectstore/mock_objectstore"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
)
|
||||
|
||||
const objId = "root"
|
||||
|
||||
type fixture struct {
|
||||
store *mock_objectstore.MockObjectStore
|
||||
sb *smarttest.SmartTest
|
||||
|
||||
*sdataview
|
||||
}
|
||||
|
||||
func newFixture(t *testing.T) *fixture {
|
||||
store := mock_objectstore.NewMockObjectStore(t)
|
||||
sb := smarttest.New(objId)
|
||||
|
||||
dv := NewDataview(sb, store).(*sdataview)
|
||||
|
||||
return &fixture{
|
||||
store: store,
|
||||
sb: sb,
|
||||
sdataview: dv,
|
||||
}
|
||||
}
|
||||
|
||||
func TestDataviewCollectionImpl_SetViewPosition(t *testing.T) {
|
||||
newTestDv := func() (Dataview, *smarttest.SmartTest) {
|
||||
sb := smarttest.New("root")
|
||||
sbs := sb.Doc.(*state.State)
|
||||
sbs.Add(simple.New(&model.Block{Id: "root", ChildrenIds: []string{"dv"}}))
|
||||
fx := newFixture(t)
|
||||
sbs := fx.sb.Doc.(*state.State)
|
||||
sbs.Add(simple.New(&model.Block{Id: objId, ChildrenIds: []string{"dv"}}))
|
||||
sbs.Add(simple.New(&model.Block{Id: "dv", Content: &model.BlockContentOfDataview{
|
||||
Dataview: &model.BlockContentDataview{
|
||||
Views: []*model.BlockContentDataviewView{
|
||||
|
@ -28,8 +56,8 @@ func TestDataviewCollectionImpl_SetViewPosition(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}}))
|
||||
|
||||
return NewDataview(sb, nil), sb
|
||||
fx.store.EXPECT().GetActiveViews(mock.Anything).Return(nil, nil).Maybe()
|
||||
return fx.sdataview, fx.sb
|
||||
}
|
||||
assertViewPositions := func(viewId string, pos uint32, exp []string) {
|
||||
dv, sb := newTestDv()
|
||||
|
@ -49,3 +77,81 @@ func TestDataviewCollectionImpl_SetViewPosition(t *testing.T) {
|
|||
assertViewPositions("1", 0, []string{"1", "2", "3"})
|
||||
assertViewPositions("1", 42, []string{"2", "3", "1"})
|
||||
}
|
||||
|
||||
func TestInjectActiveView(t *testing.T) {
|
||||
dv1 := "dataview1"
|
||||
dv2 := "dataview2"
|
||||
dv3 := "dataview3"
|
||||
|
||||
getInfo := func() smartblock.ApplyInfo {
|
||||
st := state.NewDoc(objId, map[string]simple.Block{
|
||||
objId: simple.New(&model.Block{Id: objId, ChildrenIds: []string{dv1, dv2, dv3}}),
|
||||
dv1: dataview.NewDataview(&model.Block{
|
||||
Id: dv1,
|
||||
Content: &model.BlockContentOfDataview{Dataview: &model.BlockContentDataview{}},
|
||||
}),
|
||||
dv2: dataview.NewDataview(&model.Block{
|
||||
Id: dv2,
|
||||
Content: &model.BlockContentOfDataview{Dataview: &model.BlockContentDataview{}},
|
||||
}),
|
||||
dv3: dataview.NewDataview(&model.Block{
|
||||
Id: dv3,
|
||||
Content: &model.BlockContentOfDataview{Dataview: &model.BlockContentDataview{}},
|
||||
}),
|
||||
}).(*state.State)
|
||||
return smartblock.ApplyInfo{State: st}
|
||||
}
|
||||
|
||||
t.Run("inject active views to dataview blocks", func(t *testing.T) {
|
||||
// given
|
||||
blocksToView := map[string]string{dv1: "view1", dv2: "view2"}
|
||||
fx := newFixture(t)
|
||||
fx.store.EXPECT().GetActiveViews(mock.Anything).RunAndReturn(func(id string) (map[string]string, error) {
|
||||
assert.Equal(t, objId, id)
|
||||
return blocksToView, nil
|
||||
})
|
||||
info := getInfo()
|
||||
|
||||
// when
|
||||
err := fx.injectActiveViews(info)
|
||||
st := info.State
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, blocksToView[dv1], st.Pick(dv1).Model().GetDataview().ActiveView)
|
||||
assert.Equal(t, blocksToView[dv2], st.Pick(dv2).Model().GetDataview().ActiveView)
|
||||
assert.Empty(t, st.Pick(dv3).Model().GetDataview().ActiveView)
|
||||
})
|
||||
|
||||
t.Run("do nothing if active views are not found in DB", func(t *testing.T) {
|
||||
// given
|
||||
fx := newFixture(t)
|
||||
fx.store.EXPECT().GetActiveViews(mock.Anything).RunAndReturn(func(id string) (map[string]string, error) {
|
||||
assert.Equal(t, objId, id)
|
||||
return nil, badger.ErrKeyNotFound
|
||||
})
|
||||
info := getInfo()
|
||||
|
||||
// when
|
||||
err := fx.injectActiveViews(info)
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("fail on other DB error", func(t *testing.T) {
|
||||
// given
|
||||
fx := newFixture(t)
|
||||
fx.store.EXPECT().GetActiveViews(mock.Anything).RunAndReturn(func(id string) (map[string]string, error) {
|
||||
assert.Equal(t, objId, id)
|
||||
return nil, errors.New("badger was stolen by UFO")
|
||||
})
|
||||
info := getInfo()
|
||||
|
||||
// when
|
||||
err := fx.injectActiveViews(info)
|
||||
|
||||
// then
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -688,32 +688,33 @@ func (e *export) cleanupFile(wr writer) {
|
|||
}
|
||||
|
||||
func (e *export) getRelatedDerivedObjects(objects map[string]*types.Struct) ([]database.Record, error) {
|
||||
derivedObjects, err := e.iterateObjects(objects)
|
||||
derivedObjects, typesAndTemplates, err := e.iterateObjects(objects)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(derivedObjects) > 0 {
|
||||
// get derived objects only from types and templates,
|
||||
// because relations currently have only system relations and object type
|
||||
if len(typesAndTemplates) > 0 {
|
||||
derivedObjectsMap := make(map[string]*types.Struct, 0)
|
||||
for _, object := range derivedObjects {
|
||||
for _, object := range typesAndTemplates {
|
||||
id := object.Get(bundle.RelationKeyId.String()).GetStringValue()
|
||||
derivedObjectsMap[id] = object.Details
|
||||
}
|
||||
iteratedObjects, err := e.iterateObjects(derivedObjectsMap)
|
||||
iteratedObjects, typesAndTemplates, err := e.iterateObjects(derivedObjectsMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
derivedObjects = append(derivedObjects, iteratedObjects...)
|
||||
derivedObjects = append(derivedObjects, typesAndTemplates...)
|
||||
}
|
||||
return derivedObjects, nil
|
||||
}
|
||||
|
||||
func (e *export) iterateObjects(objects map[string]*types.Struct) ([]database.Record, error) {
|
||||
var (
|
||||
derivedObjects []database.Record
|
||||
relations []string
|
||||
)
|
||||
func (e *export) iterateObjects(objects map[string]*types.Struct,
|
||||
) (allObjects []database.Record, typesAndTemplates []database.Record, err error) {
|
||||
var relations []string
|
||||
for id, object := range objects {
|
||||
err := cache.Do(e.picker, id, func(b sb.SmartBlock) error {
|
||||
err = cache.Do(e.picker, id, func(b sb.SmartBlock) error {
|
||||
state := b.NewState()
|
||||
relations = e.getObjectRelations(state, relations)
|
||||
details := state.Details()
|
||||
|
@ -727,14 +728,14 @@ func (e *export) iterateObjects(objects map[string]*types.Struct) ([]database.Re
|
|||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
derivedObjects, err = e.processObject(object, derivedObjects, relations)
|
||||
allObjects, typesAndTemplates, err = e.processObject(object, allObjects, typesAndTemplates, relations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
return derivedObjects, nil
|
||||
return allObjects, typesAndTemplates, nil
|
||||
}
|
||||
|
||||
func (e *export) getDataviewRelations(state *state.State) ([]string, error) {
|
||||
|
@ -767,60 +768,69 @@ func (e *export) isObjectWithDataview(details *types.Struct) bool {
|
|||
|
||||
func (e *export) processObject(object *types.Struct,
|
||||
derivedObjects []database.Record,
|
||||
typesAndTemplates []database.Record,
|
||||
relations []string,
|
||||
) ([]database.Record, error) {
|
||||
) ([]database.Record, []database.Record, error) {
|
||||
for _, relation := range relations {
|
||||
storeRelation, err := e.getRelation(relation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
if storeRelation != nil {
|
||||
derivedObjects, err = e.addRelationAndOptions(storeRelation, derivedObjects, relation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
objectTypeId := pbtypes.GetString(object, bundle.RelationKeyType.String())
|
||||
|
||||
derivedObjects, err := e.addObjectType(objectTypeId, derivedObjects)
|
||||
var err error
|
||||
derivedObjects, typesAndTemplates, err = e.addObjectType(objectTypeId, derivedObjects, typesAndTemplates)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
derivedObjects, err = e.addTemplates(objectTypeId, derivedObjects)
|
||||
derivedObjects, typesAndTemplates, err = e.addTemplates(objectTypeId, derivedObjects, typesAndTemplates)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
return e.handleSetOfRelation(object, derivedObjects)
|
||||
derivedObjects, err = e.handleSetOfRelation(object, derivedObjects)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return derivedObjects, typesAndTemplates, nil
|
||||
}
|
||||
|
||||
func (e *export) addObjectType(objectTypeId string, derivedObjects []database.Record) ([]database.Record, error) {
|
||||
func (e *export) addObjectType(objectTypeId string, derivedObjects []database.Record, typesAndTemplates []database.Record) ([]database.Record, []database.Record, error) {
|
||||
objectTypeDetails, err := e.objectStore.GetDetails(objectTypeId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
if objectTypeDetails == nil || objectTypeDetails.Details == nil || len(objectTypeDetails.Details.Fields) == 0 {
|
||||
return derivedObjects, nil
|
||||
return derivedObjects, typesAndTemplates, nil
|
||||
}
|
||||
uniqueKey := pbtypes.GetString(objectTypeDetails.Details, bundle.RelationKeyUniqueKey.String())
|
||||
key, err := domain.GetTypeKeyFromRawUniqueKey(uniqueKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
if bundle.IsInternalType(key) {
|
||||
return derivedObjects, nil
|
||||
return derivedObjects, typesAndTemplates, nil
|
||||
}
|
||||
recommendedRelations := pbtypes.GetStringList(objectTypeDetails.Details, bundle.RelationKeyRecommendedRelations.String())
|
||||
for _, relation := range recommendedRelations {
|
||||
if relation == addr.MissingObject {
|
||||
continue
|
||||
}
|
||||
details, err := e.objectStore.GetDetails(relation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
relationKey := pbtypes.GetString(details.Details, bundle.RelationKeyUniqueKey.String())
|
||||
uniqueKey, err := domain.UnmarshalUniqueKey(relationKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
if bundle.IsSystemRelation(domain.RelationKey(uniqueKey.InternalKey())) {
|
||||
continue
|
||||
|
@ -828,7 +838,8 @@ func (e *export) addObjectType(objectTypeId string, derivedObjects []database.Re
|
|||
derivedObjects = append(derivedObjects, database.Record{Details: details.Details})
|
||||
}
|
||||
derivedObjects = append(derivedObjects, database.Record{Details: objectTypeDetails.Details})
|
||||
return derivedObjects, nil
|
||||
typesAndTemplates = append(typesAndTemplates, database.Record{Details: objectTypeDetails.Details})
|
||||
return derivedObjects, typesAndTemplates, nil
|
||||
}
|
||||
|
||||
func (e *export) getRelation(key string) (*database.Record, error) {
|
||||
|
@ -918,7 +929,7 @@ func (e *export) getRelationOptions(relationKey string) ([]database.Record, erro
|
|||
return relationOptionsDetails, nil
|
||||
}
|
||||
|
||||
func (e *export) addTemplates(id string, derivedObjects []database.Record) ([]database.Record, error) {
|
||||
func (e *export) addTemplates(id string, derivedObjects []database.Record, typesAndTemplates []database.Record) ([]database.Record, []database.Record, error) {
|
||||
templates, err := e.objectStore.Query(database.Query{
|
||||
Filters: []*model.BlockContentDataviewFilter{
|
||||
{
|
||||
|
@ -939,10 +950,11 @@ func (e *export) addTemplates(id string, derivedObjects []database.Record) ([]da
|
|||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
derivedObjects = append(derivedObjects, templates...)
|
||||
return derivedObjects, nil
|
||||
typesAndTemplates = append(typesAndTemplates, templates...)
|
||||
return derivedObjects, typesAndTemplates, nil
|
||||
}
|
||||
|
||||
func (e *export) handleSetOfRelation(object *types.Struct, derivedObjects []database.Record) ([]database.Record, error) {
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/anyproto/anytype-heart/pb"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/core/smartblock"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/addr"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/objectstore"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
"github.com/anyproto/anytype-heart/space/spacecore/typeprovider/mock_typeprovider"
|
||||
|
@ -184,7 +185,6 @@ func Test_docsForExport(t *testing.T) {
|
|||
objectGetter := mock_cache.NewMockObjectGetter(t)
|
||||
|
||||
smartBlockTest := smarttest.New("id")
|
||||
smartBlockRelation := smarttest.New("key")
|
||||
doc := smartBlockTest.NewState().SetDetails(&types.Struct{
|
||||
Fields: map[string]*types.Value{
|
||||
bundle.RelationKeyId.String(): pbtypes.String("id"),
|
||||
|
@ -201,7 +201,6 @@ func Test_docsForExport(t *testing.T) {
|
|||
smartBlockTest.Doc = doc
|
||||
|
||||
objectGetter.EXPECT().GetObject(context.Background(), "id").Return(smartBlockTest, nil)
|
||||
objectGetter.EXPECT().GetObject(context.Background(), "key").Return(smartBlockRelation, nil)
|
||||
|
||||
e := &export{
|
||||
objectStore: storeFixture,
|
||||
|
@ -244,7 +243,6 @@ func Test_docsForExport(t *testing.T) {
|
|||
objectGetter := mock_cache.NewMockObjectGetter(t)
|
||||
|
||||
smartBlockTest := smarttest.New("id")
|
||||
smartBlockRelation := smarttest.New(relationKey)
|
||||
doc := smartBlockTest.NewState().SetDetails(&types.Struct{
|
||||
Fields: map[string]*types.Value{
|
||||
bundle.RelationKeyId.String(): pbtypes.String("id"),
|
||||
|
@ -261,7 +259,6 @@ func Test_docsForExport(t *testing.T) {
|
|||
smartBlockTest.Doc = doc
|
||||
|
||||
objectGetter.EXPECT().GetObject(context.Background(), "id").Return(smartBlockTest, nil)
|
||||
objectGetter.EXPECT().GetObject(context.Background(), relationKey).Return(smartBlockRelation, nil)
|
||||
|
||||
e := &export{
|
||||
objectStore: storeFixture,
|
||||
|
@ -313,8 +310,6 @@ func Test_docsForExport(t *testing.T) {
|
|||
objectGetter := mock_cache.NewMockObjectGetter(t)
|
||||
|
||||
smartBlockTest := smarttest.New("id")
|
||||
smartBlockRelation := smarttest.New(relationKey)
|
||||
smartBlockRelationOption := smarttest.New(optionId)
|
||||
doc := smartBlockTest.NewState().SetDetails(&types.Struct{
|
||||
Fields: map[string]*types.Value{
|
||||
bundle.RelationKeyId.String(): pbtypes.String("id"),
|
||||
|
@ -331,8 +326,6 @@ func Test_docsForExport(t *testing.T) {
|
|||
smartBlockTest.Doc = doc
|
||||
|
||||
objectGetter.EXPECT().GetObject(context.Background(), "id").Return(smartBlockTest, nil)
|
||||
objectGetter.EXPECT().GetObject(context.Background(), relationKey).Return(smartBlockRelation, nil)
|
||||
objectGetter.EXPECT().GetObject(context.Background(), optionId).Return(smartBlockRelationOption, nil)
|
||||
|
||||
e := &export{
|
||||
objectStore: storeFixture,
|
||||
|
@ -355,4 +348,147 @@ func Test_docsForExport(t *testing.T) {
|
|||
}
|
||||
assert.Contains(t, objectIds, optionId)
|
||||
})
|
||||
t.Run("get derived objects - relation, object type with recommended relations, template with link", func(t *testing.T) {
|
||||
// given
|
||||
storeFixture := objectstore.NewStoreFixture(t)
|
||||
relationKey := "key"
|
||||
objectTypeKey := "customObjectType"
|
||||
objectTypeUniqueKey, err := domain.NewUniqueKey(smartblock.SmartBlockTypeObjectType, objectTypeKey)
|
||||
assert.Nil(t, err)
|
||||
uniqueKey, err := domain.NewUniqueKey(smartblock.SmartBlockTypeRelation, relationKey)
|
||||
assert.Nil(t, err)
|
||||
|
||||
recommendedRelationKey := "recommendedRelationKey"
|
||||
recommendedRelationUniqueKey, err := domain.NewUniqueKey(smartblock.SmartBlockTypeRelation, recommendedRelationKey)
|
||||
assert.Nil(t, err)
|
||||
|
||||
templateId := "templateId"
|
||||
|
||||
linkedObjectId := "linkedObjectId"
|
||||
storeFixture.AddObjects(t, []objectstore.TestObject{
|
||||
{
|
||||
bundle.RelationKeyId: pbtypes.String("id"),
|
||||
domain.RelationKey(relationKey): pbtypes.String("test"),
|
||||
bundle.RelationKeyType: pbtypes.String(objectTypeKey),
|
||||
},
|
||||
{
|
||||
bundle.RelationKeyId: pbtypes.String(relationKey),
|
||||
bundle.RelationKeyRelationKey: pbtypes.String(relationKey),
|
||||
bundle.RelationKeyUniqueKey: pbtypes.String(uniqueKey.Marshal()),
|
||||
bundle.RelationKeyLayout: pbtypes.Int64(int64(model.ObjectType_relation)),
|
||||
},
|
||||
{
|
||||
bundle.RelationKeyId: pbtypes.String(objectTypeKey),
|
||||
bundle.RelationKeyUniqueKey: pbtypes.String(objectTypeUniqueKey.Marshal()),
|
||||
bundle.RelationKeyLayout: pbtypes.Int64(int64(model.ObjectType_objectType)),
|
||||
bundle.RelationKeyRecommendedRelations: pbtypes.StringList([]string{recommendedRelationKey}),
|
||||
},
|
||||
{
|
||||
bundle.RelationKeyId: pbtypes.String(recommendedRelationKey),
|
||||
bundle.RelationKeyRelationKey: pbtypes.String(recommendedRelationKey),
|
||||
bundle.RelationKeyUniqueKey: pbtypes.String(recommendedRelationUniqueKey.Marshal()),
|
||||
bundle.RelationKeyLayout: pbtypes.Int64(int64(model.ObjectType_relation)),
|
||||
},
|
||||
{
|
||||
bundle.RelationKeyId: pbtypes.String(templateId),
|
||||
bundle.RelationKeyTargetObjectType: pbtypes.String(objectTypeKey),
|
||||
},
|
||||
{
|
||||
bundle.RelationKeyId: pbtypes.String(linkedObjectId),
|
||||
bundle.RelationKeyType: pbtypes.String(objectTypeKey),
|
||||
},
|
||||
})
|
||||
|
||||
err = storeFixture.UpdateObjectLinks(templateId, []string{linkedObjectId})
|
||||
assert.Nil(t, err)
|
||||
|
||||
objectGetter := mock_cache.NewMockObjectGetter(t)
|
||||
|
||||
smartBlockTest := smarttest.New("id")
|
||||
smartBlockTemplate := smarttest.New(templateId)
|
||||
smartBlockObjectType := smarttest.New(objectTypeKey)
|
||||
doc := smartBlockTest.NewState().SetDetails(&types.Struct{
|
||||
Fields: map[string]*types.Value{
|
||||
bundle.RelationKeyId.String(): pbtypes.String("id"),
|
||||
relationKey: pbtypes.String("value"),
|
||||
bundle.RelationKeyType.String(): pbtypes.String("objectType"),
|
||||
}})
|
||||
doc.AddRelationLinks(&model.RelationLink{
|
||||
Key: bundle.RelationKeyId.String(),
|
||||
Format: model.RelationFormat_longtext,
|
||||
}, &model.RelationLink{
|
||||
Key: relationKey,
|
||||
Format: model.RelationFormat_tag,
|
||||
})
|
||||
smartBlockTest.Doc = doc
|
||||
|
||||
objectGetter.EXPECT().GetObject(context.Background(), "id").Return(smartBlockTest, nil)
|
||||
objectGetter.EXPECT().GetObject(context.Background(), templateId).Return(smartBlockTemplate, nil)
|
||||
objectGetter.EXPECT().GetObject(context.Background(), objectTypeKey).Return(smartBlockObjectType, nil)
|
||||
|
||||
provider := mock_typeprovider.NewMockSmartBlockTypeProvider(t)
|
||||
provider.EXPECT().Type("spaceId", linkedObjectId).Return(smartblock.SmartBlockTypePage, nil)
|
||||
|
||||
e := &export{
|
||||
objectStore: storeFixture,
|
||||
picker: objectGetter,
|
||||
sbtProvider: provider,
|
||||
}
|
||||
|
||||
// when
|
||||
docsForExport, err := e.docsForExport("spaceId", pb.RpcObjectListExportRequest{
|
||||
SpaceId: "spaceId",
|
||||
ObjectIds: []string{"id"},
|
||||
Format: model.Export_Protobuf,
|
||||
IncludeNested: true,
|
||||
})
|
||||
|
||||
// then
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 6, len(docsForExport))
|
||||
})
|
||||
t.Run("get derived objects, object type have missing relations - return only object and its type", func(t *testing.T) {
|
||||
// given
|
||||
storeFixture := objectstore.NewStoreFixture(t)
|
||||
objectTypeKey := "customObjectType"
|
||||
objectTypeUniqueKey, err := domain.NewUniqueKey(smartblock.SmartBlockTypeObjectType, objectTypeKey)
|
||||
assert.Nil(t, err)
|
||||
|
||||
storeFixture.AddObjects(t, []objectstore.TestObject{
|
||||
{
|
||||
bundle.RelationKeyId: pbtypes.String("id"),
|
||||
bundle.RelationKeyType: pbtypes.String(objectTypeKey),
|
||||
},
|
||||
{
|
||||
bundle.RelationKeyId: pbtypes.String(objectTypeKey),
|
||||
bundle.RelationKeyUniqueKey: pbtypes.String(objectTypeUniqueKey.Marshal()),
|
||||
bundle.RelationKeyLayout: pbtypes.Int64(int64(model.ObjectType_objectType)),
|
||||
bundle.RelationKeyRecommendedRelations: pbtypes.StringList([]string{addr.MissingObject}),
|
||||
},
|
||||
})
|
||||
|
||||
objectGetter := mock_cache.NewMockObjectGetter(t)
|
||||
|
||||
smartBlockTest := smarttest.New("id")
|
||||
smartBlockObjectType := smarttest.New(objectTypeKey)
|
||||
|
||||
objectGetter.EXPECT().GetObject(context.Background(), "id").Return(smartBlockTest, nil)
|
||||
objectGetter.EXPECT().GetObject(context.Background(), objectTypeKey).Return(smartBlockObjectType, nil)
|
||||
|
||||
e := &export{
|
||||
objectStore: storeFixture,
|
||||
picker: objectGetter,
|
||||
}
|
||||
|
||||
// when
|
||||
docsForExport, err := e.docsForExport("spaceId", pb.RpcObjectListExportRequest{
|
||||
SpaceId: "spaceId",
|
||||
ObjectIds: []string{"id"},
|
||||
Format: model.Export_Protobuf,
|
||||
})
|
||||
|
||||
// then
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(docsForExport))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ import (
|
|||
"github.com/globalsign/mgo/bson"
|
||||
"github.com/gogo/protobuf/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/yuin/goldmark/ast"
|
||||
"github.com/yuin/goldmark/text"
|
||||
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
|
@ -235,3 +237,54 @@ func TestCloseTextBlock(t *testing.T) {
|
|||
assert.Equal(t, "id2", renderer.blocks[1].ChildrenIds[0])
|
||||
})
|
||||
}
|
||||
|
||||
func Test_renderCodeBloc(t *testing.T) {
|
||||
t.Run("simple case", func(t *testing.T) {
|
||||
// given
|
||||
r := NewRenderer(newBlocksRenderer("", nil, false))
|
||||
node := ast.NewCodeBlock()
|
||||
segments := text.NewSegments()
|
||||
segments.Append(text.Segment{
|
||||
Start: 0,
|
||||
Stop: 4,
|
||||
})
|
||||
node.SetLines(segments)
|
||||
|
||||
// when
|
||||
_, err := r.renderCodeBlock(nil, []byte("test"), node, true)
|
||||
assert.Nil(t, err)
|
||||
_, err = r.renderCodeBlock(nil, []byte("test"), node, false)
|
||||
|
||||
// then
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, r.blocks, 1)
|
||||
assert.Equal(t, "test", r.blocks[0].GetText().GetText())
|
||||
assert.Equal(t, r.blocks[0].GetText().GetStyle(), model.BlockContentText_Code)
|
||||
})
|
||||
t.Run("2 lines", func(t *testing.T) {
|
||||
// given
|
||||
r := NewRenderer(newBlocksRenderer("", nil, false))
|
||||
node := ast.NewCodeBlock()
|
||||
segments := text.NewSegments()
|
||||
segments.Append(text.Segment{
|
||||
Start: 0,
|
||||
Stop: 5,
|
||||
})
|
||||
segments.Append(text.Segment{
|
||||
Start: 5,
|
||||
Stop: 8,
|
||||
})
|
||||
node.SetLines(segments)
|
||||
|
||||
// when
|
||||
_, err := r.renderCodeBlock(nil, []byte("testtest"), node, true)
|
||||
assert.Nil(t, err)
|
||||
_, err = r.renderCodeBlock(nil, []byte("testtest"), node, false)
|
||||
|
||||
// then
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, r.blocks, 1)
|
||||
assert.Equal(t, "testtest", r.blocks[0].GetText().GetText())
|
||||
assert.Equal(t, model.BlockContentText_Code, r.blocks[0].GetText().GetStyle())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -192,11 +192,8 @@ func getCustomHTMLRules() []html2md.Rule {
|
|||
img := html2md.Rule{
|
||||
Filter: []string{"img"},
|
||||
Replacement: func(content string, selec *goquery.Selection, options *html2md.Options) *string {
|
||||
var (
|
||||
src, title string
|
||||
ok bool
|
||||
)
|
||||
if src, ok = selec.Attr("src"); !ok {
|
||||
var src, title string
|
||||
if src = extractImageSource(selec); src == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -233,6 +230,19 @@ func getCustomHTMLRules() []html2md.Rule {
|
|||
simpleText, blockquote, italic, code, bdo, div, img, table}
|
||||
}
|
||||
|
||||
func extractImageSource(selec *goquery.Selection) string {
|
||||
var (
|
||||
src string
|
||||
ok bool
|
||||
)
|
||||
if src, ok = selec.Attr("src"); !ok || src == "" {
|
||||
if src, ok = selec.Attr("data-src"); !ok || src == "" {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return src
|
||||
}
|
||||
|
||||
func addHeaderRow(content string, numberOfCells int, numberOfRows int) string {
|
||||
numberOfColumns := numberOfCells / numberOfRows
|
||||
|
||||
|
|
|
@ -121,6 +121,9 @@ func (r *Renderer) renderCodeBlock(_ util.BufWriter,
|
|||
n ast.Node,
|
||||
entering bool) (ast.WalkStatus, error) {
|
||||
r.openTextBlockWithStyle(entering, model.BlockContentText_Code, nil)
|
||||
if entering {
|
||||
r.writeLines(source, n)
|
||||
}
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -217,8 +217,23 @@
|
|||
"blocks": [{"id":"1","Content":{"file":{"name":"http://static.com/image.png","type":2}}}]
|
||||
},
|
||||
{
|
||||
"desc": "section linm",
|
||||
"desc": "section link",
|
||||
"html": "<img class=\"logo-icon\" src=\"#description\" alt=\"\">",
|
||||
"blocks": [{"id":"1","Content":{"file":{"name":"http://test.com/test#description","type":2}}}]
|
||||
},
|
||||
{
|
||||
"desc": "image with data-src atr",
|
||||
"html": "<img class=\"logo-icon\" data-src=\"/static/image.png\" alt=\"\">",
|
||||
"blocks": [{"id":"1","Content":{"file":{"name":"http://test.com/static/image.png","type":2}}}]
|
||||
},
|
||||
{
|
||||
"desc": "image without src",
|
||||
"html": "<img class=\"logo-icon\" alt=\"\">",
|
||||
"blocks": null
|
||||
},
|
||||
{
|
||||
"desc": "image with empty src",
|
||||
"html": "<img class=\"logo-icon\" src=\"\" data-src=\"\" alt=\"\">",
|
||||
"blocks": null
|
||||
}
|
||||
]
|
|
@ -104,8 +104,6 @@ func (s *service) InstallBundledObjects(
|
|||
objects = append(objects, newDetails)
|
||||
}
|
||||
}
|
||||
|
||||
s.reviseSystemObjects(space, existingObjectMap)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -388,7 +388,7 @@ func (s *service) addFileNode(ctx context.Context, spaceID string, mill m.Mill,
|
|||
return newExistingFileResult(variant)
|
||||
}
|
||||
|
||||
res, err := mill.Mill(conf.Reader, conf.Name, conf.checksum)
|
||||
res, err := mill.Mill(conf.Reader, conf.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", m.ErrProcessing, err)
|
||||
}
|
||||
|
@ -401,7 +401,12 @@ func (s *service) addFileNode(ctx context.Context, spaceID string, mill m.Mill,
|
|||
}
|
||||
|
||||
if variant, err := s.fileStore.GetFileVariantByChecksum(mill.ID(), variantChecksum); err == nil {
|
||||
return newExistingFileResult(variant)
|
||||
if variant.Source == conf.checksum {
|
||||
// we may have same variant checksum for different files
|
||||
// e.g. empty image exif with the same resolution
|
||||
// reuse the whole file only in case the checksum of the original file is the same
|
||||
return newExistingFileResult(variant)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = conf.Reader.Seek(0, io.SeekStart)
|
||||
|
@ -409,8 +414,7 @@ func (s *service) addFileNode(ctx context.Context, spaceID string, mill m.Mill,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// because mill result reader doesn't support seek we need to do the mill again
|
||||
res, err = mill.Mill(conf.Reader, conf.Name, conf.checksum)
|
||||
_, err = res.File.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -465,6 +469,10 @@ func (s *service) addFileNode(ctx context.Context, spaceID string, mill m.Mill,
|
|||
fileInfo.MetaHash = metaNode.Cid().String()
|
||||
|
||||
pairNode, err := s.addFilePairNode(ctx, spaceID, fileInfo)
|
||||
err = res.File.Close()
|
||||
if err != nil {
|
||||
log.Warnf("failed to close file: %s", err)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("add file pair node: %w", err)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package files
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -102,6 +104,52 @@ func TestImageAddWithCustomEncryptionKeys(t *testing.T) {
|
|||
assertCustomEncryptionKeys(t, fx, got, customKeys)
|
||||
}
|
||||
|
||||
func TestImageAddReuse(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
|
||||
f, err := os.Open("../../pkg/lib/mill/testdata/image.jpeg")
|
||||
require.NoError(t, err)
|
||||
defer f.Close()
|
||||
|
||||
fileName := "myFile"
|
||||
lastModifiedDate := time.Now()
|
||||
opts := []AddOption{
|
||||
WithName(fileName),
|
||||
WithLastModifiedDate(lastModifiedDate.Unix()),
|
||||
WithReader(f),
|
||||
}
|
||||
got1, err := fx.ImageAdd(context.Background(), spaceId, opts...)
|
||||
require.NoError(t, err)
|
||||
got1.Commit()
|
||||
|
||||
f.Seek(0, 0)
|
||||
got2, err := fx.ImageAdd(context.Background(), spaceId, opts...)
|
||||
require.NoError(t, err)
|
||||
got2.Commit()
|
||||
require.True(t, got2.IsExisting)
|
||||
require.Equal(t, got1.FileId.String(), got2.FileId.String())
|
||||
require.Equal(t, got1.EncryptionKeys.EncryptionKeys, got2.EncryptionKeys.EncryptionKeys)
|
||||
|
||||
b, err := io.ReadAll(f)
|
||||
require.NoError(t, err)
|
||||
b[10000] = 0x00
|
||||
// patch the original image so it will have the different source hash, but the same(empty) exif
|
||||
patchedReader := bytes.NewReader(b)
|
||||
opts = []AddOption{
|
||||
WithName(fileName),
|
||||
WithLastModifiedDate(lastModifiedDate.Unix()),
|
||||
WithReader(patchedReader),
|
||||
}
|
||||
// exif will be the same but images are different
|
||||
got3, err := fx.ImageAdd(context.Background(), spaceId, opts...)
|
||||
require.NoError(t, err)
|
||||
got3.Commit()
|
||||
fileId3 := got3.FileId.String()
|
||||
|
||||
require.NotEqual(t, got1.FileId.String(), fileId3)
|
||||
require.False(t, got3.IsExisting)
|
||||
}
|
||||
|
||||
func assertCustomEncryptionKeys(t *testing.T, fx *fixture, got *AddResult, customKeys map[string]string) {
|
||||
encKeys, err := fx.fileStore.GetFileKeys(got.FileId)
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -32,6 +32,7 @@ var log = logger.NewNamed(CName)
|
|||
var loopTimeout = time.Minute
|
||||
|
||||
type StatusCallback func(fileObjectId string) error
|
||||
type DeleteCallback func(fileObjectId domain.FullFileId)
|
||||
|
||||
type FileSync interface {
|
||||
AddFile(fileObjectId string, fileId domain.FullFileId, uploadedByUser, imported bool) (err error)
|
||||
|
@ -39,6 +40,7 @@ type FileSync interface {
|
|||
OnUploadStarted(StatusCallback)
|
||||
OnUploaded(StatusCallback)
|
||||
OnLimited(StatusCallback)
|
||||
OnDelete(DeleteCallback)
|
||||
DeleteFile(objectId string, fileId domain.FullFileId) (err error)
|
||||
DeleteFileSynchronously(fileId domain.FullFileId) (err error)
|
||||
UpdateNodeUsage(ctx context.Context) error
|
||||
|
@ -74,6 +76,7 @@ type fileSync struct {
|
|||
onUploaded StatusCallback
|
||||
onUploadStarted StatusCallback
|
||||
onLimited StatusCallback
|
||||
onDelete DeleteCallback
|
||||
|
||||
uploadingQueue *persistentqueue.Queue[*QueueItem]
|
||||
retryUploadingQueue *persistentqueue.Queue[*QueueItem]
|
||||
|
@ -121,6 +124,10 @@ func (s *fileSync) OnLimited(callback StatusCallback) {
|
|||
s.onLimited = callback
|
||||
}
|
||||
|
||||
func (s *fileSync) OnDelete(callback DeleteCallback) {
|
||||
s.onDelete = callback
|
||||
}
|
||||
|
||||
func (s *fileSync) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
|
|
|
@ -514,6 +514,39 @@ func (_c *MockFileSync_NodeUsage_Call) RunAndReturn(run func(context.Context) (f
|
|||
return _c
|
||||
}
|
||||
|
||||
// OnDelete provides a mock function with given fields: _a0
|
||||
func (_m *MockFileSync) OnDelete(_a0 filesync.DeleteCallback) {
|
||||
_m.Called(_a0)
|
||||
}
|
||||
|
||||
// MockFileSync_OnDelete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OnDelete'
|
||||
type MockFileSync_OnDelete_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// OnDelete is a helper method to define mock.On call
|
||||
// - _a0 filesync.DeleteCallback
|
||||
func (_e *MockFileSync_Expecter) OnDelete(_a0 interface{}) *MockFileSync_OnDelete_Call {
|
||||
return &MockFileSync_OnDelete_Call{Call: _e.mock.On("OnDelete", _a0)}
|
||||
}
|
||||
|
||||
func (_c *MockFileSync_OnDelete_Call) Run(run func(_a0 filesync.DeleteCallback)) *MockFileSync_OnDelete_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(filesync.DeleteCallback))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockFileSync_OnDelete_Call) Return() *MockFileSync_OnDelete_Call {
|
||||
_c.Call.Return()
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockFileSync_OnDelete_Call) RunAndReturn(run func(filesync.DeleteCallback)) *MockFileSync_OnDelete_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// OnLimited provides a mock function with given fields: _a0
|
||||
func (_m *MockFileSync) OnLimited(_a0 filesync.StatusCallback) {
|
||||
_m.Called(_a0)
|
||||
|
|
|
@ -46,6 +46,9 @@ func (s *fileSync) deletionHandler(ctx context.Context, it *QueueItem) (persiste
|
|||
if err != nil {
|
||||
log.Error("remove from deletion queues", zap.String("fileId", it.FileId.String()), zap.Error(err))
|
||||
}
|
||||
if s.onDelete != nil {
|
||||
s.onDelete(fileId)
|
||||
}
|
||||
return persistentqueue.ActionDone, nil
|
||||
}
|
||||
|
||||
|
@ -63,6 +66,9 @@ func (s *fileSync) retryDeletionHandler(ctx context.Context, it *QueueItem) (per
|
|||
if err != nil {
|
||||
log.Error("remove from deletion queues", zap.String("fileId", it.FileId.String()), zap.Error(err))
|
||||
}
|
||||
if s.onDelete != nil {
|
||||
s.onDelete(fileId)
|
||||
}
|
||||
return persistentqueue.ActionDone, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -133,7 +133,7 @@ func (s *ownProfileSubscription) run(ctx context.Context) (err error) {
|
|||
s.handleOwnProfileDetails(records[0].Details)
|
||||
}
|
||||
|
||||
s.fetchGlobalName()
|
||||
go s.fetchGlobalName(s.componentCtx, s.namingService)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
|
@ -205,8 +205,12 @@ func (s *ownProfileSubscription) handleOwnProfileDetails(profileDetails *types.S
|
|||
s.enqueuePush()
|
||||
}
|
||||
|
||||
func (s *ownProfileSubscription) fetchGlobalName() {
|
||||
response, err := s.namingService.GetNameByAnyId(s.componentCtx, &nameserviceproto.NameByAnyIdRequest{AnyAddress: s.myIdentity})
|
||||
func (s *ownProfileSubscription) fetchGlobalName(ctx context.Context, ns nameserviceclient.AnyNsClientService) {
|
||||
if ns == nil {
|
||||
log.Error("error fetching global name of our own identity from Naming Service as the service is not initialized")
|
||||
return
|
||||
}
|
||||
response, err := ns.GetNameByAnyId(ctx, &nameserviceproto.NameByAnyIdRequest{AnyAddress: s.myIdentity})
|
||||
if err != nil || response == nil {
|
||||
log.Error("error fetching global name of our own identity from Naming Service", zap.Error(err))
|
||||
return
|
||||
|
@ -215,11 +219,16 @@ func (s *ownProfileSubscription) fetchGlobalName() {
|
|||
log.Debug("globalName was not found for our own identity in Naming Service")
|
||||
return
|
||||
}
|
||||
s.handleGlobalNameUpdate(response.Name)
|
||||
s.updateGlobalName(response.Name)
|
||||
}
|
||||
|
||||
func (s *ownProfileSubscription) updateGlobalName(globalName string) {
|
||||
s.globalNameUpdatedCh <- globalName
|
||||
select {
|
||||
case <-s.componentCtx.Done():
|
||||
return
|
||||
case s.globalNameUpdatedCh <- globalName:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ownProfileSubscription) handleGlobalNameUpdate(globalName string) {
|
||||
|
|
|
@ -135,6 +135,8 @@ func TestOwnProfileSubscription(t *testing.T) {
|
|||
err := fx.run(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(testBatchTimeout / 4)
|
||||
|
||||
fx.objectStoreFixture.AddObjects(t, []objectstore.TestObject{
|
||||
{
|
||||
bundle.RelationKeyId: pbtypes.String(testProfileObjectId),
|
||||
|
@ -201,6 +203,8 @@ func TestOwnProfileSubscription(t *testing.T) {
|
|||
err := fx.run(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(testBatchTimeout / 4)
|
||||
|
||||
fx.updateGlobalName(newName)
|
||||
|
||||
time.Sleep(2 * testBatchTimeout)
|
||||
|
@ -254,6 +258,8 @@ func TestOwnProfileSubscription(t *testing.T) {
|
|||
err := fx.run(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(testBatchTimeout / 4)
|
||||
|
||||
fx.objectStoreFixture.AddObjects(t, []objectstore.TestObject{
|
||||
{
|
||||
bundle.RelationKeyId: pbtypes.String(testProfileObjectId),
|
||||
|
|
|
@ -33,7 +33,7 @@ func (mw *Middleware) LinkPreview(cctx context.Context, req *pb.RpcLinkPreviewRe
|
|||
}
|
||||
}
|
||||
lp := mw.applicationService.GetApp().MustComponent(linkpreview.CName).(linkpreview.LinkPreview)
|
||||
data, _, err := lp.Fetch(ctx, u.String())
|
||||
data, _, _, err := lp.Fetch(ctx, u.String())
|
||||
if err != nil {
|
||||
// trim the actual url from the error
|
||||
errTrimmed := strings.Replace(err.Error(), u.String(), "<url>", -1)
|
||||
|
|
|
@ -181,6 +181,7 @@ func (mw *Middleware) ObjectSearchWithMeta(cctx context.Context, req *pb.RpcObje
|
|||
rec.Details = pbtypes.StructFilterKeys(rec.Details, req.Keys)
|
||||
}
|
||||
resultsModels = append(resultsModels, &model.SearchResult{
|
||||
|
||||
ObjectId: pbtypes.GetString(rec.Details, database.RecordIDField),
|
||||
Details: rec.Details,
|
||||
Meta: []*model.SearchMeta{&(results[i].Meta)},
|
||||
|
|
|
@ -12,6 +12,13 @@ import (
|
|||
"github.com/anyproto/anytype-heart/pb"
|
||||
)
|
||||
|
||||
// Semantics in case of NO INTERNET:
|
||||
//
|
||||
// If called with req.NoCache -> returns error
|
||||
// If called without req.NoCache:
|
||||
//
|
||||
// has no fresh data -> returns error
|
||||
// has fresh data -> returns data
|
||||
func (mw *Middleware) MembershipGetStatus(ctx context.Context, req *pb.RpcMembershipGetStatusRequest) *pb.RpcMembershipGetStatusResponse {
|
||||
log.Info("payments - client asked to get a subscription status", zap.Any("req", req))
|
||||
|
||||
|
|
42
core/payments/cache/cache.go
vendored
42
core/payments/cache/cache.go
vendored
|
@ -31,9 +31,11 @@ var (
|
|||
|
||||
// once you change the cache format, you need to update this variable
|
||||
// it will cause cache to be dropped and recreated
|
||||
const LAST_CACHE_VERSION = 5
|
||||
const cacheLastVersion = 6
|
||||
|
||||
var dbKey = "payments/subscription/v" + strconv.Itoa(LAST_CACHE_VERSION)
|
||||
const cacheLifetimeDur = 24 * time.Hour
|
||||
|
||||
var dbKey = "payments/subscription/v" + strconv.Itoa(cacheLastVersion)
|
||||
|
||||
type StorageStruct struct {
|
||||
// not to migrate old storage to new format, but just to check the validity of the cache
|
||||
|
@ -44,7 +46,7 @@ type StorageStruct struct {
|
|||
// this variable is just for info
|
||||
LastUpdated time.Time
|
||||
|
||||
// depending on the type of the subscription the cache will have different lifetime
|
||||
// depending on the type of the membership the cache will have different lifetime
|
||||
// if current time is >= ExpireTime -> cache is expired
|
||||
ExpireTime time.Time
|
||||
|
||||
|
@ -58,7 +60,7 @@ type StorageStruct struct {
|
|||
|
||||
func newStorageStruct() *StorageStruct {
|
||||
return &StorageStruct{
|
||||
CurrentVersion: LAST_CACHE_VERSION,
|
||||
CurrentVersion: cacheLastVersion,
|
||||
LastUpdated: time.Now().UTC(),
|
||||
ExpireTime: time.Time{},
|
||||
DisableUntilTime: time.Time{},
|
||||
|
@ -79,7 +81,7 @@ type CacheService interface {
|
|||
// if cache is disabled -> will return no error
|
||||
// if cache is expired -> will return no error
|
||||
// status or tiers can be nil depending on what you want to update
|
||||
CacheSet(status *pb.RpcMembershipGetStatusResponse, tiers *pb.RpcMembershipGetTiersResponse, ExpireTime time.Time) (err error)
|
||||
CacheSet(status *pb.RpcMembershipGetStatusResponse, tiers *pb.RpcMembershipGetTiersResponse, subscriptionEnds time.Time) (err error)
|
||||
|
||||
IsCacheEnabled() (enabled bool)
|
||||
|
||||
|
@ -134,11 +136,11 @@ func (s *cacheservice) CacheGet() (status *pb.RpcMembershipGetStatusResponse, ti
|
|||
// 1 - check in storage
|
||||
ss, err := s.get()
|
||||
if err != nil {
|
||||
log.Error("can not get subscription status from cache", zap.Error(err))
|
||||
log.Error("can not get membership status from cache", zap.Error(err))
|
||||
return nil, nil, ErrCacheDbError
|
||||
}
|
||||
|
||||
if ss.CurrentVersion != LAST_CACHE_VERSION {
|
||||
if ss.CurrentVersion != cacheLastVersion {
|
||||
// currently we have only one version, but in future we can have more
|
||||
// this error can happen if you "downgrade" the app
|
||||
log.Error("unsupported cache version", zap.Uint16("version", ss.CurrentVersion))
|
||||
|
@ -161,7 +163,31 @@ func (s *cacheservice) CacheGet() (status *pb.RpcMembershipGetStatusResponse, ti
|
|||
return &ss.SubscriptionStatus, &ss.TiersData, nil
|
||||
}
|
||||
|
||||
func (s *cacheservice) CacheSet(status *pb.RpcMembershipGetStatusResponse, tiers *pb.RpcMembershipGetTiersResponse, expireTime time.Time) (err error) {
|
||||
func getCacheExpireTime(dateEnds time.Time) time.Time {
|
||||
// dateEnds can be 0
|
||||
isExpired := time.Now().UTC().After(dateEnds)
|
||||
|
||||
timeNow := time.Now().UTC()
|
||||
timeNext := timeNow.Add(cacheLifetimeDur)
|
||||
|
||||
// sub end < now OR no sub end provided (unlimited)
|
||||
if isExpired {
|
||||
log.Debug("incrementing cache lifetime because membership is isExpired")
|
||||
return timeNext
|
||||
}
|
||||
|
||||
// sub end >= now
|
||||
// return min(sub end, now + 24h)
|
||||
if dateEnds.Before(timeNext) {
|
||||
log.Debug("incrementing cache lifetime because membership ends soon")
|
||||
return dateEnds
|
||||
}
|
||||
return timeNext
|
||||
}
|
||||
|
||||
func (s *cacheservice) CacheSet(status *pb.RpcMembershipGetStatusResponse, tiers *pb.RpcMembershipGetTiersResponse, subscriptionEnds time.Time) (err error) {
|
||||
expireTime := getCacheExpireTime(subscriptionEnds)
|
||||
|
||||
// 1 - get existing storage
|
||||
ss, err := s.get()
|
||||
if err != nil {
|
||||
|
|
2
core/payments/cache/cache_test.go
vendored
2
core/payments/cache/cache_test.go
vendored
|
@ -381,6 +381,6 @@ func TestPayments_CacheSetSubscriptionStatus(t *testing.T) {
|
|||
require.Equal(t, nil, err)
|
||||
|
||||
_, _, err = fx.CacheGet()
|
||||
require.Equal(t, ErrCacheExpired, err)
|
||||
require.Equal(t, nil, err)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -170,8 +170,8 @@ func (s *service) Close(_ context.Context) (err error) {
|
|||
}
|
||||
|
||||
func (s *service) getPeriodicStatus(ctx context.Context) error {
|
||||
// get subscription status (from cache or from PP node)
|
||||
// if status is changed -> it will send an event
|
||||
// get subscription status (from cache or from the PP node)
|
||||
// if status has changed -> it will send an event
|
||||
log.Debug("periodic: getting subscription status from cache/PP node")
|
||||
|
||||
_, err := s.GetSubscriptionStatus(ctx, &pb.RpcMembershipGetStatusRequest{})
|
||||
|
@ -192,6 +192,18 @@ func (s *service) sendEvent(status *pb.RpcMembershipGetStatusResponse) {
|
|||
})
|
||||
}
|
||||
|
||||
// Logic:
|
||||
//
|
||||
// 1. Check in cache. if req.NoCache -> do not check in cache.
|
||||
// 2. If found in cache -> return it
|
||||
// 3. Ask PP node
|
||||
// 4a. If PP node didn't answer and we have membership -> return it
|
||||
// 4b. If PP node didn't answer -> create empty response
|
||||
// 5. Save to cache. Lifetime - min(subscription ends, 24h)
|
||||
// 6. If tier or status has changed -> send event
|
||||
// 7. If name has changed -> update global name
|
||||
// 8. UpdateLimits
|
||||
// 9. Enable cache again if status is active
|
||||
func (s *service) GetSubscriptionStatus(ctx context.Context, req *pb.RpcMembershipGetStatusRequest) (*pb.RpcMembershipGetStatusResponse, error) {
|
||||
s.mx.Lock()
|
||||
defer s.mx.Unlock()
|
||||
|
@ -200,16 +212,18 @@ func (s *service) GetSubscriptionStatus(ctx context.Context, req *pb.RpcMembersh
|
|||
privKey := s.wallet.GetAccountPrivkey()
|
||||
|
||||
// 1 - check in cache
|
||||
// tiers var. is unused here
|
||||
cachedStatus, tiers, err := s.cache.CacheGet()
|
||||
// if cache is disabled -> will return objects and ErrCacheDisabled
|
||||
// if cache is expired -> will return objects and ErrCacheExpired
|
||||
cachedStatus, _, err := s.cache.CacheGet()
|
||||
|
||||
// if NoCache -> skip returning from cache
|
||||
// if NoCache flag -> skip returning from cache
|
||||
if !req.NoCache && (err == nil) && (cachedStatus != nil) && (cachedStatus.Data != nil) {
|
||||
// 2. If found in cache -> return it
|
||||
log.Debug("returning subscription status from cache", zap.Error(err), zap.Any("cachedStatus", cachedStatus))
|
||||
return cachedStatus, nil
|
||||
}
|
||||
|
||||
// 2 - send request to PP node
|
||||
// 3 - send request to PP node
|
||||
gsr := proto.GetSubscriptionRequest{
|
||||
// payment node will check if signature matches with this OwnerAnyID
|
||||
OwnerAnyID: ownerID,
|
||||
|
@ -236,18 +250,14 @@ func (s *service) GetSubscriptionStatus(ctx context.Context, req *pb.RpcMembersh
|
|||
|
||||
status, err := s.ppclient.GetSubscriptionStatus(ctx, &reqSigned)
|
||||
if err != nil {
|
||||
// if we have non-standard tiers already -> then try not to overwrite cache please
|
||||
// but just return error
|
||||
if tiers != nil && tiers.Tiers != nil && len(tiers.Tiers) > 0 {
|
||||
if tiers.Tiers[0].Id != uint32(proto.SubscriptionTier_TierExplorer) {
|
||||
// return error
|
||||
log.Error("returning error in get status", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
// 4a. try reading from cache again
|
||||
if (cachedStatus != nil) && (cachedStatus.Data != nil) {
|
||||
log.Debug("returning subscription status from cache again", zap.Error(err), zap.Any("cachedStatus", cachedStatus))
|
||||
return cachedStatus, nil
|
||||
}
|
||||
|
||||
// if we have no tiers or standard tier -> overwrite cache and return no error please
|
||||
log.Info("creating empty subscription in cache because can not get subscription status from payment node")
|
||||
// 4b. If PP node didn't answer -> create empty response
|
||||
log.Info("creating empty subscription in cache because can not get subscription status from the payment node")
|
||||
|
||||
// eat error and create empty status ("no tier") so that we will then save it to the cache
|
||||
status = &proto.GetSubscriptionResponse{
|
||||
|
@ -273,22 +283,10 @@ func (s *service) GetSubscriptionStatus(ctx context.Context, req *pb.RpcMembersh
|
|||
out.Data.UserEmail = status.UserEmail
|
||||
out.Data.SubscribeToNewsletter = status.SubscribeToNewsletter
|
||||
|
||||
// 3 - save into cache
|
||||
// truncate nseconds here
|
||||
var cacheExpireTime time.Time = time.Unix(int64(status.DateEnds), 0)
|
||||
isExpired := time.Now().UTC().After(cacheExpireTime)
|
||||
|
||||
// if subscription DateEns is null - then default expire time is in 10 days
|
||||
// or until user clicks on a “Pay by card/crypto” or “Manage” button
|
||||
if status.DateEnds == 0 || isExpired {
|
||||
log.Debug("setting cache to +1 day because subscription is isExpired")
|
||||
|
||||
timeNow := time.Now().UTC()
|
||||
cacheExpireTime = timeNow.Add(1 * 24 * time.Hour)
|
||||
}
|
||||
|
||||
// 5. Save to cache. Lifetime - min(subscription ends, now + 24h)
|
||||
// update only status, not tiers
|
||||
err = s.cache.CacheSet(&out, nil, cacheExpireTime)
|
||||
// truncate nseconds here
|
||||
err = s.cache.CacheSet(&out, nil, time.Unix(int64(status.DateEnds), 0))
|
||||
if err != nil {
|
||||
log.Error("can not save subscription status to cache", zap.Error(err))
|
||||
return nil, ErrCacheProblem
|
||||
|
@ -300,7 +298,7 @@ func (s *service) GetSubscriptionStatus(ctx context.Context, req *pb.RpcMembersh
|
|||
|
||||
log.Debug("subscription status", zap.Any("from server", status), zap.Any("cached", cachedStatus), zap.Bool("isEmailDiff", isEmailDiff))
|
||||
|
||||
// 4 - return, if cache was enabled and nothing is changed
|
||||
// 6. If tier or status has changed -> send event
|
||||
if cachedStatus != nil && !isDiffTier && !isDiffStatus && !isEmailDiff {
|
||||
log.Debug("subscription status has NOT changed",
|
||||
zap.Bool("cache was empty", cachedStatus == nil),
|
||||
|
@ -316,11 +314,9 @@ func (s *service) GetSubscriptionStatus(ctx context.Context, req *pb.RpcMembersh
|
|||
zap.Bool("isDiffStatus", isDiffStatus),
|
||||
zap.Bool("isEmailDiff", isEmailDiff),
|
||||
)
|
||||
|
||||
// 4.1 - send the event
|
||||
s.sendEvent(&out)
|
||||
|
||||
// 4.2 - update globalName of our own identity
|
||||
// 7. If name has changed -> update global name or own identity
|
||||
if status.RequestedAnyName != "" {
|
||||
log.Debug("update global name",
|
||||
zap.String("requestedAnyName", status.RequestedAnyName),
|
||||
|
@ -329,14 +325,14 @@ func (s *service) GetSubscriptionStatus(ctx context.Context, req *pb.RpcMembersh
|
|||
s.profileUpdater.UpdateOwnGlobalName(status.RequestedAnyName)
|
||||
}
|
||||
|
||||
// 8. UpdateLimits
|
||||
err = s.updateLimits(ctx)
|
||||
if err != nil {
|
||||
log.Error("update limits", zap.Error(err))
|
||||
}
|
||||
|
||||
// 4.3 - enable cache again (only when status is active)
|
||||
// 9. Enable cache again if status is active
|
||||
isFinished := status.Status == proto.SubscriptionStatus_StatusActive
|
||||
|
||||
if isFinished {
|
||||
log.Info("enabling cache again")
|
||||
|
||||
|
@ -860,17 +856,12 @@ func (s *service) getAllTiers(ctx context.Context, req *pb.RpcMembershipGetTiers
|
|||
}
|
||||
|
||||
// 3 - update tiers, not status
|
||||
var cacheExpireTime time.Time
|
||||
var ends time.Time = time.Unix(0, 0)
|
||||
if (cachedStatus != nil) && (cachedStatus.Data != nil) {
|
||||
cacheExpireTime = time.Unix(int64(cachedStatus.Data.DateEnds), 0)
|
||||
} else {
|
||||
log.Debug("setting tiers cache to +1 day")
|
||||
|
||||
timeNow := time.Now().UTC()
|
||||
cacheExpireTime = timeNow.Add(1 * 24 * time.Hour)
|
||||
ends = time.Unix(int64(cachedStatus.Data.DateEnds), 0)
|
||||
}
|
||||
|
||||
err = s.cache.CacheSet(nil, &out, cacheExpireTime)
|
||||
err = s.cache.CacheSet(nil, &out, ends)
|
||||
if err != nil {
|
||||
log.Error("can not save tiers to cache", zap.Error(err))
|
||||
return nil, ErrCacheProblem
|
||||
|
|
|
@ -174,6 +174,71 @@ func TestGetStatus(t *testing.T) {
|
|||
assert.Equal(t, model.Membership_StatusUnknown, resp.Data.Status)
|
||||
})
|
||||
|
||||
t.Run("success if NoCache flag is passed, but no connectivity", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.finish(t)
|
||||
|
||||
fx.ppclient.EXPECT().GetSubscriptionStatus(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx interface{}, in *psp.GetSubscriptionRequestSigned) (*psp.GetSubscriptionResponse, error) {
|
||||
// >>> here
|
||||
return nil, ErrNoConnection
|
||||
}).MinTimes(1)
|
||||
|
||||
psgsr := pb.RpcMembershipGetStatusResponse{
|
||||
Data: &model.Membership{
|
||||
Tier: uint32(psp.SubscriptionTier_TierExplorer),
|
||||
Status: model.Membership_StatusActive,
|
||||
DateStarted: uint64(timeNow.Unix()),
|
||||
DateEnds: uint64(subsExpire.Unix()),
|
||||
IsAutoRenew: true,
|
||||
PaymentMethod: model.Membership_MethodCrypto,
|
||||
NsName: "something",
|
||||
NsNameType: model.NameserviceNameType_AnyName,
|
||||
},
|
||||
}
|
||||
fx.cache.EXPECT().CacheGet().Return(&psgsr, nil, nil)
|
||||
|
||||
// Call the function being tested
|
||||
req := pb.RpcMembershipGetStatusRequest{
|
||||
// / >>> here:
|
||||
NoCache: true,
|
||||
}
|
||||
resp, err := fx.GetSubscriptionStatus(ctx, &req)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, uint32(psp.SubscriptionTier_TierExplorer), resp.Data.Tier)
|
||||
assert.Equal(t, model.Membership_StatusActive, resp.Data.Status)
|
||||
})
|
||||
|
||||
t.Run("fail if NoCache flag is passed, no cache, no connectivity", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.finish(t)
|
||||
|
||||
fx.ppclient.EXPECT().GetSubscriptionStatus(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx interface{}, in *psp.GetSubscriptionRequestSigned) (*psp.GetSubscriptionResponse, error) {
|
||||
// >>> here
|
||||
return nil, ErrNoConnection
|
||||
}).MinTimes(1)
|
||||
|
||||
// >>> here:
|
||||
fx.cache.EXPECT().CacheGet().Return(nil, nil, nil)
|
||||
fx.cache.EXPECT().CacheSet(mock.AnythingOfType("*pb.RpcMembershipGetStatusResponse"), mock.AnythingOfType("*pb.RpcMembershipGetTiersResponse"), mock.AnythingOfType("time.Time")).RunAndReturn(func(in *pb.RpcMembershipGetStatusResponse, tiers *pb.RpcMembershipGetTiersResponse, expire time.Time) (err error) {
|
||||
return nil
|
||||
})
|
||||
|
||||
fx.expectLimitsUpdated()
|
||||
|
||||
// Call the function being tested
|
||||
req := pb.RpcMembershipGetStatusRequest{
|
||||
// / >>> here:
|
||||
NoCache: true,
|
||||
}
|
||||
resp, err := fx.GetSubscriptionStatus(ctx, &req)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// default values
|
||||
assert.Equal(t, uint32(psp.SubscriptionTier_TierUnknown), resp.Data.Tier)
|
||||
assert.Equal(t, model.Membership_StatusUnknown, resp.Data.Status)
|
||||
})
|
||||
|
||||
t.Run("fail if no cache, GetSubscriptionStatus returns error, and default tiers", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.finish(t)
|
||||
|
@ -219,22 +284,13 @@ func TestGetStatus(t *testing.T) {
|
|||
}).MinTimes(1)
|
||||
|
||||
fx.cache.EXPECT().CacheGet().Return(&psgsr, &tgr, cache.ErrCacheExpired)
|
||||
fx.cache.EXPECT().CacheSet(mock.AnythingOfType("*pb.RpcMembershipGetStatusResponse"), mock.AnythingOfType("*pb.RpcMembershipGetTiersResponse"), mock.AnythingOfType("time.Time")).RunAndReturn(func(in *pb.RpcMembershipGetStatusResponse, tiers *pb.RpcMembershipGetTiersResponse, expire time.Time) (err error) {
|
||||
return nil
|
||||
})
|
||||
|
||||
fx.expectLimitsUpdated()
|
||||
|
||||
// Call the function being tested
|
||||
_, err := fx.GetSubscriptionStatus(ctx, &pb.RpcMembershipGetStatusRequest{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// resp object is nil in case of error
|
||||
// assert.Equal(t, pb.RpcPaymentsSubscriptionGetStatusResponseErrorCode(pb.RpcPaymentsSubscriptionGetStatusResponseError_UNKNOWN_ERROR), resp.Error.Code)
|
||||
// assert.Equal(t, "can not write to cache!", resp.Error.Description)
|
||||
})
|
||||
|
||||
t.Run("fail if no cache, GetSubscriptionStatus returns error, and NOT default tiers", func(t *testing.T) {
|
||||
t.Run("success if no cache, GetSubscriptionStatus returns error and data", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.finish(t)
|
||||
|
||||
|
@ -279,11 +335,13 @@ func TestGetStatus(t *testing.T) {
|
|||
return nil, errors.New("no internet")
|
||||
}).MinTimes(1)
|
||||
|
||||
// TODO: refactor - bad method semantics:
|
||||
// returns error, but also returns data...
|
||||
fx.cache.EXPECT().CacheGet().Return(&psgsr, &tgr, cache.ErrCacheExpired)
|
||||
|
||||
// Call the function being tested
|
||||
_, err := fx.GetSubscriptionStatus(ctx, &pb.RpcMembershipGetStatusRequest{})
|
||||
assert.Error(t, err)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("success if cache is expired and GetSubscriptionStatus returns no error", func(t *testing.T) {
|
||||
|
@ -318,7 +376,7 @@ func TestGetStatus(t *testing.T) {
|
|||
}).MinTimes(1)
|
||||
|
||||
fx.cache.EXPECT().CacheGet().Return(&psgsr, nil, cache.ErrCacheExpired)
|
||||
fx.cache.EXPECT().CacheSet(mock.AnythingOfType("*pb.RpcMembershipGetStatusResponse"), mock.AnythingOfType("*pb.RpcMembershipGetTiersResponse"), cacheExpireTime).RunAndReturn(func(in *pb.RpcMembershipGetStatusResponse, tiers *pb.RpcMembershipGetTiersResponse, expire time.Time) (err error) {
|
||||
fx.cache.EXPECT().CacheSet(mock.AnythingOfType("*pb.RpcMembershipGetStatusResponse"), mock.AnythingOfType("*pb.RpcMembershipGetTiersResponse"), mock.AnythingOfType("time.Time")).RunAndReturn(func(in *pb.RpcMembershipGetStatusResponse, tiers *pb.RpcMembershipGetTiersResponse, expire time.Time) (err error) {
|
||||
return nil
|
||||
})
|
||||
// fx.cache.EXPECT().CacheEnable().Return(nil)
|
||||
|
@ -422,7 +480,7 @@ func TestGetStatus(t *testing.T) {
|
|||
}).MinTimes(1)
|
||||
|
||||
fx.cache.EXPECT().CacheGet().Return(&psgsr, nil, cache.ErrCacheExpired)
|
||||
fx.cache.EXPECT().CacheSet(mock.AnythingOfType("*pb.RpcMembershipGetStatusResponse"), mock.AnythingOfType("*pb.RpcMembershipGetTiersResponse"), cacheExpireTime).RunAndReturn(func(in *pb.RpcMembershipGetStatusResponse, tiers *pb.RpcMembershipGetTiersResponse, expire time.Time) (err error) {
|
||||
fx.cache.EXPECT().CacheSet(mock.AnythingOfType("*pb.RpcMembershipGetStatusResponse"), mock.AnythingOfType("*pb.RpcMembershipGetTiersResponse"), mock.AnythingOfType("time.Time")).RunAndReturn(func(in *pb.RpcMembershipGetStatusResponse, tiers *pb.RpcMembershipGetTiersResponse, expire time.Time) (err error) {
|
||||
return errors.New("can not write to cache!")
|
||||
})
|
||||
|
||||
|
@ -500,7 +558,7 @@ func TestGetStatus(t *testing.T) {
|
|||
}).MinTimes(1)
|
||||
|
||||
fx.cache.EXPECT().CacheGet().Return(nil, nil, cache.ErrCacheExpired)
|
||||
fx.cache.EXPECT().CacheSet(&psgsr, mock.AnythingOfType("*pb.RpcMembershipGetTiersResponse"), cacheExpireTime).RunAndReturn(func(in *pb.RpcMembershipGetStatusResponse, tiers *pb.RpcMembershipGetTiersResponse, expire time.Time) (err error) {
|
||||
fx.cache.EXPECT().CacheSet(&psgsr, mock.AnythingOfType("*pb.RpcMembershipGetTiersResponse"), mock.AnythingOfType("time.Time")).RunAndReturn(func(in *pb.RpcMembershipGetStatusResponse, tiers *pb.RpcMembershipGetTiersResponse, expire time.Time) (err error) {
|
||||
return nil
|
||||
})
|
||||
fx.cache.EXPECT().CacheEnable().Return(nil)
|
||||
|
@ -559,7 +617,7 @@ func TestGetStatus(t *testing.T) {
|
|||
|
||||
// return real struct and error
|
||||
fx.cache.EXPECT().CacheGet().Return(nil, nil, cache.ErrCacheDisabled)
|
||||
fx.cache.EXPECT().CacheSet(&psgsr2, mock.AnythingOfType("*pb.RpcMembershipGetTiersResponse"), cacheExpireTime).RunAndReturn(func(in *pb.RpcMembershipGetStatusResponse, tiers *pb.RpcMembershipGetTiersResponse, expire time.Time) (err error) {
|
||||
fx.cache.EXPECT().CacheSet(&psgsr2, mock.AnythingOfType("*pb.RpcMembershipGetTiersResponse"), mock.AnythingOfType("time.Time")).RunAndReturn(func(in *pb.RpcMembershipGetStatusResponse, tiers *pb.RpcMembershipGetTiersResponse, expire time.Time) (err error) {
|
||||
return nil
|
||||
})
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ func RelationFromStruct(st *types.Struct) *Relation {
|
|||
DefaultValue: pbtypes.Get(st, bundle.RelationKeyRelationDefaultValue.String()),
|
||||
DataSource: model.Relation_details,
|
||||
Hidden: pbtypes.GetBool(st, bundle.RelationKeyIsHidden.String()),
|
||||
ReadOnly: pbtypes.GetBool(st, bundle.RelationKeyIsReadonly.String()),
|
||||
ReadOnly: pbtypes.GetBool(st, bundle.RelationKeyRelationReadonlyValue.String()),
|
||||
ReadOnlyRelation: false,
|
||||
Multi: maxCount > 1,
|
||||
ObjectTypes: pbtypes.GetStringList(st, bundle.RelationKeyRelationFormatObjectTypes.String()),
|
||||
|
|
|
@ -27,6 +27,10 @@ func (s *service) OnFileLimited(objectId string) error {
|
|||
return s.indexFileSyncStatus(objectId, filesyncstatus.Limited)
|
||||
}
|
||||
|
||||
func (s *service) OnFileDelete(fileId domain.FullFileId) {
|
||||
s.sendSpaceStatusUpdate(filesyncstatus.Synced, fileId.SpaceId)
|
||||
}
|
||||
|
||||
func (s *service) indexFileSyncStatus(fileObjectId string, status filesyncstatus.Status) error {
|
||||
var spaceId string
|
||||
err := cache.Do(s.objectGetter, fileObjectId, func(sb smartblock.SmartBlock) (err error) {
|
||||
|
|
|
@ -0,0 +1,256 @@
|
|||
// Code generated by mockery. DO NOT EDIT.
|
||||
|
||||
package mock_objectsyncstatus
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
app "github.com/anyproto/any-sync/app"
|
||||
|
||||
domain "github.com/anyproto/anytype-heart/core/domain"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// MockSpaceStatusUpdater is an autogenerated mock type for the SpaceStatusUpdater type
|
||||
type MockSpaceStatusUpdater struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockSpaceStatusUpdater_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockSpaceStatusUpdater) EXPECT() *MockSpaceStatusUpdater_Expecter {
|
||||
return &MockSpaceStatusUpdater_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Close provides a mock function with given fields: ctx
|
||||
func (_m *MockSpaceStatusUpdater) Close(ctx context.Context) error {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Close")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
|
||||
r0 = rf(ctx)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockSpaceStatusUpdater_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'
|
||||
type MockSpaceStatusUpdater_Close_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Close is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
func (_e *MockSpaceStatusUpdater_Expecter) Close(ctx interface{}) *MockSpaceStatusUpdater_Close_Call {
|
||||
return &MockSpaceStatusUpdater_Close_Call{Call: _e.mock.On("Close", ctx)}
|
||||
}
|
||||
|
||||
func (_c *MockSpaceStatusUpdater_Close_Call) Run(run func(ctx context.Context)) *MockSpaceStatusUpdater_Close_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockSpaceStatusUpdater_Close_Call) Return(err error) *MockSpaceStatusUpdater_Close_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockSpaceStatusUpdater_Close_Call) RunAndReturn(run func(context.Context) error) *MockSpaceStatusUpdater_Close_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Init provides a mock function with given fields: a
|
||||
func (_m *MockSpaceStatusUpdater) Init(a *app.App) error {
|
||||
ret := _m.Called(a)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Init")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*app.App) error); ok {
|
||||
r0 = rf(a)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockSpaceStatusUpdater_Init_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Init'
|
||||
type MockSpaceStatusUpdater_Init_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Init is a helper method to define mock.On call
|
||||
// - a *app.App
|
||||
func (_e *MockSpaceStatusUpdater_Expecter) Init(a interface{}) *MockSpaceStatusUpdater_Init_Call {
|
||||
return &MockSpaceStatusUpdater_Init_Call{Call: _e.mock.On("Init", a)}
|
||||
}
|
||||
|
||||
func (_c *MockSpaceStatusUpdater_Init_Call) Run(run func(a *app.App)) *MockSpaceStatusUpdater_Init_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(*app.App))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockSpaceStatusUpdater_Init_Call) Return(err error) *MockSpaceStatusUpdater_Init_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockSpaceStatusUpdater_Init_Call) RunAndReturn(run func(*app.App) error) *MockSpaceStatusUpdater_Init_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Name provides a mock function with given fields:
|
||||
func (_m *MockSpaceStatusUpdater) Name() string {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Name")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockSpaceStatusUpdater_Name_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Name'
|
||||
type MockSpaceStatusUpdater_Name_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Name is a helper method to define mock.On call
|
||||
func (_e *MockSpaceStatusUpdater_Expecter) Name() *MockSpaceStatusUpdater_Name_Call {
|
||||
return &MockSpaceStatusUpdater_Name_Call{Call: _e.mock.On("Name")}
|
||||
}
|
||||
|
||||
func (_c *MockSpaceStatusUpdater_Name_Call) Run(run func()) *MockSpaceStatusUpdater_Name_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockSpaceStatusUpdater_Name_Call) Return(name string) *MockSpaceStatusUpdater_Name_Call {
|
||||
_c.Call.Return(name)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockSpaceStatusUpdater_Name_Call) RunAndReturn(run func() string) *MockSpaceStatusUpdater_Name_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Run provides a mock function with given fields: ctx
|
||||
func (_m *MockSpaceStatusUpdater) Run(ctx context.Context) error {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Run")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
|
||||
r0 = rf(ctx)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockSpaceStatusUpdater_Run_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Run'
|
||||
type MockSpaceStatusUpdater_Run_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Run is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
func (_e *MockSpaceStatusUpdater_Expecter) Run(ctx interface{}) *MockSpaceStatusUpdater_Run_Call {
|
||||
return &MockSpaceStatusUpdater_Run_Call{Call: _e.mock.On("Run", ctx)}
|
||||
}
|
||||
|
||||
func (_c *MockSpaceStatusUpdater_Run_Call) Run(run func(ctx context.Context)) *MockSpaceStatusUpdater_Run_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockSpaceStatusUpdater_Run_Call) Return(err error) *MockSpaceStatusUpdater_Run_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockSpaceStatusUpdater_Run_Call) RunAndReturn(run func(context.Context) error) *MockSpaceStatusUpdater_Run_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SendUpdate provides a mock function with given fields: spaceSync
|
||||
func (_m *MockSpaceStatusUpdater) SendUpdate(spaceSync *domain.SpaceSync) {
|
||||
_m.Called(spaceSync)
|
||||
}
|
||||
|
||||
// MockSpaceStatusUpdater_SendUpdate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendUpdate'
|
||||
type MockSpaceStatusUpdater_SendUpdate_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// SendUpdate is a helper method to define mock.On call
|
||||
// - spaceSync *domain.SpaceSync
|
||||
func (_e *MockSpaceStatusUpdater_Expecter) SendUpdate(spaceSync interface{}) *MockSpaceStatusUpdater_SendUpdate_Call {
|
||||
return &MockSpaceStatusUpdater_SendUpdate_Call{Call: _e.mock.On("SendUpdate", spaceSync)}
|
||||
}
|
||||
|
||||
func (_c *MockSpaceStatusUpdater_SendUpdate_Call) Run(run func(spaceSync *domain.SpaceSync)) *MockSpaceStatusUpdater_SendUpdate_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(*domain.SpaceSync))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockSpaceStatusUpdater_SendUpdate_Call) Return() *MockSpaceStatusUpdater_SendUpdate_Call {
|
||||
_c.Call.Return()
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockSpaceStatusUpdater_SendUpdate_Call) RunAndReturn(run func(*domain.SpaceSync)) *MockSpaceStatusUpdater_SendUpdate_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockSpaceStatusUpdater creates a new instance of MockSpaceStatusUpdater. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockSpaceStatusUpdater(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockSpaceStatusUpdater {
|
||||
mock := &MockSpaceStatusUpdater{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
|
@ -30,6 +30,11 @@ const (
|
|||
|
||||
var log = logger.NewNamed(syncstatus.CName)
|
||||
|
||||
type SpaceStatusUpdater interface {
|
||||
app.ComponentRunnable
|
||||
SendUpdate(spaceSync *domain.SpaceSync)
|
||||
}
|
||||
|
||||
type UpdateReceiver interface {
|
||||
UpdateTree(ctx context.Context, treeId string, status SyncStatus) (err error)
|
||||
UpdateNodeStatus()
|
||||
|
@ -97,6 +102,7 @@ type syncStatusService struct {
|
|||
syncDetailsUpdater Updater
|
||||
nodeStatus nodestatus.NodeStatus
|
||||
config *config.Config
|
||||
spaceSyncStatus SpaceStatusUpdater
|
||||
}
|
||||
|
||||
func NewSyncStatusService() StatusService {
|
||||
|
@ -113,6 +119,7 @@ func (s *syncStatusService) Init(a *app.App) (err error) {
|
|||
s.spaceId = sharedState.SpaceId
|
||||
s.configuration = app.MustComponent[nodeconf.NodeConf](a)
|
||||
s.storage = app.MustComponent[spacestorage.SpaceStorage](a)
|
||||
s.spaceSyncStatus = app.MustComponent[SpaceStatusUpdater](a)
|
||||
s.periodicSync = periodicsync.NewPeriodicSync(
|
||||
s.updateIntervalSecs,
|
||||
s.updateTimeout,
|
||||
|
@ -154,6 +161,7 @@ func (s *syncStatusService) HeadsChange(treeId string, heads []string) {
|
|||
}
|
||||
s.stateCounter++
|
||||
s.updateDetails(treeId, domain.Syncing)
|
||||
s.spaceSyncStatus.SendUpdate(domain.MakeSyncStatus(s.spaceId, domain.Syncing, 1, domain.Null, domain.Objects))
|
||||
}
|
||||
|
||||
func (s *syncStatusService) update(ctx context.Context) (err error) {
|
||||
|
|
|
@ -20,6 +20,8 @@ import (
|
|||
"github.com/anyproto/anytype-heart/core/syncstatus/nodestatus"
|
||||
"github.com/anyproto/anytype-heart/core/syncstatus/objectsyncstatus/mock_objectsyncstatus"
|
||||
"github.com/anyproto/anytype-heart/pb"
|
||||
"github.com/anyproto/anytype-heart/core/domain"
|
||||
"github.com/anyproto/anytype-heart/core/syncstatus/objectsyncstatus/mock_objectsyncstatus"
|
||||
"github.com/anyproto/anytype-heart/tests/testutil"
|
||||
)
|
||||
|
||||
|
@ -27,7 +29,7 @@ func Test_HeadsChange(t *testing.T) {
|
|||
t.Run("HeadsChange: new object", func(t *testing.T) {
|
||||
// given
|
||||
s := newFixture(t)
|
||||
s.config.NetworkMode = pb.RpcAccount_LocalOnly
|
||||
s.spaceStatusUpdater.EXPECT().SendUpdate(domain.MakeSyncStatus(s.spaceId, domain.Syncing, 1, domain.Null, domain.Objects)).Return()
|
||||
s.detailsUpdater.EXPECT().UpdateDetails("id", domain.Offline, domain.Null, "spaceId")
|
||||
|
||||
// when
|
||||
|
@ -40,6 +42,7 @@ func Test_HeadsChange(t *testing.T) {
|
|||
t.Run("HeadsChange: update existing object", func(t *testing.T) {
|
||||
// given
|
||||
s := newFixture(t)
|
||||
s.spaceStatusUpdater.EXPECT().SendUpdate(domain.MakeSyncStatus(s.spaceId, domain.Syncing, 1, domain.Null, domain.Objects)).Return()
|
||||
s.config.NetworkMode = pb.RpcAccount_DefaultConfig
|
||||
s.detailsUpdater.EXPECT().UpdateDetails("id", domain.Syncing, domain.Null, "spaceId")
|
||||
|
||||
|
@ -101,6 +104,7 @@ func TestSyncStatusService_HeadsReceive(t *testing.T) {
|
|||
s.detailsUpdater.EXPECT().UpdateDetails("id", domain.Syncing, domain.Null, "spaceId")
|
||||
|
||||
// when
|
||||
s.spaceStatusUpdater.EXPECT().SendUpdate(domain.MakeSyncStatus(s.spaceId, domain.Syncing, 1, domain.Null, domain.Objects)).Return()
|
||||
s.HeadsChange("id", []string{"head1"})
|
||||
s.HeadsReceive("peerId", "id", []string{"head2"})
|
||||
|
||||
|
@ -116,6 +120,7 @@ func TestSyncStatusService_HeadsReceive(t *testing.T) {
|
|||
s.detailsUpdater.EXPECT().UpdateDetails("id", domain.Synced, domain.Null, "spaceId")
|
||||
|
||||
// when
|
||||
s.spaceStatusUpdater.EXPECT().SendUpdate(domain.MakeSyncStatus(s.spaceId, domain.Syncing, 1, domain.Null, domain.Objects)).Return()
|
||||
s.HeadsChange("id", []string{"head1"})
|
||||
s.HeadsReceive("peerId", "id", []string{"head1"})
|
||||
|
||||
|
@ -131,6 +136,7 @@ func TestSyncStatusService_Watch(t *testing.T) {
|
|||
s := newFixture(t)
|
||||
|
||||
// when
|
||||
s.spaceStatusUpdater.EXPECT().SendUpdate(domain.MakeSyncStatus(s.spaceId, domain.Syncing, 1, domain.Null, domain.Objects)).Return()
|
||||
s.detailsUpdater.EXPECT().UpdateDetails("id", domain.Syncing, domain.Null, "spaceId")
|
||||
s.HeadsChange("id", []string{"head1"})
|
||||
err := s.Watch("id")
|
||||
|
@ -179,6 +185,7 @@ func TestSyncStatusService_Unwatch(t *testing.T) {
|
|||
|
||||
// when
|
||||
s.detailsUpdater.EXPECT().UpdateDetails("id", domain.Syncing, domain.Null, "spaceId")
|
||||
s.spaceStatusUpdater.EXPECT().SendUpdate(domain.MakeSyncStatus(s.spaceId, domain.Syncing, 1, domain.Null, domain.Objects)).Return()
|
||||
s.HeadsChange("id", []string{"head1"})
|
||||
err := s.Watch("id")
|
||||
assert.Nil(t, err)
|
||||
|
@ -202,6 +209,7 @@ func TestSyncStatusService_update(t *testing.T) {
|
|||
|
||||
// when
|
||||
s.detailsUpdater.EXPECT().UpdateDetails("id", domain.Syncing, domain.Null, "spaceId")
|
||||
s.spaceStatusUpdater.EXPECT().SendUpdate(domain.MakeSyncStatus(s.spaceId, domain.Syncing, 1, domain.Null, domain.Objects)).Return()
|
||||
s.HeadsChange("id", []string{"head1"})
|
||||
err := s.Watch("id")
|
||||
assert.Nil(t, err)
|
||||
|
@ -219,6 +227,7 @@ func TestSyncStatusService_update(t *testing.T) {
|
|||
|
||||
// when
|
||||
s.detailsUpdater.EXPECT().UpdateDetails("id", domain.Syncing, domain.Null, "spaceId")
|
||||
s.spaceStatusUpdater.EXPECT().SendUpdate(domain.MakeSyncStatus(s.spaceId, domain.Syncing, 1, domain.Null, domain.Objects)).Return()
|
||||
s.HeadsChange("id", []string{"head1"})
|
||||
err := s.Watch("id")
|
||||
assert.Nil(t, err)
|
||||
|
@ -316,6 +325,7 @@ type fixture struct {
|
|||
config *config.Config
|
||||
detailsUpdater *mock_objectsyncstatus.MockUpdater
|
||||
nodeStatus nodestatus.NodeStatus
|
||||
spaceStatusUpdater *mock_objectsyncstatus.MockSpaceStatusUpdater
|
||||
}
|
||||
|
||||
func newFixture(t *testing.T) *fixture {
|
||||
|
@ -326,6 +336,7 @@ func newFixture(t *testing.T) *fixture {
|
|||
config := &config.Config{}
|
||||
detailsUpdater := mock_objectsyncstatus.NewMockUpdater(t)
|
||||
nodeStatus := nodestatus.NewNodeStatus()
|
||||
spaceStatusUpdater := mock_objectsyncstatus.NewMockSpaceStatusUpdater(t)
|
||||
a := &app.App{}
|
||||
|
||||
a.Register(testutil.PrepareMock(context.Background(), a, service)).
|
||||
|
@ -351,5 +362,6 @@ func newFixture(t *testing.T) *fixture {
|
|||
config: config,
|
||||
detailsUpdater: detailsUpdater,
|
||||
nodeStatus: nodeStatus,
|
||||
spaceStatusUpdater: spaceStatusUpdater,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,6 +74,7 @@ func (s *service) Init(a *app.App) (err error) {
|
|||
s.fileSyncService.OnUploaded(s.OnFileUploaded)
|
||||
s.fileSyncService.OnUploadStarted(s.OnFileUploadStarted)
|
||||
s.fileSyncService.OnLimited(s.OnFileLimited)
|
||||
s.fileSyncService.OnDelete(s.OnFileDelete)
|
||||
|
||||
s.spaceSyncStatus = app.MustComponent[spacesyncstatus.Updater](a)
|
||||
return nil
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
type FileState struct {
|
||||
fileSyncCountBySpace map[string]int
|
||||
fileSyncStatusBySpace map[string]domain.SyncStatus
|
||||
filesErrorBySpace map[string]domain.SyncError
|
||||
|
||||
store objectstore.ObjectStore
|
||||
}
|
||||
|
@ -21,6 +22,7 @@ func NewFileState(store objectstore.ObjectStore) *FileState {
|
|||
return &FileState{
|
||||
fileSyncCountBySpace: make(map[string]int, 0),
|
||||
fileSyncStatusBySpace: make(map[string]domain.SyncStatus, 0),
|
||||
filesErrorBySpace: make(map[string]domain.SyncError, 0),
|
||||
|
||||
store: store,
|
||||
}
|
||||
|
@ -47,18 +49,29 @@ func (f *FileState) SetObjectsNumber(status *domain.SpaceSync) {
|
|||
f.fileSyncCountBySpace[status.SpaceId] = len(records)
|
||||
}
|
||||
|
||||
func (f *FileState) SetSyncStatus(status *domain.SpaceSync) {
|
||||
func (f *FileState) SetSyncStatusAndErr(status *domain.SpaceSync) {
|
||||
switch status.Status {
|
||||
case domain.Synced:
|
||||
f.fileSyncStatusBySpace[status.SpaceId] = domain.Synced
|
||||
if number := f.fileSyncCountBySpace[status.SpaceId]; number > 0 {
|
||||
f.fileSyncStatusBySpace[status.SpaceId] = domain.Syncing
|
||||
f.setError(status.SpaceId, status.SyncError)
|
||||
return
|
||||
}
|
||||
if fileLimitedCount := f.getFileLimitedCount(status.SpaceId); fileLimitedCount > 0 {
|
||||
f.fileSyncStatusBySpace[status.SpaceId] = domain.Error
|
||||
f.setError(status.SpaceId, domain.StorageLimitExceed)
|
||||
}
|
||||
case domain.Error, domain.Syncing, domain.Offline:
|
||||
f.fileSyncStatusBySpace[status.SpaceId] = status.Status
|
||||
f.setError(status.SpaceId, status.SyncError)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FileState) setError(spaceId string, syncErr domain.SyncError) {
|
||||
f.filesErrorBySpace[spaceId] = syncErr
|
||||
}
|
||||
|
||||
func (f *FileState) GetSyncStatus(spaceId string) domain.SyncStatus {
|
||||
return f.fileSyncStatusBySpace[spaceId]
|
||||
}
|
||||
|
@ -67,11 +80,27 @@ func (f *FileState) GetSyncObjectCount(spaceId string) int {
|
|||
return f.fileSyncCountBySpace[spaceId]
|
||||
}
|
||||
|
||||
func (f *FileState) IsSyncFinished(spaceId string) bool {
|
||||
if _, ok := f.fileSyncStatusBySpace[spaceId]; !ok {
|
||||
return false
|
||||
}
|
||||
status := f.fileSyncStatusBySpace[spaceId]
|
||||
count := f.fileSyncCountBySpace[spaceId]
|
||||
return count == 0 && status == domain.Synced
|
||||
func (f *FileState) GetSyncErr(spaceId string) domain.SyncError {
|
||||
return f.filesErrorBySpace[spaceId]
|
||||
}
|
||||
|
||||
func (f *FileState) getFileLimitedCount(spaceId string) int {
|
||||
records, err := f.store.Query(database.Query{
|
||||
Filters: []*model.BlockContentDataviewFilter{
|
||||
{
|
||||
RelationKey: bundle.RelationKeyFileBackupStatus.String(),
|
||||
Condition: model.BlockContentDataviewFilter_Equal,
|
||||
Value: pbtypes.Int64(int64(filesyncstatus.Limited)),
|
||||
},
|
||||
{
|
||||
RelationKey: bundle.RelationKeySpaceId.String(),
|
||||
Condition: model.BlockContentDataviewFilter_Equal,
|
||||
Value: pbtypes.String(spaceId),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("failed to query file status: %s", err)
|
||||
}
|
||||
return len(records)
|
||||
}
|
||||
|
|
|
@ -104,56 +104,19 @@ func TestFileState_SetObjectsNumber(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestFileState_IsSyncFinished(t *testing.T) {
|
||||
t.Run("IsSyncFinished, sync is not finished", func(t *testing.T) {
|
||||
// given
|
||||
fileState := NewFileState(nil)
|
||||
|
||||
// when
|
||||
finished := fileState.IsSyncFinished("spaceId")
|
||||
|
||||
// then
|
||||
assert.False(t, finished)
|
||||
})
|
||||
t.Run("IsSyncFinished, sync is finished", func(t *testing.T) {
|
||||
// given
|
||||
fileState := NewFileState(nil)
|
||||
|
||||
// when
|
||||
syncStatus := domain.MakeSyncStatus("spaceId", domain.Synced, 0, domain.Null, domain.Files)
|
||||
fileState.SetSyncStatus(syncStatus)
|
||||
finished := fileState.IsSyncFinished("spaceId")
|
||||
|
||||
// then
|
||||
assert.True(t, finished)
|
||||
})
|
||||
t.Run("IsSyncFinished, sync is not finished", func(t *testing.T) {
|
||||
// given
|
||||
fileState := NewFileState(nil)
|
||||
|
||||
// when
|
||||
syncStatus := domain.MakeSyncStatus("spaceId", domain.Offline, 3, domain.Null, domain.Files)
|
||||
fileState.SetSyncStatus(syncStatus)
|
||||
finished := fileState.IsSyncFinished("spaceId")
|
||||
|
||||
// then
|
||||
assert.False(t, finished)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFileState_SetSyncStatus(t *testing.T) {
|
||||
t.Run("SetSyncStatus, status synced", func(t *testing.T) {
|
||||
t.Run("SetSyncStatusAndErr, status synced", func(t *testing.T) {
|
||||
// given
|
||||
fileState := NewFileState(nil)
|
||||
fileState := NewFileState(objectstore.NewStoreFixture(t))
|
||||
|
||||
// when
|
||||
syncStatus := domain.MakeSyncStatus("spaceId", domain.Synced, 0, domain.Null, domain.Files)
|
||||
fileState.SetSyncStatus(syncStatus)
|
||||
fileState.SetSyncStatusAndErr(syncStatus)
|
||||
|
||||
// then
|
||||
assert.Equal(t, domain.Synced, fileState.GetSyncStatus("spaceId"))
|
||||
})
|
||||
t.Run("SetSyncStatus, received status synced, but there are syncing files in store", func(t *testing.T) {
|
||||
t.Run("SetSyncStatusAndErr, received status synced, but there are syncing files in store", func(t *testing.T) {
|
||||
// given
|
||||
storeFixture := objectstore.NewStoreFixture(t)
|
||||
storeFixture.AddObjects(t, []objectstore.TestObject{
|
||||
|
@ -178,40 +141,40 @@ func TestFileState_SetSyncStatus(t *testing.T) {
|
|||
// when
|
||||
syncStatus := domain.MakeSyncStatus("spaceId", domain.Synced, 0, domain.Null, domain.Files)
|
||||
fileState.SetObjectsNumber(syncStatus)
|
||||
fileState.SetSyncStatus(syncStatus)
|
||||
fileState.SetSyncStatusAndErr(syncStatus)
|
||||
|
||||
// then
|
||||
assert.Equal(t, domain.Syncing, fileState.GetSyncStatus("spaceId"))
|
||||
})
|
||||
t.Run("SetSyncStatus, sync in progress", func(t *testing.T) {
|
||||
t.Run("SetSyncStatusAndErr, sync in progress", func(t *testing.T) {
|
||||
// given
|
||||
fileState := NewFileState(nil)
|
||||
fileState := NewFileState(objectstore.NewStoreFixture(t))
|
||||
|
||||
// when
|
||||
syncStatus := domain.MakeSyncStatus("spaceId", domain.Syncing, 0, domain.Null, domain.Files)
|
||||
fileState.SetSyncStatus(syncStatus)
|
||||
fileState.SetSyncStatusAndErr(syncStatus)
|
||||
|
||||
// then
|
||||
assert.Equal(t, domain.Syncing, fileState.GetSyncStatus("spaceId"))
|
||||
})
|
||||
t.Run("SetSyncStatus, sync is finished with error", func(t *testing.T) {
|
||||
t.Run("SetSyncStatusAndErr, sync is finished with error", func(t *testing.T) {
|
||||
// given
|
||||
fileState := NewFileState(nil)
|
||||
fileState := NewFileState(objectstore.NewStoreFixture(t))
|
||||
|
||||
// when
|
||||
syncStatus := domain.MakeSyncStatus("spaceId", domain.Error, 3, domain.Null, domain.Files)
|
||||
fileState.SetSyncStatus(syncStatus)
|
||||
fileState.SetSyncStatusAndErr(syncStatus)
|
||||
|
||||
// then
|
||||
assert.Equal(t, domain.Error, fileState.GetSyncStatus("spaceId"))
|
||||
})
|
||||
t.Run("SetSyncStatus, offline", func(t *testing.T) {
|
||||
t.Run("SetSyncStatusAndErr, offline", func(t *testing.T) {
|
||||
// given
|
||||
fileState := NewFileState(nil)
|
||||
fileState := NewFileState(objectstore.NewStoreFixture(t))
|
||||
|
||||
// when
|
||||
syncStatus := domain.MakeSyncStatus("spaceId", domain.Offline, 3, domain.Null, domain.Files)
|
||||
fileState.SetSyncStatus(syncStatus)
|
||||
fileState.SetSyncStatusAndErr(syncStatus)
|
||||
|
||||
// then
|
||||
assert.Equal(t, domain.Offline, fileState.GetSyncStatus("spaceId"))
|
||||
|
|
|
@ -7,12 +7,14 @@ import (
|
|||
type ObjectState struct {
|
||||
objectSyncStatusBySpace map[string]domain.SyncStatus
|
||||
objectSyncCountBySpace map[string]int
|
||||
objectSyncErrBySpace map[string]domain.SyncError
|
||||
}
|
||||
|
||||
func NewObjectState() *ObjectState {
|
||||
return &ObjectState{
|
||||
objectSyncCountBySpace: make(map[string]int, 0),
|
||||
objectSyncStatusBySpace: make(map[string]domain.SyncStatus, 0),
|
||||
objectSyncErrBySpace: make(map[string]domain.SyncError, 0),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,8 +27,9 @@ func (o *ObjectState) SetObjectsNumber(status *domain.SpaceSync) {
|
|||
}
|
||||
}
|
||||
|
||||
func (o *ObjectState) SetSyncStatus(status *domain.SpaceSync) {
|
||||
func (o *ObjectState) SetSyncStatusAndErr(status *domain.SpaceSync) {
|
||||
o.objectSyncStatusBySpace[status.SpaceId] = status.Status
|
||||
o.objectSyncErrBySpace[status.SpaceId] = status.SyncError
|
||||
}
|
||||
|
||||
func (o *ObjectState) GetSyncStatus(spaceId string) domain.SyncStatus {
|
||||
|
@ -37,11 +40,6 @@ func (o *ObjectState) GetSyncObjectCount(spaceId string) int {
|
|||
return o.objectSyncCountBySpace[spaceId]
|
||||
}
|
||||
|
||||
func (o *ObjectState) IsSyncFinished(spaceId string) bool {
|
||||
if _, ok := o.objectSyncStatusBySpace[spaceId]; !ok {
|
||||
return false
|
||||
}
|
||||
status := o.objectSyncStatusBySpace[spaceId]
|
||||
count := o.objectSyncCountBySpace[spaceId]
|
||||
return count == 0 && status == domain.Synced
|
||||
func (o *ObjectState) GetSyncErr(spaceId string) domain.SyncError {
|
||||
return o.objectSyncErrBySpace[spaceId]
|
||||
}
|
||||
|
|
|
@ -81,84 +81,47 @@ func TestObjectState_SetObjectsNumber(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestObjectState_IsSyncFinished(t *testing.T) {
|
||||
t.Run("IsSyncFinished, sync is not finished", func(t *testing.T) {
|
||||
// given
|
||||
objectState := NewObjectState()
|
||||
|
||||
// when
|
||||
finished := objectState.IsSyncFinished("spaceId")
|
||||
|
||||
// then
|
||||
assert.False(t, finished)
|
||||
})
|
||||
t.Run("IsSyncFinished, sync is finished", func(t *testing.T) {
|
||||
// given
|
||||
objectState := NewObjectState()
|
||||
|
||||
// when
|
||||
syncStatus := domain.MakeSyncStatus("spaceId", domain.Synced, 0, domain.Null, domain.Objects)
|
||||
objectState.SetSyncStatus(syncStatus)
|
||||
finished := objectState.IsSyncFinished("spaceId")
|
||||
|
||||
// then
|
||||
assert.True(t, finished)
|
||||
})
|
||||
t.Run("IsSyncFinished, sync is not finished", func(t *testing.T) {
|
||||
// given
|
||||
objectState := NewObjectState()
|
||||
|
||||
// when
|
||||
syncStatus := domain.MakeSyncStatus("spaceId", domain.Offline, 3, domain.Null, domain.Objects)
|
||||
objectState.SetSyncStatus(syncStatus)
|
||||
finished := objectState.IsSyncFinished("spaceId")
|
||||
|
||||
// then
|
||||
assert.False(t, finished)
|
||||
})
|
||||
}
|
||||
|
||||
func TestObjectState_SetSyncStatus(t *testing.T) {
|
||||
t.Run("SetSyncStatus, status synced", func(t *testing.T) {
|
||||
t.Run("SetSyncStatusAndErr, status synced", func(t *testing.T) {
|
||||
// given
|
||||
objectState := NewObjectState()
|
||||
|
||||
// when
|
||||
syncStatus := domain.MakeSyncStatus("spaceId", domain.Synced, 0, domain.Null, domain.Objects)
|
||||
objectState.SetSyncStatus(syncStatus)
|
||||
objectState.SetSyncStatusAndErr(syncStatus)
|
||||
|
||||
// then
|
||||
assert.Equal(t, domain.Synced, objectState.GetSyncStatus("spaceId"))
|
||||
})
|
||||
t.Run("SetSyncStatus, sync in progress", func(t *testing.T) {
|
||||
t.Run("SetSyncStatusAndErr, sync in progress", func(t *testing.T) {
|
||||
// given
|
||||
objectState := NewObjectState()
|
||||
|
||||
// when
|
||||
syncStatus := domain.MakeSyncStatus("spaceId", domain.Syncing, 1, domain.Null, domain.Objects)
|
||||
objectState.SetSyncStatus(syncStatus)
|
||||
objectState.SetSyncStatusAndErr(syncStatus)
|
||||
|
||||
// then
|
||||
assert.Equal(t, domain.Syncing, objectState.GetSyncStatus("spaceId"))
|
||||
})
|
||||
t.Run("SetSyncStatus, sync is finished with error", func(t *testing.T) {
|
||||
t.Run("SetSyncStatusAndErr, sync is finished with error", func(t *testing.T) {
|
||||
// given
|
||||
objectState := NewObjectState()
|
||||
|
||||
// when
|
||||
syncStatus := domain.MakeSyncStatus("spaceId", domain.Error, 3, domain.Null, domain.Objects)
|
||||
objectState.SetSyncStatus(syncStatus)
|
||||
objectState.SetSyncStatusAndErr(syncStatus)
|
||||
|
||||
// then
|
||||
assert.Equal(t, domain.Error, objectState.GetSyncStatus("spaceId"))
|
||||
})
|
||||
t.Run("SetSyncStatus, offline", func(t *testing.T) {
|
||||
t.Run("SetSyncStatusAndErr, offline", func(t *testing.T) {
|
||||
// given
|
||||
objectState := NewObjectState()
|
||||
|
||||
// when
|
||||
syncStatus := domain.MakeSyncStatus("spaceId", domain.Offline, 3, domain.Null, domain.Objects)
|
||||
objectState.SetSyncStatus(syncStatus)
|
||||
objectState.SetSyncStatusAndErr(syncStatus)
|
||||
|
||||
// then
|
||||
assert.Equal(t, domain.Offline, objectState.GetSyncStatus("spaceId"))
|
||||
|
|
|
@ -28,10 +28,10 @@ type TechSpaceIdGetter interface {
|
|||
|
||||
type State interface {
|
||||
SetObjectsNumber(status *domain.SpaceSync)
|
||||
SetSyncStatus(status *domain.SpaceSync)
|
||||
SetSyncStatusAndErr(status *domain.SpaceSync)
|
||||
GetSyncStatus(spaceId string) domain.SyncStatus
|
||||
GetSyncObjectCount(spaceId string) int
|
||||
IsSyncFinished(spaceId string) bool
|
||||
GetSyncErr(spaceId string) domain.SyncError
|
||||
}
|
||||
|
||||
type NetworkConfig interface {
|
||||
|
@ -118,14 +118,9 @@ func (s *spaceSyncStatus) processEvents() {
|
|||
}
|
||||
|
||||
func (s *spaceSyncStatus) updateSpaceSyncStatus(status *domain.SpaceSync) {
|
||||
// don't send unnecessary event
|
||||
if s.isSyncFinished(status) {
|
||||
return
|
||||
}
|
||||
|
||||
state := s.getCurrentState(status)
|
||||
state.SetObjectsNumber(status)
|
||||
state.SetSyncStatus(status)
|
||||
state.SetSyncStatusAndErr(status)
|
||||
|
||||
// send synced event only if files and objects are all synced
|
||||
if !s.needToSendEvent(status) {
|
||||
|
@ -156,16 +151,12 @@ func (s *spaceSyncStatus) Close(ctx context.Context) (err error) {
|
|||
return s.batcher.Close()
|
||||
}
|
||||
|
||||
func (s *spaceSyncStatus) isSyncFinished(status *domain.SpaceSync) bool {
|
||||
return status.Status == domain.Synced && s.filesState.IsSyncFinished(status.SpaceId) && s.objectsState.IsSyncFinished(status.SpaceId)
|
||||
}
|
||||
|
||||
func (s *spaceSyncStatus) makeSpaceSyncEvent(status *domain.SpaceSync) *pb.EventSpaceSyncStatusUpdate {
|
||||
return &pb.EventSpaceSyncStatusUpdate{
|
||||
Id: status.SpaceId,
|
||||
Status: mapStatus(s.getSpaceSyncStatus(status)),
|
||||
Network: mapNetworkMode(s.networkConfig.GetNetworkMode()),
|
||||
Error: mapError(status.SyncError),
|
||||
Error: s.getError(status.SpaceId),
|
||||
SyncingObjectsCounter: int64(s.filesState.GetSyncObjectCount(status.SpaceId) + s.objectsState.GetSyncObjectCount(status.SpaceId)),
|
||||
}
|
||||
}
|
||||
|
@ -215,6 +206,20 @@ func (s *spaceSyncStatus) getCurrentState(status *domain.SpaceSync) State {
|
|||
return s.objectsState
|
||||
}
|
||||
|
||||
func (s *spaceSyncStatus) getError(spaceId string) pb.EventSpaceSyncError {
|
||||
syncErr := s.filesState.GetSyncErr(spaceId)
|
||||
if syncErr != domain.Null {
|
||||
return mapError(syncErr)
|
||||
}
|
||||
|
||||
syncErr = s.objectsState.GetSyncErr(spaceId)
|
||||
if syncErr != domain.Null {
|
||||
return mapError(syncErr)
|
||||
}
|
||||
|
||||
return pb.EventSpace_Null
|
||||
}
|
||||
|
||||
func mapNetworkMode(mode pb.RpcAccountNetworkMode) pb.EventSpaceNetwork {
|
||||
switch mode {
|
||||
case pb.RpcAccount_LocalOnly:
|
||||
|
|
|
@ -81,28 +81,6 @@ func TestSpaceSyncStatus_Init(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSpaceSyncStatus_updateSpaceSyncStatus(t *testing.T) {
|
||||
t.Run("don't send not needed synced event", func(t *testing.T) {
|
||||
// given
|
||||
eventSender := mock_event.NewMockSender(t)
|
||||
status := spaceSyncStatus{
|
||||
eventSender: eventSender,
|
||||
networkConfig: &config.Config{NetworkMode: pb.RpcAccount_DefaultConfig},
|
||||
batcher: mb.New[*domain.SpaceSync](0),
|
||||
filesState: NewFileState(objectstore.NewStoreFixture(t)),
|
||||
objectsState: NewObjectState(),
|
||||
}
|
||||
syncStatus := domain.MakeSyncStatus("spaceId", domain.Synced, 0, domain.Null, domain.Files)
|
||||
status.filesState.SetSyncStatus(syncStatus)
|
||||
status.filesState.SetObjectsNumber(syncStatus)
|
||||
status.objectsState.SetSyncStatus(syncStatus)
|
||||
status.objectsState.SetObjectsNumber(syncStatus)
|
||||
|
||||
// then
|
||||
status.updateSpaceSyncStatus(syncStatus)
|
||||
|
||||
// when
|
||||
eventSender.AssertNotCalled(t, "Broadcast")
|
||||
})
|
||||
t.Run("syncing event for objects", func(t *testing.T) {
|
||||
// given
|
||||
eventSender := mock_event.NewMockSender(t)
|
||||
|
@ -199,7 +177,7 @@ func TestSpaceSyncStatus_updateSpaceSyncStatus(t *testing.T) {
|
|||
objectsState: NewObjectState(),
|
||||
}
|
||||
objectsSyncStatus := domain.MakeSyncStatus("spaceId", domain.Syncing, 2, domain.Null, domain.Objects)
|
||||
status.objectsState.SetSyncStatus(objectsSyncStatus)
|
||||
status.objectsState.SetSyncStatusAndErr(objectsSyncStatus)
|
||||
|
||||
// then
|
||||
syncStatus := domain.MakeSyncStatus("spaceId", domain.Synced, 0, domain.Null, domain.Files)
|
||||
|
@ -299,7 +277,7 @@ func TestSpaceSyncStatus_updateSpaceSyncStatus(t *testing.T) {
|
|||
}
|
||||
syncStatus := domain.MakeSyncStatus("spaceId", domain.Syncing, 2, domain.Null, domain.Objects)
|
||||
status.objectsState.SetObjectsNumber(syncStatus)
|
||||
status.objectsState.SetSyncStatus(syncStatus)
|
||||
status.objectsState.SetSyncStatusAndErr(syncStatus)
|
||||
|
||||
// then
|
||||
syncStatus = domain.MakeSyncStatus("spaceId", domain.Synced, 0, domain.Null, domain.Objects)
|
||||
|
|
|
@ -6456,7 +6456,7 @@ Makes blocks copy by given ids and paste it to shown place
|
|||
<a name="anytype-Rpc-BlockDataview-View-SetActive"></a>
|
||||
|
||||
### Rpc.BlockDataview.View.SetActive
|
||||
set the current active view (persisted only within a session)
|
||||
set the current active view locally
|
||||
|
||||
|
||||
|
||||
|
@ -6474,8 +6474,6 @@ set the current active view (persisted only within a session)
|
|||
| contextId | [string](#string) | | |
|
||||
| blockId | [string](#string) | | id of dataview block |
|
||||
| viewId | [string](#string) | | id of active view |
|
||||
| offset | [uint32](#uint32) | | |
|
||||
| limit | [uint32](#uint32) | | |
|
||||
|
||||
|
||||
|
||||
|
@ -19491,7 +19489,7 @@ Middleware-to-front-end response, that can contain a NULL error or a non-NULL er
|
|||
| ---- | ------ | ----------- |
|
||||
| NULL | 0 | |
|
||||
| UNKNOWN_ERROR | 1 | |
|
||||
| BAD_INPUT | 2 | ... |
|
||||
| BAD_INPUT | 2 | |
|
||||
|
||||
|
||||
|
||||
|
@ -25910,8 +25908,8 @@ Bookmark is to keep a web-link and to preview a content.
|
|||
| ----- | ---- | ----- | ----------- |
|
||||
| source | [string](#string) | repeated | |
|
||||
| views | [Block.Content.Dataview.View](#anytype-model-Block-Content-Dataview-View) | repeated | |
|
||||
| activeView | [string](#string) | | do not generate changes for this field |
|
||||
| relations | [Relation](#anytype-model-Relation) | repeated | deprecated |
|
||||
| activeView | [string](#string) | | saved within a session |
|
||||
| groupOrders | [Block.Content.Dataview.GroupOrder](#anytype-model-Block-Content-Dataview-GroupOrder) | repeated | |
|
||||
| objectOrders | [Block.Content.Dataview.ObjectOrder](#anytype-model-Block-Content-Dataview-ObjectOrder) | repeated | |
|
||||
| relationLinks | [RelationLink](#anytype-model-RelationLink) | repeated | |
|
||||
|
|
16
go.mod
16
go.mod
|
@ -91,11 +91,11 @@ require (
|
|||
go.uber.org/multierr v1.11.0
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
||||
golang.org/x/image v0.15.0
|
||||
golang.org/x/image v0.16.0
|
||||
golang.org/x/mobile v0.0.0-20240404231514-09dbf07665ed
|
||||
golang.org/x/net v0.25.0
|
||||
golang.org/x/oauth2 v0.19.0
|
||||
golang.org/x/text v0.15.0
|
||||
golang.org/x/net v0.26.0
|
||||
golang.org/x/oauth2 v0.20.0
|
||||
golang.org/x/text v0.16.0
|
||||
google.golang.org/grpc v1.64.0
|
||||
gopkg.in/Graylog2/go-gelf.v2 v2.0.0-20180125164251-1832d8546a9f
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
|
@ -260,13 +260,13 @@ require (
|
|||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/otel v1.14.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.14.0 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/crypto v0.24.0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/term v0.20.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
golang.org/x/term v0.21.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.21.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
|
|
32
go.sum
32
go.sum
|
@ -1527,8 +1527,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm
|
|||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
@ -1549,8 +1549,8 @@ golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86h
|
|||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw=
|
||||
golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
@ -1642,8 +1642,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -1653,8 +1653,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
|
|||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
|
||||
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
|
||||
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
|
||||
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -1763,8 +1763,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
|
@ -1772,8 +1772,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
|||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -1785,8 +1785,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
@ -1855,8 +1855,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
|||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
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=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
2284
pb/commands.pb.go
2284
pb/commands.pb.go
File diff suppressed because it is too large
Load diff
|
@ -5483,14 +5483,12 @@ message Rpc {
|
|||
}
|
||||
}
|
||||
}
|
||||
// set the current active view (persisted only within a session)
|
||||
// set the current active view locally
|
||||
message SetActive {
|
||||
message Request {
|
||||
string contextId = 1;
|
||||
string blockId = 2; // id of dataview block
|
||||
string viewId = 3; // id of active view
|
||||
uint32 offset = 4;
|
||||
uint32 limit = 5;
|
||||
}
|
||||
message Response {
|
||||
Error error = 1;
|
||||
|
@ -5504,7 +5502,6 @@ message Rpc {
|
|||
NULL = 0;
|
||||
UNKNOWN_ERROR = 1;
|
||||
BAD_INPUT = 2;
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
65
pkg/lib/localstore/objectstore/activeview.go
Normal file
65
pkg/lib/localstore/objectstore/activeview.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package objectstore
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/anyproto/anytype-heart/util/badgerhelper"
|
||||
)
|
||||
|
||||
const (
|
||||
blockViewSeparator = ":"
|
||||
viewsSeparator = ","
|
||||
)
|
||||
|
||||
var ErrParseView = errors.New("failed to parse view")
|
||||
|
||||
// SetActiveViews accepts map of active views by blocks, as objects can handle multiple dataview blocks
|
||||
func (s *dsObjectStore) SetActiveViews(objectId string, views map[string]string) error {
|
||||
return badgerhelper.SetValue(s.db, pagesActiveViewBase.ChildString(objectId).Bytes(), viewsMapToString(views))
|
||||
}
|
||||
|
||||
func (s *dsObjectStore) SetActiveView(objectId, blockId, viewId string) error {
|
||||
views, err := s.GetActiveViews(objectId)
|
||||
// if active views are not found in BD, or we could not parse them, then we need to rewrite them
|
||||
if err != nil && !badgerhelper.IsNotFound(err) && !errors.Is(err, ErrParseView) {
|
||||
return err
|
||||
}
|
||||
if views == nil {
|
||||
views = make(map[string]string, 1)
|
||||
}
|
||||
views[blockId] = viewId
|
||||
return s.SetActiveViews(objectId, views)
|
||||
}
|
||||
|
||||
// GetActiveViews returns a map of activeViews by block ids
|
||||
func (s *dsObjectStore) GetActiveViews(objectId string) (views map[string]string, err error) {
|
||||
raw, err := badgerhelper.GetValue(s.db, pagesActiveViewBase.ChildString(objectId).Bytes(), bytesToString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parseViewsMap(raw)
|
||||
}
|
||||
|
||||
func viewsMapToString(views map[string]string) (result string) {
|
||||
for block, view := range views {
|
||||
result = result + viewsSeparator + block + blockViewSeparator + view
|
||||
}
|
||||
if len(views) != 0 {
|
||||
result = result[1:]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func parseViewsMap(s string) (viewsMap map[string]string, err error) {
|
||||
viewsMap = make(map[string]string)
|
||||
views := strings.Split(s, viewsSeparator)
|
||||
for _, view := range views {
|
||||
parts := strings.Split(view, blockViewSeparator)
|
||||
if len(parts) != 2 {
|
||||
return nil, ErrParseView
|
||||
}
|
||||
viewsMap[parts[0]] = parts[1]
|
||||
}
|
||||
return viewsMap, nil
|
||||
}
|
39
pkg/lib/localstore/objectstore/activeview_test.go
Normal file
39
pkg/lib/localstore/objectstore/activeview_test.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package objectstore
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestViewsMapToString(t *testing.T) {
|
||||
assert.Contains(t, []string{"block1:view1,block2:view2", "block2:view2,block1:view1"}, viewsMapToString(map[string]string{"block1": "view1", "block2": "view2"}))
|
||||
assert.Equal(t, "", viewsMapToString(nil))
|
||||
assert.Equal(t, "", viewsMapToString(map[string]string{}))
|
||||
assert.Contains(t, []string{":view,block:", "block:,:view"}, viewsMapToString(map[string]string{"": "view", "block": ""}))
|
||||
}
|
||||
|
||||
func TestParseViewsMap(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name, str string
|
||||
expectedErr error
|
||||
expectedMap map[string]string
|
||||
}{
|
||||
{"success", "block1:view1,block2:view2", nil, map[string]string{"block1": "view1", "block2": "view2"}},
|
||||
{"empty", "", nil, nil},
|
||||
{"invalid", "invalid", ErrParseView, nil},
|
||||
{"empty ids", ":view,block:", nil, map[string]string{"": "view", "block": ""}},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
views, err := parseViewsMap(tc.str)
|
||||
if tc.expectedErr != nil {
|
||||
assert.Error(t, err)
|
||||
assert.True(t, errors.Is(err, tc.expectedErr))
|
||||
} else {
|
||||
assert.True(t, reflect.DeepEqual(tc.expectedMap, views))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -684,6 +684,64 @@ func (_c *MockObjectStore_GetAccountStatus_Call) RunAndReturn(run func() (*coord
|
|||
return _c
|
||||
}
|
||||
|
||||
// GetActiveViews provides a mock function with given fields: objectId
|
||||
func (_m *MockObjectStore) GetActiveViews(objectId string) (map[string]string, error) {
|
||||
ret := _m.Called(objectId)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetActiveViews")
|
||||
}
|
||||
|
||||
var r0 map[string]string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string) (map[string]string, error)); ok {
|
||||
return rf(objectId)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) map[string]string); ok {
|
||||
r0 = rf(objectId)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(map[string]string)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(objectId)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockObjectStore_GetActiveViews_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetActiveViews'
|
||||
type MockObjectStore_GetActiveViews_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetActiveViews is a helper method to define mock.On call
|
||||
// - objectId string
|
||||
func (_e *MockObjectStore_Expecter) GetActiveViews(objectId interface{}) *MockObjectStore_GetActiveViews_Call {
|
||||
return &MockObjectStore_GetActiveViews_Call{Call: _e.mock.On("GetActiveViews", objectId)}
|
||||
}
|
||||
|
||||
func (_c *MockObjectStore_GetActiveViews_Call) Run(run func(objectId string)) *MockObjectStore_GetActiveViews_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockObjectStore_GetActiveViews_Call) Return(_a0 map[string]string, _a1 error) *MockObjectStore_GetActiveViews_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockObjectStore_GetActiveViews_Call) RunAndReturn(run func(string) (map[string]string, error)) *MockObjectStore_GetActiveViews_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetByIDs provides a mock function with given fields: spaceID, ids
|
||||
func (_m *MockObjectStore) GetByIDs(spaceID string, ids []string) ([]*model.ObjectInfo, error) {
|
||||
ret := _m.Called(spaceID, ids)
|
||||
|
@ -2672,6 +2730,101 @@ func (_c *MockObjectStore_SaveVirtualSpace_Call) RunAndReturn(run func(string) e
|
|||
return _c
|
||||
}
|
||||
|
||||
// SetActiveView provides a mock function with given fields: objectId, blockId, viewId
|
||||
func (_m *MockObjectStore) SetActiveView(objectId string, blockId string, viewId string) error {
|
||||
ret := _m.Called(objectId, blockId, viewId)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SetActiveView")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, string) error); ok {
|
||||
r0 = rf(objectId, blockId, viewId)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockObjectStore_SetActiveView_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetActiveView'
|
||||
type MockObjectStore_SetActiveView_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// SetActiveView is a helper method to define mock.On call
|
||||
// - objectId string
|
||||
// - blockId string
|
||||
// - viewId string
|
||||
func (_e *MockObjectStore_Expecter) SetActiveView(objectId interface{}, blockId interface{}, viewId interface{}) *MockObjectStore_SetActiveView_Call {
|
||||
return &MockObjectStore_SetActiveView_Call{Call: _e.mock.On("SetActiveView", objectId, blockId, viewId)}
|
||||
}
|
||||
|
||||
func (_c *MockObjectStore_SetActiveView_Call) Run(run func(objectId string, blockId string, viewId string)) *MockObjectStore_SetActiveView_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string), args[2].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockObjectStore_SetActiveView_Call) Return(_a0 error) *MockObjectStore_SetActiveView_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockObjectStore_SetActiveView_Call) RunAndReturn(run func(string, string, string) error) *MockObjectStore_SetActiveView_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetActiveViews provides a mock function with given fields: objectId, views
|
||||
func (_m *MockObjectStore) SetActiveViews(objectId string, views map[string]string) error {
|
||||
ret := _m.Called(objectId, views)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SetActiveViews")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, map[string]string) error); ok {
|
||||
r0 = rf(objectId, views)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockObjectStore_SetActiveViews_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetActiveViews'
|
||||
type MockObjectStore_SetActiveViews_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// SetActiveViews is a helper method to define mock.On call
|
||||
// - objectId string
|
||||
// - views map[string]string
|
||||
func (_e *MockObjectStore_Expecter) SetActiveViews(objectId interface{}, views interface{}) *MockObjectStore_SetActiveViews_Call {
|
||||
return &MockObjectStore_SetActiveViews_Call{Call: _e.mock.On("SetActiveViews", objectId, views)}
|
||||
}
|
||||
|
||||
func (_c *MockObjectStore_SetActiveViews_Call) Run(run func(objectId string, views map[string]string)) *MockObjectStore_SetActiveViews_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(map[string]string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockObjectStore_SetActiveViews_Call) Return(_a0 error) *MockObjectStore_SetActiveViews_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockObjectStore_SetActiveViews_Call) RunAndReturn(run func(string, map[string]string) error) *MockObjectStore_SetActiveViews_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SubscribeForAll provides a mock function with given fields: callback
|
||||
func (_m *MockObjectStore) SubscribeForAll(callback func(database.Record)) {
|
||||
_m.Called(callback)
|
||||
|
|
|
@ -35,9 +35,10 @@ const CName = "objectstore"
|
|||
|
||||
var (
|
||||
// ObjectInfo is stored in db key pattern:
|
||||
pagesPrefix = "pages"
|
||||
pagesDetailsBase = ds.NewKey("/" + pagesPrefix + "/details")
|
||||
pendingDetailsBase = ds.NewKey("/" + pagesPrefix + "/pending")
|
||||
pagesPrefix = "pages"
|
||||
pagesDetailsBase = ds.NewKey("/" + pagesPrefix + "/details")
|
||||
pendingDetailsBase = ds.NewKey("/" + pagesPrefix + "/pending")
|
||||
pagesActiveViewBase = ds.NewKey("/" + pagesPrefix + "/activeView")
|
||||
|
||||
pagesSnippetBase = ds.NewKey("/" + pagesPrefix + "/snippet")
|
||||
pagesInboundLinksBase = ds.NewKey("/" + pagesPrefix + "/inbound")
|
||||
|
@ -145,6 +146,10 @@ type ObjectStore interface {
|
|||
GetOutboundLinksByID(id string) ([]string, error)
|
||||
GetWithLinksInfoByID(spaceID string, id string) (*model.ObjectInfoWithLinks, error)
|
||||
|
||||
SetActiveView(objectId, blockId, viewId string) error
|
||||
SetActiveViews(objectId string, views map[string]string) error
|
||||
GetActiveViews(objectId string) (map[string]string, error)
|
||||
|
||||
GetRelationLink(spaceID string, key string) (*model.RelationLink, error)
|
||||
FetchRelationByKey(spaceID string, key string) (relation *relationutils.Relation, err error)
|
||||
FetchRelationByKeys(spaceId string, keys ...string) (relations relationutils.Relations, err error)
|
||||
|
|
|
@ -22,6 +22,6 @@ func (m *Blob) Options(add map[string]interface{}) (string, error) {
|
|||
return hashOpts(make(map[string]string), add)
|
||||
}
|
||||
|
||||
func (m *Blob) Mill(r io.ReadSeeker, name string, sourceChecksum string) (*Result, error) {
|
||||
return &Result{File: r}, nil
|
||||
func (m *Blob) Mill(r io.ReadSeeker, name string) (*Result, error) {
|
||||
return &Result{File: noopCloser(r)}, nil
|
||||
}
|
||||
|
|
|
@ -12,7 +12,9 @@ func TestBlob_Mill(t *testing.T) {
|
|||
input := make([]byte, 512)
|
||||
rand.Read(input)
|
||||
|
||||
if _, err := m.Mill(bytes.NewReader(input), "test", ""); err != nil {
|
||||
if r, err := m.Mill(bytes.NewReader(input), "test"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
_ = r.File.Close()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,18 +14,17 @@ import (
|
|||
)
|
||||
|
||||
type ImageExifSchema struct {
|
||||
SourceChecksum string `json:"source_checksum"`
|
||||
Created time.Time `json:"created,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Ext string `json:"extension"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Format string `json:"format"`
|
||||
CameraModel string `json:"model,omitempty"`
|
||||
ISO int `json:"iso"`
|
||||
ExposureTime string `json:"exposure_time"`
|
||||
FNumber float64 `json:"f_number"`
|
||||
Created time.Time `json:"created,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Ext string `json:"extension"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Format string `json:"format"`
|
||||
CameraModel string `json:"model,omitempty"`
|
||||
ISO int `json:"iso"`
|
||||
ExposureTime string `json:"exposure_time"`
|
||||
FNumber float64 `json:"f_number"`
|
||||
|
||||
Latitude float64 `json:"latitude,omitempty"`
|
||||
Longitude float64 `json:"longitude,omitempty"`
|
||||
|
@ -57,7 +56,7 @@ func (m *ImageExif) Options(add map[string]interface{}) (string, error) {
|
|||
return hashOpts(make(map[string]string), add)
|
||||
}
|
||||
|
||||
func (m *ImageExif) Mill(r io.ReadSeeker, name string, sourceChecksum string) (*Result, error) {
|
||||
func (m *ImageExif) Mill(r io.ReadSeeker, name string) (*Result, error) {
|
||||
conf, formatStr, err := image.DecodeConfig(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -124,21 +123,20 @@ func (m *ImageExif) Mill(r io.ReadSeeker, name string, sourceChecksum string) (*
|
|||
}
|
||||
|
||||
res := &ImageExifSchema{
|
||||
SourceChecksum: sourceChecksum,
|
||||
Created: created,
|
||||
Name: name,
|
||||
Ext: strings.ToLower(filepath.Ext(name)),
|
||||
Format: string(format),
|
||||
CameraModel: model,
|
||||
ISO: iso,
|
||||
ExposureTime: exposureTime,
|
||||
FNumber: fNumber,
|
||||
Width: conf.Width,
|
||||
Height: conf.Height,
|
||||
Latitude: lat,
|
||||
Longitude: lon,
|
||||
Artist: artist,
|
||||
Description: description,
|
||||
Created: created,
|
||||
Name: name,
|
||||
Ext: strings.ToLower(filepath.Ext(name)),
|
||||
Format: string(format),
|
||||
CameraModel: model,
|
||||
ISO: iso,
|
||||
ExposureTime: exposureTime,
|
||||
FNumber: fNumber,
|
||||
Width: conf.Width,
|
||||
Height: conf.Height,
|
||||
Latitude: lat,
|
||||
Longitude: lon,
|
||||
Artist: artist,
|
||||
Description: description,
|
||||
}
|
||||
|
||||
b, err := jsonutil.MarshalSafely(res)
|
||||
|
@ -146,5 +144,5 @@ func (m *ImageExif) Mill(r io.ReadSeeker, name string, sourceChecksum string) (*
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return &Result{File: bytes.NewReader(b)}, nil
|
||||
return &Result{File: noopCloser(bytes.NewReader(b))}, nil
|
||||
}
|
||||
|
|
|
@ -2,12 +2,9 @@ package mill
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/mill/testdata"
|
||||
)
|
||||
|
||||
|
@ -20,7 +17,7 @@ func TestImageExif_Mill(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := m.Mill(file, "test", "")
|
||||
res, err := m.Mill(file, "test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -42,26 +39,3 @@ func TestImageExif_Mill(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageExif_Mill_Checksum(t *testing.T) {
|
||||
m := &ImageExif{}
|
||||
|
||||
file, err := os.Open("testdata/Landscape_8.jpg")
|
||||
require.NoError(t, err)
|
||||
defer file.Close()
|
||||
|
||||
res, err := m.Mill(file, "test", "FOO")
|
||||
require.NoError(t, err)
|
||||
|
||||
raw1, err := io.ReadAll(res.File)
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err = m.Mill(file, "test", "BAR")
|
||||
require.NoError(t, err)
|
||||
|
||||
raw2, err := io.ReadAll(res.File)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Different checksums produce different results
|
||||
require.NotEqual(t, raw1, raw2)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package mill
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
|
@ -99,13 +98,12 @@ func (m *ImageResize) Options(add map[string]interface{}) (string, error) {
|
|||
return hashOpts(m.Opts, add)
|
||||
}
|
||||
|
||||
func (m *ImageResize) Mill(r io.ReadSeeker, name string, sourceChecksum string) (*Result, error) {
|
||||
func (m *ImageResize) Mill(r io.ReadSeeker, name string) (*Result, error) {
|
||||
imgConfig, formatStr, err := image.DecodeConfig(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
format := Format(formatStr)
|
||||
|
||||
_, err = r.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -179,7 +177,7 @@ func (m *ImageResize) resizeJPEG(imgConfig *image.Config, r io.ReadSeeker) (*Res
|
|||
}
|
||||
|
||||
if orientation <= 1 && width == imgConfig.Width {
|
||||
var r2 io.Reader
|
||||
var r2 io.ReadSeekCloser
|
||||
r2, err = patchReaderRemoveExif(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -187,7 +185,7 @@ func (m *ImageResize) resizeJPEG(imgConfig *image.Config, r io.ReadSeeker) (*Res
|
|||
// here is an optimization
|
||||
// lets return the original picture in case it has not been resized or normalized
|
||||
return &Result{
|
||||
File: r2,
|
||||
File: noopCloser(r2),
|
||||
Meta: map[string]interface{}{
|
||||
"width": imgConfig.Width,
|
||||
"height": imgConfig.Height,
|
||||
|
@ -204,13 +202,21 @@ func (m *ImageResize) resizeJPEG(imgConfig *image.Config, r io.ReadSeeker) (*Res
|
|||
resized := imaging.Resize(img, width, 0, imaging.Lanczos)
|
||||
width, height = resized.Rect.Max.X, resized.Rect.Max.Y
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
buff := pool.Get()
|
||||
defer func() {
|
||||
_ = buff.Close()
|
||||
}()
|
||||
if err = jpeg.Encode(buff, resized, &jpeg.Options{Quality: quality}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
readCloser, err := buff.GetReadSeekCloser()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Result{
|
||||
File: buff,
|
||||
File: readCloser,
|
||||
Meta: map[string]interface{}{
|
||||
"width": width,
|
||||
"height": height,
|
||||
|
@ -234,7 +240,7 @@ func (m *ImageResize) resizePNG(imgConfig *image.Config, r io.ReadSeeker) (*Resu
|
|||
// here is an optimization
|
||||
// lets return the original picture in case it has not been resized or normalized
|
||||
return &Result{
|
||||
File: r,
|
||||
File: noopCloser(r),
|
||||
Meta: map[string]interface{}{
|
||||
"width": imgConfig.Width,
|
||||
"height": imgConfig.Height,
|
||||
|
@ -250,13 +256,20 @@ func (m *ImageResize) resizePNG(imgConfig *image.Config, r io.ReadSeeker) (*Resu
|
|||
resized := imaging.Resize(img, width, 0, imaging.Lanczos)
|
||||
width, height = resized.Rect.Max.X, resized.Rect.Max.Y
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
if err = png.Encode(buff, resized); err != nil {
|
||||
buf := pool.Get()
|
||||
defer func() {
|
||||
_ = buf.Close()
|
||||
}()
|
||||
if err = png.Encode(buf, resized); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
readSeekCloser, err := buf.GetReadSeekCloser()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Result{
|
||||
File: buff,
|
||||
File: readSeekCloser,
|
||||
Meta: map[string]interface{}{
|
||||
"width": width,
|
||||
"height": height,
|
||||
|
@ -279,7 +292,7 @@ func (m *ImageResize) resizeGIF(imgConfig *image.Config, r io.ReadSeeker) (*Resu
|
|||
// here is an optimization
|
||||
// lets return the original picture in case it has not been resized or normalized
|
||||
return &Result{
|
||||
File: r,
|
||||
File: noopCloser(r),
|
||||
Meta: map[string]interface{}{
|
||||
"width": imgConfig.Width,
|
||||
"height": imgConfig.Height,
|
||||
|
@ -302,13 +315,20 @@ func (m *ImageResize) resizeGIF(imgConfig *image.Config, r io.ReadSeeker) (*Resu
|
|||
}
|
||||
gifImg.Config.Width, gifImg.Config.Height = gifImg.Image[0].Bounds().Dx(), gifImg.Image[0].Bounds().Dy()
|
||||
|
||||
buff := bytes.NewBuffer(make([]byte, 0))
|
||||
if err = gif.EncodeAll(buff, gifImg); err != nil {
|
||||
buf := pool.Get()
|
||||
defer func() {
|
||||
_ = buf.Close()
|
||||
}()
|
||||
if err = gif.EncodeAll(buf, gifImg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
readSeekCloser, err := buf.GetReadSeekCloser()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Result{
|
||||
File: buff,
|
||||
File: readSeekCloser,
|
||||
Meta: map[string]interface{}{
|
||||
"width": gifImg.Config.Width,
|
||||
"height": gifImg.Config.Height,
|
||||
|
@ -379,7 +399,7 @@ func imageToPaletted(img image.Image) *image.Paletted {
|
|||
return pm
|
||||
}
|
||||
|
||||
func patchReaderRemoveExif(r io.ReadSeeker) (io.Reader, error) {
|
||||
func patchReaderRemoveExif(r io.ReadSeeker) (io.ReadSeekCloser, error) {
|
||||
jmp := jpegstructure.NewJpegMediaParser()
|
||||
size, err := r.Seek(0, io.SeekEnd)
|
||||
if err != nil {
|
||||
|
@ -387,7 +407,10 @@ func patchReaderRemoveExif(r io.ReadSeeker) (io.Reader, error) {
|
|||
}
|
||||
_, _ = r.Seek(0, io.SeekStart)
|
||||
|
||||
buff := bytes.NewBuffer(make([]byte, 0, size))
|
||||
buff := pool.Get()
|
||||
defer func() {
|
||||
_ = buff.Close()
|
||||
}()
|
||||
intfc, err := jmp.Parse(r, int(size))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open file to read exif: %w", err)
|
||||
|
@ -404,5 +427,5 @@ func patchReaderRemoveExif(r io.ReadSeeker) (io.Reader, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return buff, nil
|
||||
return buff.GetReadSeekCloser()
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
package mill
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
|
@ -35,14 +34,20 @@ func (m *ImageResize) resizeHEIC(imgConfig *image.Config, r io.ReadSeeker) (*Res
|
|||
return nil, fmt.Errorf("invalid quality: " + m.Opts.Quality)
|
||||
}
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
buf := pool.Get()
|
||||
defer func() {
|
||||
_ = buf.Close()
|
||||
}()
|
||||
|
||||
if err = jpeg.Encode(buff, resized, &jpeg.Options{Quality: quality}); err != nil {
|
||||
if err = jpeg.Encode(buf, resized, &jpeg.Options{Quality: quality}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
readSeekCloser, err := buf.GetReadSeekCloser()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Result{
|
||||
File: buff,
|
||||
File: readSeekCloser,
|
||||
Meta: map[string]interface{}{
|
||||
"width": width,
|
||||
"height": height,
|
||||
|
|
|
@ -56,7 +56,7 @@ func TestImageResize_Mill_ShouldRotateAndRemoveExif(t *testing.T) {
|
|||
|
||||
file.Seek(0, io.SeekStart)
|
||||
|
||||
res, err := cfg.Mill(file, "test", "")
|
||||
res, err := cfg.Mill(file, "test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ func TestImageResize_Mill_ShouldNotBeReencoded(t *testing.T) {
|
|||
|
||||
file.Seek(0, io.SeekStart)
|
||||
|
||||
res, err := cfg.Mill(file, "test", "")
|
||||
res, err := cfg.Mill(file, "test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -128,6 +128,9 @@ func TestImageResize_Mill_ShouldNotBeReencoded(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = res.File.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
img, err := jpeg.Decode(bytes.NewReader(b))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 680, img.Bounds().Max.X)
|
||||
|
@ -154,7 +157,7 @@ func TestImageResize_Mill(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := m.Mill(file, "test", "")
|
||||
res, err := m.Mill(file, "test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -169,6 +172,8 @@ func TestImageResize_Mill(t *testing.T) {
|
|||
t.Errorf("exif data was not removed")
|
||||
}
|
||||
file.Close()
|
||||
err = res.File.Close()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
package mill
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"io"
|
||||
|
@ -34,7 +33,7 @@ func (m *ImageResize) resizeWEBP(imgConfig *image.Config, r io.ReadSeeker) (*Res
|
|||
// here is an optimization
|
||||
// lets return the original picture in case it has not been resized or normalized
|
||||
return &Result{
|
||||
File: r,
|
||||
File: noopCloser(r),
|
||||
Meta: map[string]interface{}{
|
||||
"width": imgConfig.Width,
|
||||
"height": imgConfig.Height,
|
||||
|
@ -50,13 +49,21 @@ func (m *ImageResize) resizeWEBP(imgConfig *image.Config, r io.ReadSeeker) (*Res
|
|||
resized := imaging.Resize(img, width, 0, imaging.Lanczos)
|
||||
width, height = resized.Rect.Max.X, resized.Rect.Max.Y
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
if webp.Encode(buff, resized, &webp.Options{Quality: float32(quality)}) != nil {
|
||||
buf := pool.Get()
|
||||
defer func() {
|
||||
_ = buf.Close()
|
||||
}()
|
||||
if webp.Encode(buf, resized, &webp.Options{Quality: float32(quality)}) != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
readSeekCloser, err := buf.GetReadSeekCloser()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Result{
|
||||
File: buff,
|
||||
File: readSeekCloser,
|
||||
Meta: map[string]interface{}{
|
||||
"width": width,
|
||||
"height": height,
|
||||
|
|
|
@ -9,14 +9,17 @@ import (
|
|||
"github.com/mr-tron/base58/base58"
|
||||
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/logging"
|
||||
"github.com/anyproto/anytype-heart/util/bufferpool"
|
||||
)
|
||||
|
||||
var log = logging.Logger("tex-mill")
|
||||
|
||||
var pool = bufferpool.NewPool()
|
||||
|
||||
var ErrMediaTypeNotSupported = fmt.Errorf("media type not supported")
|
||||
|
||||
type Result struct {
|
||||
File io.Reader
|
||||
File io.ReadSeekCloser
|
||||
Meta map[string]interface{}
|
||||
}
|
||||
|
||||
|
@ -25,7 +28,7 @@ type Mill interface {
|
|||
Pin() bool // pin by default
|
||||
AcceptMedia(media string) error
|
||||
Options(add map[string]interface{}) (string, error)
|
||||
Mill(r io.ReadSeeker, name string, sourceChecksum string) (*Result, error)
|
||||
Mill(r io.ReadSeeker, name string) (*Result, error)
|
||||
}
|
||||
|
||||
func accepts(list []string, media string) error {
|
||||
|
|
15
pkg/lib/mill/nopocloser.go
Normal file
15
pkg/lib/mill/nopocloser.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package mill
|
||||
|
||||
import "io"
|
||||
|
||||
type noopCloserWrapper struct {
|
||||
io.ReadSeeker
|
||||
}
|
||||
|
||||
func (n *noopCloserWrapper) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func noopCloser(r io.ReadSeeker) io.ReadSeekCloser {
|
||||
return &noopCloserWrapper{r}
|
||||
}
|
|
@ -3591,11 +3591,11 @@ func (m *BlockContentSmartblock) XXX_DiscardUnknown() {
|
|||
var xxx_messageInfo_BlockContentSmartblock proto.InternalMessageInfo
|
||||
|
||||
type BlockContentDataview struct {
|
||||
Source []string `protobuf:"bytes,1,rep,name=source,proto3" json:"source,omitempty"`
|
||||
Views []*BlockContentDataviewView `protobuf:"bytes,2,rep,name=views,proto3" json:"views,omitempty"`
|
||||
Source []string `protobuf:"bytes,1,rep,name=source,proto3" json:"source,omitempty"`
|
||||
Views []*BlockContentDataviewView `protobuf:"bytes,2,rep,name=views,proto3" json:"views,omitempty"`
|
||||
ActiveView string `protobuf:"bytes,3,opt,name=activeView,proto3" json:"activeView,omitempty"`
|
||||
// deprecated
|
||||
Relations []*Relation `protobuf:"bytes,4,rep,name=relations,proto3" json:"relations,omitempty"`
|
||||
ActiveView string `protobuf:"bytes,3,opt,name=activeView,proto3" json:"activeView,omitempty"`
|
||||
GroupOrders []*BlockContentDataviewGroupOrder `protobuf:"bytes,12,rep,name=groupOrders,proto3" json:"groupOrders,omitempty"`
|
||||
ObjectOrders []*BlockContentDataviewObjectOrder `protobuf:"bytes,13,rep,name=objectOrders,proto3" json:"objectOrders,omitempty"`
|
||||
RelationLinks []*RelationLink `protobuf:"bytes,5,rep,name=relationLinks,proto3" json:"relationLinks,omitempty"`
|
||||
|
@ -3650,13 +3650,6 @@ func (m *BlockContentDataview) GetViews() []*BlockContentDataviewView {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *BlockContentDataview) GetRelations() []*Relation {
|
||||
if m != nil {
|
||||
return m.Relations
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *BlockContentDataview) GetActiveView() string {
|
||||
if m != nil {
|
||||
return m.ActiveView
|
||||
|
@ -3664,6 +3657,13 @@ func (m *BlockContentDataview) GetActiveView() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (m *BlockContentDataview) GetRelations() []*Relation {
|
||||
if m != nil {
|
||||
return m.Relations
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *BlockContentDataview) GetGroupOrders() []*BlockContentDataviewGroupOrder {
|
||||
if m != nil {
|
||||
return m.GroupOrders
|
||||
|
@ -9036,7 +9036,7 @@ func init() {
|
|||
}
|
||||
|
||||
var fileDescriptor_98a910b73321e591 = []byte{
|
||||
// 8233 bytes of a gzipped FileDescriptorProto
|
||||
// 8232 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x7c, 0x5b, 0x8c, 0x24, 0xd9,
|
||||
0x95, 0x50, 0xe5, 0x3b, 0xf3, 0x64, 0x3d, 0x6e, 0xdd, 0x7e, 0xa5, 0x73, 0x7a, 0x7b, 0xdb, 0xb1,
|
||||
0xf6, 0xb8, 0xdd, 0x1e, 0x57, 0xcf, 0xf4, 0xbc, 0x67, 0x3d, 0x33, 0xae, 0x67, 0x57, 0xce, 0xd4,
|
||||
|
@ -9169,389 +9169,389 @@ var fileDescriptor_98a910b73321e591 = []byte{
|
|||
0x8d, 0xc7, 0xd2, 0x61, 0x79, 0xeb, 0x9d, 0x19, 0x6a, 0x76, 0x09, 0x6a, 0x8f, 0x87, 0x5e, 0x20,
|
||||
0x9c, 0x2b, 0xf4, 0xec, 0x22, 0x40, 0xea, 0x1c, 0x37, 0x7f, 0xff, 0x57, 0xd3, 0xe3, 0x1c, 0x6d,
|
||||
0xd1, 0x28, 0x18, 0x85, 0x5d, 0x49, 0x2a, 0xa4, 0x66, 0x9b, 0x16, 0xff, 0x36, 0x94, 0xf0, 0x7d,
|
||||
0x1c, 0x8d, 0xb9, 0x3f, 0x97, 0x4b, 0xb6, 0xf6, 0xc4, 0x95, 0xcf, 0x6d, 0x4d, 0xc8, 0xdf, 0xce,
|
||||
0x9a, 0x27, 0x57, 0x87, 0x0b, 0x33, 0x76, 0x0b, 0xbf, 0x03, 0x20, 0xba, 0xca, 0x3d, 0x93, 0xc8,
|
||||
0xcb, 0xe8, 0x88, 0x0c, 0x84, 0xdb, 0x50, 0xc7, 0xad, 0x3b, 0x3c, 0x0c, 0x71, 0xb7, 0x37, 0x16,
|
||||
0x89, 0xf1, 0xeb, 0xf3, 0x75, 0xef, 0x51, 0x42, 0x68, 0x67, 0x99, 0xf0, 0xc7, 0xb0, 0xa8, 0x43,
|
||||
0x63, 0x86, 0xe9, 0x12, 0x31, 0x7d, 0x63, 0x3e, 0xa6, 0x87, 0x29, 0xa5, 0x3d, 0xc6, 0x66, 0x3a,
|
||||
0xba, 0x58, 0x7a, 0xe9, 0xe8, 0xe2, 0xab, 0xb0, 0xdc, 0x19, 0xdf, 0x05, 0xfa, 0xa8, 0x98, 0x80,
|
||||
0x72, 0x0b, 0x16, 0xdd, 0x28, 0x0d, 0x6e, 0x52, 0xa8, 0xa3, 0x6a, 0x8f, 0xc1, 0x9a, 0xff, 0xae,
|
||||
0x0c, 0x45, 0x9a, 0xc2, 0xc9, 0x50, 0xd5, 0xe6, 0x98, 0x4a, 0x7f, 0x30, 0xff, 0x52, 0x4f, 0xec,
|
||||
0x78, 0xd2, 0x20, 0x85, 0x8c, 0x06, 0xf9, 0x36, 0x94, 0xa2, 0x20, 0x54, 0xf1, 0xf2, 0xcf, 0x29,
|
||||
0x44, 0xed, 0x20, 0x54, 0xb6, 0x26, 0xe4, 0x3b, 0x50, 0x39, 0x71, 0x3d, 0x85, 0x8b, 0xa2, 0x27,
|
||||
0xef, 0xb5, 0xf9, 0x78, 0xec, 0x10, 0x91, 0x1d, 0x13, 0xf3, 0xbd, 0xac, 0x30, 0x96, 0x89, 0xd3,
|
||||
0xda, 0x7c, 0x9c, 0x66, 0xc9, 0xe8, 0x7d, 0x60, 0xdd, 0xe0, 0x4c, 0x86, 0x76, 0x26, 0xbe, 0xa8,
|
||||
0x0f, 0xe9, 0x29, 0x38, 0x6f, 0x42, 0xb5, 0xef, 0x3a, 0x12, 0xed, 0x1c, 0xd2, 0x31, 0x55, 0x3b,
|
||||
0x69, 0xf3, 0x4f, 0xa1, 0x4a, 0xfe, 0x01, 0x6a, 0xc5, 0xda, 0x4b, 0x4f, 0xbe, 0x76, 0x55, 0x62,
|
||||
0x06, 0xf8, 0x21, 0xfa, 0xf8, 0x8e, 0xab, 0x28, 0xcc, 0x5c, 0xb5, 0x93, 0x36, 0x76, 0x98, 0xe4,
|
||||
0x3d, 0xdb, 0xe1, 0xba, 0xee, 0xf0, 0x24, 0x9c, 0xbf, 0x05, 0x37, 0x08, 0x36, 0x71, 0x48, 0xe2,
|
||||
0x56, 0x43, 0xa6, 0xb3, 0x5f, 0xa2, 0xc1, 0x32, 0x14, 0x3d, 0xb9, 0xe7, 0x0e, 0x5c, 0xd5, 0x58,
|
||||
0xba, 0x9b, 0xbb, 0x57, 0xb2, 0x53, 0x00, 0x7f, 0x0d, 0x56, 0x1d, 0x79, 0x22, 0x46, 0x9e, 0xea,
|
||||
0xc8, 0xc1, 0xd0, 0x13, 0x4a, 0xb6, 0x1c, 0x92, 0xd1, 0x9a, 0x3d, 0xfd, 0x82, 0xbf, 0x0e, 0xd7,
|
||||
0x0c, 0xf0, 0x30, 0x49, 0x0e, 0xb4, 0x1c, 0x8a, 0xc2, 0xd5, 0xec, 0x59, 0xaf, 0xac, 0x7d, 0xa3,
|
||||
0x86, 0xf1, 0x00, 0x45, 0x3f, 0x35, 0x56, 0xa0, 0x91, 0xd2, 0x27, 0xf2, 0x23, 0xe1, 0x79, 0x32,
|
||||
0xbc, 0xd0, 0x4e, 0xee, 0xa7, 0xc2, 0x3f, 0x16, 0x3e, 0x2b, 0xd0, 0x19, 0x2b, 0x3c, 0xe9, 0x3b,
|
||||
0x22, 0xd4, 0x27, 0xf2, 0x23, 0x3a, 0xd0, 0x4b, 0xd6, 0x3d, 0x28, 0xd2, 0x94, 0xd6, 0xa0, 0xa4,
|
||||
0xbd, 0x24, 0xf2, 0x98, 0x8d, 0x87, 0x44, 0x1a, 0x79, 0x0f, 0xb7, 0x1f, 0xcb, 0x37, 0x7f, 0x56,
|
||||
0x80, 0x6a, 0x3c, 0x79, 0x71, 0x2a, 0x20, 0x97, 0xa6, 0x02, 0xd0, 0x8c, 0x8b, 0x9e, 0xb8, 0x91,
|
||||
0x7b, 0x6c, 0xcc, 0xd2, 0xaa, 0x9d, 0x02, 0xd0, 0x12, 0x7a, 0xee, 0x3a, 0xaa, 0x4f, 0x7b, 0xa6,
|
||||
0x64, 0xeb, 0x06, 0xbf, 0x07, 0x2b, 0x0e, 0xce, 0x83, 0xdf, 0xf5, 0x46, 0x8e, 0xec, 0xe0, 0x29,
|
||||
0xaa, 0xc3, 0x04, 0x93, 0x60, 0xfe, 0x3d, 0x00, 0xe5, 0x0e, 0xe4, 0x4e, 0x10, 0x0e, 0x84, 0x32,
|
||||
0xbe, 0xc1, 0xfb, 0x2f, 0x27, 0xd5, 0x6b, 0x9d, 0x84, 0x81, 0x9d, 0x61, 0x86, 0xac, 0xf1, 0x6b,
|
||||
0x86, 0x75, 0xe5, 0x0b, 0xb1, 0xde, 0x4a, 0x18, 0xd8, 0x19, 0x66, 0xd6, 0x6f, 0x00, 0xa4, 0x6f,
|
||||
0xf8, 0x4d, 0xe0, 0xfb, 0x81, 0xaf, 0xfa, 0xeb, 0xc7, 0xc7, 0xe1, 0x86, 0x3c, 0x09, 0x42, 0xb9,
|
||||
0x25, 0xf0, 0x58, 0xbb, 0x01, 0xab, 0x09, 0x7c, 0xfd, 0x44, 0xc9, 0x10, 0xc1, 0x34, 0xf5, 0xed,
|
||||
0x7e, 0x10, 0x2a, 0x6d, 0x5b, 0xd1, 0xe3, 0xe3, 0x36, 0x2b, 0xe0, 0x51, 0xda, 0x6a, 0x1f, 0xb2,
|
||||
0xa2, 0x75, 0x0f, 0x20, 0x1d, 0x12, 0xf9, 0x20, 0xf4, 0xf4, 0xc6, 0x43, 0xe3, 0x91, 0x50, 0xeb,
|
||||
0xe1, 0x5b, 0x2c, 0xd7, 0xfc, 0xa3, 0x02, 0x14, 0x51, 0xd5, 0xa0, 0xfb, 0x95, 0xdd, 0x17, 0x7a,
|
||||
0xf9, 0xb2, 0xa0, 0x2f, 0xa6, 0x20, 0x91, 0x77, 0x56, 0x41, 0xbe, 0x07, 0xf5, 0xee, 0x28, 0x52,
|
||||
0xc1, 0x80, 0x4e, 0x07, 0x93, 0x47, 0xb9, 0x39, 0x15, 0xc8, 0x78, 0x22, 0xbc, 0x91, 0xb4, 0xb3,
|
||||
0xa8, 0xfc, 0x6d, 0x28, 0x9f, 0xe8, 0x85, 0xd0, 0xa1, 0x8c, 0x5f, 0xb9, 0xe4, 0x00, 0x31, 0x93,
|
||||
0x6d, 0x90, 0x71, 0x5c, 0xee, 0x94, 0x10, 0x65, 0x41, 0xe6, 0x20, 0x28, 0x27, 0x07, 0xc1, 0x6f,
|
||||
0xc0, 0xb2, 0x44, 0xb3, 0xe2, 0xc8, 0x13, 0x5d, 0x39, 0x90, 0x7e, 0xbc, 0xf2, 0x6f, 0xbd, 0xc4,
|
||||
0x88, 0xc9, 0x2e, 0xa1, 0x61, 0x4f, 0xf0, 0xb2, 0xbe, 0x6a, 0x36, 0x69, 0x05, 0x0a, 0xeb, 0x51,
|
||||
0xd7, 0xb8, 0xdd, 0x32, 0xea, 0x6a, 0x9b, 0x7e, 0x93, 0x06, 0xcc, 0xf2, 0xd6, 0x1b, 0x50, 0x4b,
|
||||
0x78, 0x70, 0x06, 0x8b, 0x07, 0x81, 0x6a, 0x0f, 0x65, 0xd7, 0x3d, 0x71, 0xa5, 0xa3, 0x03, 0x09,
|
||||
0x6d, 0x25, 0x42, 0xa5, 0x23, 0x57, 0xdb, 0xbe, 0xc3, 0xf2, 0xcd, 0x7f, 0x5b, 0x81, 0xb2, 0xd6,
|
||||
0xf8, 0x66, 0x48, 0xb5, 0x64, 0x48, 0xdf, 0x81, 0x6a, 0x30, 0x94, 0xa1, 0x50, 0x41, 0x68, 0xc2,
|
||||
0x05, 0x6f, 0xbf, 0xcc, 0x09, 0xb2, 0x76, 0x68, 0x88, 0xed, 0x84, 0xcd, 0xa4, 0xbc, 0xe4, 0xa7,
|
||||
0xe5, 0xe5, 0x3e, 0xb0, 0xf8, 0xb0, 0x38, 0x0a, 0x91, 0x4e, 0x5d, 0x18, 0xe7, 0x6f, 0x0a, 0xce,
|
||||
0x3b, 0x50, 0xeb, 0x06, 0xbe, 0xe3, 0x26, 0xa1, 0x83, 0xe5, 0x87, 0xef, 0xbc, 0x54, 0x0f, 0x37,
|
||||
0x63, 0x6a, 0x3b, 0x65, 0xc4, 0x5f, 0x83, 0xd2, 0x19, 0x0a, 0x12, 0x49, 0xcc, 0xe5, 0x62, 0xa6,
|
||||
0x91, 0xf8, 0x67, 0x50, 0xff, 0xfe, 0xc8, 0xed, 0x9e, 0x1e, 0x66, 0x43, 0x53, 0xef, 0xbd, 0x54,
|
||||
0x2f, 0xbe, 0x93, 0xd2, 0xdb, 0x59, 0x66, 0x19, 0xe1, 0xad, 0xfc, 0x29, 0x84, 0xb7, 0x3a, 0x25,
|
||||
0xbc, 0xd6, 0x2b, 0x50, 0x8d, 0x17, 0x87, 0x44, 0xca, 0x47, 0xe9, 0x28, 0x43, 0xfe, 0x30, 0x64,
|
||||
0x39, 0xeb, 0x8f, 0x73, 0x50, 0x4b, 0x26, 0x66, 0x3c, 0x0c, 0xb5, 0xfd, 0xfd, 0x91, 0xf0, 0x58,
|
||||
0x8e, 0xfc, 0xa8, 0x40, 0xe9, 0x16, 0x69, 0x93, 0x47, 0x94, 0x8c, 0x0d, 0x59, 0x81, 0xce, 0x0e,
|
||||
0x19, 0x45, 0xac, 0xc8, 0x39, 0x2c, 0x1b, 0xf0, 0x61, 0xa8, 0x51, 0x4b, 0xe8, 0x66, 0xe1, 0xdb,
|
||||
0x18, 0x50, 0xd6, 0x47, 0xcd, 0xa9, 0xd4, 0x6e, 0xe4, 0x41, 0xa0, 0xa8, 0x51, 0xc5, 0xbe, 0xb4,
|
||||
0x7c, 0x56, 0xc3, 0x6f, 0x1e, 0x04, 0xaa, 0xe5, 0x33, 0x48, 0xed, 0xf6, 0x7a, 0xfc, 0x79, 0x6a,
|
||||
0x2d, 0x92, 0x57, 0xe0, 0x79, 0x2d, 0x9f, 0x2d, 0x99, 0x17, 0xba, 0xb5, 0x8c, 0x1c, 0xb7, 0xcf,
|
||||
0x45, 0x17, 0xc9, 0x57, 0xf8, 0x32, 0x00, 0xd2, 0x98, 0x36, 0xc3, 0x6d, 0xb3, 0x7d, 0xee, 0x46,
|
||||
0x2a, 0x62, 0xab, 0xd6, 0xbf, 0xc9, 0x41, 0x3d, 0xb3, 0x08, 0xe8, 0x17, 0x10, 0x22, 0xea, 0x5a,
|
||||
0xed, 0x26, 0x7c, 0x4f, 0x46, 0x4a, 0x86, 0x4e, 0xac, 0x47, 0x3b, 0x01, 0x3e, 0xe6, 0xf1, 0x7b,
|
||||
0x9d, 0x60, 0x10, 0x84, 0x61, 0xf0, 0x5c, 0x9f, 0x89, 0x7b, 0x22, 0x52, 0x4f, 0xa5, 0x3c, 0x65,
|
||||
0x45, 0x1c, 0xea, 0xe6, 0x28, 0x0c, 0xa5, 0xaf, 0x01, 0x25, 0xea, 0x9c, 0x3c, 0xd7, 0xad, 0x32,
|
||||
0x32, 0x45, 0x64, 0x52, 0xd4, 0xac, 0x82, 0x9b, 0xd5, 0x60, 0x6b, 0x48, 0x15, 0x11, 0x10, 0x5d,
|
||||
0x37, 0x6b, 0xe8, 0x7a, 0x6b, 0xd7, 0xf5, 0xf0, 0x64, 0x4b, 0x5c, 0x44, 0xeb, 0xbd, 0x80, 0xc1,
|
||||
0x24, 0xf0, 0x20, 0x78, 0xce, 0xea, 0xcd, 0x11, 0x40, 0x6a, 0xac, 0xa3, 0x93, 0x82, 0xb2, 0x96,
|
||||
0x04, 0x97, 0x4d, 0x8b, 0x1f, 0x02, 0xe0, 0x13, 0x61, 0xc6, 0x9e, 0xca, 0x4b, 0x58, 0x50, 0x44,
|
||||
0x67, 0x67, 0x58, 0x34, 0xff, 0x22, 0xd4, 0x92, 0x17, 0xe8, 0x9b, 0x92, 0xad, 0x93, 0x7c, 0x36,
|
||||
0x6e, 0xe2, 0xc1, 0xed, 0xfa, 0x8e, 0x3c, 0xa7, 0xbd, 0x5f, 0xb2, 0x75, 0x03, 0x7b, 0xd9, 0x77,
|
||||
0x1d, 0x47, 0xfa, 0x71, 0x0a, 0x40, 0xb7, 0x66, 0xe5, 0x5b, 0x8b, 0x33, 0xf3, 0xad, 0xcd, 0xdf,
|
||||
0x84, 0x7a, 0xc6, 0x9b, 0xb8, 0x74, 0xd8, 0x99, 0x8e, 0xe5, 0xc7, 0x3b, 0x76, 0x1b, 0x6a, 0x71,
|
||||
0x8e, 0x3f, 0xa2, 0x13, 0xa6, 0x66, 0xa7, 0x80, 0xe6, 0x3f, 0xcb, 0xa3, 0x89, 0x83, 0x43, 0x9b,
|
||||
0xf4, 0x00, 0x76, 0xa0, 0x8c, 0xee, 0xf0, 0x28, 0x4e, 0x56, 0xcf, 0x69, 0x65, 0xb7, 0x89, 0x66,
|
||||
0x77, 0xc1, 0x36, 0xd4, 0xfc, 0x43, 0x28, 0x28, 0xd1, 0x33, 0x11, 0xb4, 0xaf, 0xcf, 0xc7, 0xa4,
|
||||
0x23, 0x7a, 0xbb, 0x0b, 0x36, 0xd2, 0xf1, 0x3d, 0xa8, 0x76, 0x4d, 0xd0, 0xc3, 0x28, 0xae, 0x39,
|
||||
0x8d, 0xf4, 0x38, 0x54, 0xb2, 0xbb, 0x60, 0x27, 0x1c, 0xf8, 0xb7, 0xa1, 0x88, 0x66, 0x87, 0xc9,
|
||||
0xe9, 0xcf, 0xe9, 0x7c, 0xe0, 0x76, 0xd9, 0x5d, 0xb0, 0x89, 0x72, 0xa3, 0x02, 0x25, 0xd2, 0x93,
|
||||
0xcd, 0x06, 0x94, 0xf5, 0x58, 0x27, 0x67, 0xae, 0x79, 0x0b, 0x0a, 0x1d, 0xd1, 0x43, 0xd3, 0xcf,
|
||||
0x75, 0x22, 0xe3, 0x43, 0xe3, 0x63, 0xf3, 0x2b, 0x69, 0x00, 0x27, 0x1b, 0x1b, 0xcc, 0x8d, 0xc5,
|
||||
0x06, 0x9b, 0x65, 0x28, 0xe2, 0x17, 0x9b, 0xb7, 0xaf, 0x32, 0x23, 0x9b, 0xff, 0x23, 0x8f, 0x16,
|
||||
0xa7, 0x92, 0xe7, 0x33, 0xe3, 0x9e, 0x9f, 0x40, 0x6d, 0x18, 0x06, 0x5d, 0x19, 0x45, 0x41, 0x68,
|
||||
0x4c, 0x94, 0xd7, 0x5e, 0x9c, 0x5a, 0x5c, 0x3b, 0x8a, 0x69, 0xec, 0x94, 0xdc, 0xfa, 0x1b, 0x79,
|
||||
0xa8, 0x25, 0x2f, 0xb4, 0xa1, 0xab, 0xe4, 0xb9, 0x8e, 0x71, 0xed, 0xcb, 0x70, 0x20, 0x5c, 0x47,
|
||||
0x6b, 0x8f, 0xcd, 0xbe, 0x88, 0xad, 0xb0, 0xef, 0x05, 0x23, 0x35, 0x3a, 0x96, 0x3a, 0xb6, 0xf1,
|
||||
0xc4, 0x1d, 0xc8, 0x80, 0x15, 0x29, 0xab, 0x80, 0x82, 0xdd, 0xf5, 0x82, 0x91, 0xc3, 0x4a, 0xd8,
|
||||
0x7e, 0x44, 0x47, 0xd0, 0xbe, 0x18, 0x46, 0x5a, 0x67, 0xee, 0xbb, 0x61, 0xc0, 0x2a, 0x48, 0xb4,
|
||||
0xe3, 0xf6, 0x06, 0x82, 0x55, 0x91, 0x59, 0xe7, 0xb9, 0xab, 0x50, 0x09, 0xd7, 0xf8, 0x2a, 0x2c,
|
||||
0x1d, 0x0e, 0xa5, 0xdf, 0x56, 0xa1, 0x94, 0x6a, 0x5f, 0x0c, 0x75, 0xb0, 0xcb, 0x96, 0x8e, 0xe3,
|
||||
0x2a, 0xad, 0x3f, 0x77, 0x44, 0x57, 0x1e, 0x07, 0xc1, 0x29, 0x5b, 0x44, 0x45, 0xd3, 0xf2, 0x23,
|
||||
0x25, 0x7a, 0xa1, 0x18, 0x68, 0x1d, 0xda, 0x91, 0x9e, 0xa4, 0xd6, 0x32, 0x7d, 0xdb, 0x55, 0xfd,
|
||||
0xd1, 0xf1, 0x23, 0x74, 0x08, 0x56, 0x74, 0x02, 0xc2, 0x91, 0x43, 0x89, 0x3a, 0x74, 0x11, 0xaa,
|
||||
0x1b, 0xae, 0xe7, 0x1e, 0xbb, 0x9e, 0xcb, 0x56, 0x11, 0x75, 0xfb, 0xbc, 0x2b, 0x3c, 0xd7, 0x09,
|
||||
0xc5, 0x73, 0xc6, 0x9b, 0xab, 0xb0, 0x32, 0x91, 0x42, 0x6d, 0x56, 0x8c, 0x8f, 0xd1, 0x5c, 0x82,
|
||||
0x7a, 0x26, 0x29, 0xd6, 0x7c, 0x15, 0xaa, 0x71, 0xca, 0x0c, 0x7d, 0x31, 0x37, 0xd2, 0xc1, 0x3e,
|
||||
0xb3, 0xe2, 0x49, 0xbb, 0xf9, 0x4f, 0x73, 0x50, 0xd6, 0x69, 0x47, 0xbe, 0x91, 0x94, 0x09, 0xe4,
|
||||
0xe6, 0xc8, 0x51, 0x69, 0x22, 0x93, 0xe1, 0x4b, 0x6a, 0x05, 0xae, 0x43, 0xc9, 0x23, 0xa7, 0xcb,
|
||||
0xe8, 0x22, 0x6a, 0x64, 0x54, 0x47, 0x21, 0xab, 0x3a, 0xac, 0x77, 0x93, 0xac, 0x62, 0x1c, 0x60,
|
||||
0x22, 0x33, 0xac, 0x13, 0x4a, 0xa9, 0x83, 0x47, 0xe4, 0x33, 0xe5, 0x49, 0xf1, 0x07, 0x83, 0xa1,
|
||||
0xe8, 0x2a, 0x02, 0x14, 0xac, 0x13, 0xa8, 0x1e, 0x05, 0xd1, 0xe4, 0x71, 0x5a, 0x81, 0x42, 0x27,
|
||||
0x18, 0x6a, 0x03, 0x6e, 0x23, 0x50, 0x64, 0xc0, 0xe9, 0xd3, 0xf3, 0x44, 0x69, 0x79, 0xb0, 0xdd,
|
||||
0x5e, 0x5f, 0x69, 0xef, 0xaa, 0xe5, 0xfb, 0x32, 0x64, 0x25, 0x9c, 0x7e, 0x5b, 0x0e, 0xd1, 0x2c,
|
||||
0x64, 0x65, 0x9c, 0x70, 0x82, 0xef, 0xb8, 0x61, 0xa4, 0x58, 0xc5, 0x6a, 0xe1, 0x41, 0xe8, 0xf6,
|
||||
0xe8, 0xfc, 0xa2, 0x07, 0x62, 0xb5, 0x80, 0x1d, 0xa2, 0xe6, 0xa6, 0xf4, 0x51, 0x3c, 0x28, 0x6d,
|
||||
0xa5, 0xeb, 0x46, 0xe8, 0x03, 0x79, 0x3c, 0x7c, 0xa8, 0xfd, 0xc9, 0x28, 0x52, 0xee, 0xc9, 0x05,
|
||||
0x2b, 0x58, 0x4f, 0x61, 0x69, 0xac, 0xc2, 0x84, 0x5f, 0x07, 0x36, 0x06, 0xc0, 0xae, 0x2f, 0xf0,
|
||||
0x5b, 0x70, 0x6d, 0x0c, 0xba, 0xef, 0x3a, 0x0e, 0xc5, 0xef, 0x26, 0x5f, 0xc4, 0x03, 0xdc, 0xa8,
|
||||
0x41, 0xa5, 0xab, 0xd7, 0xc4, 0x3a, 0x82, 0x25, 0x5a, 0xa4, 0x7d, 0xa9, 0xc4, 0xa1, 0xef, 0x5d,
|
||||
0xfc, 0xa9, 0xcb, 0x80, 0xac, 0x6f, 0x40, 0x89, 0xe2, 0xed, 0xb8, 0xd5, 0x4f, 0xc2, 0x60, 0x40,
|
||||
0xbc, 0x4a, 0x36, 0x3d, 0x23, 0x77, 0x15, 0x98, 0x95, 0xce, 0xab, 0xc0, 0xfa, 0x11, 0x40, 0x65,
|
||||
0xbd, 0xdb, 0x0d, 0x46, 0xbe, 0x9a, 0xfa, 0xf2, 0xac, 0x90, 0xee, 0xdb, 0x50, 0x16, 0x67, 0x42,
|
||||
0x89, 0xd0, 0xa8, 0xe8, 0x49, 0x63, 0xcc, 0xf0, 0x5a, 0x5b, 0x27, 0x24, 0xdb, 0x20, 0x23, 0x59,
|
||||
0x37, 0xf0, 0x4f, 0xdc, 0x9e, 0xd1, 0xca, 0x97, 0x91, 0x6d, 0x12, 0x92, 0x6d, 0x90, 0x91, 0xcc,
|
||||
0x9c, 0x2a, 0xa5, 0x2b, 0xc9, 0xb4, 0x6a, 0x4d, 0x0e, 0x91, 0x07, 0x50, 0x74, 0xfd, 0x93, 0xc0,
|
||||
0x94, 0xff, 0xbd, 0x72, 0x09, 0x11, 0xd5, 0xc0, 0x11, 0x62, 0x53, 0x42, 0x59, 0x77, 0x98, 0xbf,
|
||||
0x0f, 0x25, 0x4a, 0xab, 0x99, 0x44, 0xc6, 0x5c, 0x15, 0x3b, 0x9a, 0x82, 0xdf, 0x8c, 0xb3, 0x34,
|
||||
0x34, 0x5f, 0x08, 0xa7, 0xe6, 0x46, 0x35, 0x9e, 0xb2, 0xe6, 0x7f, 0xca, 0x41, 0x59, 0x8f, 0x90,
|
||||
0xbf, 0x0a, 0xcb, 0xd2, 0xc7, 0xcd, 0x1e, 0x9f, 0x1b, 0x66, 0x97, 0x4f, 0x40, 0xd1, 0x8a, 0x35,
|
||||
0x10, 0x79, 0x3c, 0xea, 0x99, 0x08, 0x40, 0x16, 0xc4, 0xdf, 0x83, 0x5b, 0xba, 0x79, 0x14, 0xca,
|
||||
0x50, 0x7a, 0x52, 0x44, 0x72, 0xb3, 0x2f, 0x7c, 0x5f, 0x7a, 0xc6, 0x8a, 0xb8, 0xec, 0x35, 0xb7,
|
||||
0x60, 0x51, 0xbf, 0x6a, 0x0f, 0x45, 0x57, 0x46, 0x26, 0xeb, 0x34, 0x06, 0xe3, 0xdf, 0x84, 0x12,
|
||||
0x15, 0x61, 0x36, 0x9c, 0xab, 0x85, 0x4f, 0x63, 0x35, 0x83, 0xe4, 0x98, 0x5b, 0x07, 0xd0, 0xab,
|
||||
0x81, 0x5e, 0x98, 0xd1, 0x4e, 0x5f, 0xbe, 0x72, 0xf9, 0xc8, 0xe5, 0xcb, 0x10, 0x61, 0xff, 0x1c,
|
||||
0xe9, 0x49, 0xaa, 0x96, 0xc3, 0x63, 0x38, 0x4f, 0xf1, 0xfd, 0x31, 0x58, 0xf3, 0xb7, 0x8b, 0x50,
|
||||
0xc4, 0x85, 0x44, 0xe4, 0x7e, 0x30, 0x90, 0x49, 0x94, 0x53, 0x0b, 0xed, 0x18, 0x0c, 0xed, 0x28,
|
||||
0xa1, 0x13, 0xcd, 0x09, 0x9a, 0x56, 0x6e, 0x93, 0x60, 0xc4, 0x1c, 0x86, 0xc1, 0x89, 0xeb, 0xa5,
|
||||
0x98, 0xc6, 0xe2, 0x9a, 0x00, 0xf3, 0x77, 0xe0, 0xe6, 0x40, 0x84, 0xa7, 0x52, 0x91, 0x3e, 0x7a,
|
||||
0x1a, 0x84, 0xa7, 0x11, 0xce, 0x5c, 0xcb, 0x31, 0xe1, 0xb1, 0x4b, 0xde, 0xa2, 0x82, 0x77, 0xe4,
|
||||
0x99, 0x4b, 0x98, 0x55, 0x5d, 0x5c, 0x19, 0xb7, 0x51, 0x38, 0x84, 0x9e, 0x9a, 0xb6, 0xe1, 0x65,
|
||||
0x32, 0x17, 0xe3, 0x50, 0x34, 0xd6, 0x74, 0xd1, 0x49, 0xd4, 0x72, 0x28, 0x62, 0x57, 0xb3, 0x53,
|
||||
0x00, 0x8a, 0x0e, 0x7d, 0xec, 0x89, 0x56, 0xe3, 0x4b, 0xda, 0xcb, 0xcc, 0x80, 0x10, 0x43, 0xc9,
|
||||
0x6e, 0x3f, 0xfe, 0x88, 0x0e, 0xa7, 0x65, 0x41, 0xfc, 0x0e, 0x40, 0x4f, 0x28, 0xf9, 0x5c, 0x5c,
|
||||
0x3c, 0x0e, 0xbd, 0x86, 0xd4, 0xb1, 0xf4, 0x14, 0x82, 0x7e, 0xaa, 0x17, 0x74, 0x85, 0xd7, 0x56,
|
||||
0x41, 0x28, 0x7a, 0xf2, 0x48, 0xa8, 0x7e, 0xa3, 0xa7, 0xfd, 0xd4, 0x49, 0x38, 0x8e, 0x58, 0xb9,
|
||||
0x03, 0xf9, 0x59, 0xe0, 0xcb, 0x46, 0x5f, 0x8f, 0x38, 0x6e, 0x63, 0x4f, 0x84, 0x2f, 0xbc, 0x0b,
|
||||
0xe5, 0x76, 0x71, 0x2c, 0xae, 0xee, 0x49, 0x06, 0x84, 0x63, 0xf5, 0xa5, 0x7a, 0x1e, 0x84, 0xa7,
|
||||
0x2d, 0xa7, 0xf1, 0xb9, 0x1e, 0x6b, 0x02, 0xb0, 0x0e, 0x01, 0x52, 0x21, 0xc2, 0xb3, 0x64, 0x9d,
|
||||
0xe2, 0xfd, 0x6c, 0x01, 0x9d, 0x83, 0x23, 0xe9, 0x3b, 0xae, 0xdf, 0xdb, 0x32, 0x72, 0xc3, 0x72,
|
||||
0x08, 0xa4, 0x10, 0x80, 0x74, 0x12, 0x20, 0x19, 0x22, 0xd4, 0x92, 0x0e, 0x2b, 0x58, 0xff, 0x2b,
|
||||
0x07, 0xf5, 0x4c, 0x56, 0xfc, 0xcf, 0x30, 0x93, 0x8f, 0x27, 0x3b, 0xea, 0x0b, 0x9c, 0x50, 0x2d,
|
||||
0x53, 0x49, 0x1b, 0xa7, 0xdb, 0x24, 0xed, 0xf1, 0xad, 0x76, 0xf8, 0x33, 0x90, 0x2f, 0x94, 0xc5,
|
||||
0xb7, 0x1e, 0x9a, 0xa8, 0x49, 0x1d, 0x2a, 0x8f, 0xfd, 0x53, 0x3f, 0x78, 0xee, 0xeb, 0x23, 0x9b,
|
||||
0x4a, 0x33, 0xc6, 0x92, 0x4c, 0x71, 0xf5, 0x44, 0xc1, 0xfa, 0xc7, 0xc5, 0x89, 0x2a, 0xa6, 0x6d,
|
||||
0x28, 0x6b, 0x37, 0x80, 0x2c, 0xd4, 0xe9, 0xb2, 0x93, 0x2c, 0xb2, 0x49, 0x68, 0x64, 0x40, 0xb6,
|
||||
0x21, 0x46, 0xfb, 0x3c, 0x29, 0xd5, 0xcb, 0xcf, 0x4c, 0xbc, 0x8c, 0x31, 0x8a, 0xd5, 0xe0, 0x58,
|
||||
0xb5, 0x6a, 0xc2, 0xa1, 0xf9, 0xd7, 0x72, 0x70, 0x7d, 0x16, 0x4a, 0xb6, 0xa6, 0x37, 0x37, 0x5e,
|
||||
0xd3, 0xdb, 0x9e, 0xa8, 0x91, 0xcd, 0xd3, 0x68, 0x1e, 0xbc, 0x64, 0x27, 0xc6, 0x2b, 0x66, 0xad,
|
||||
0x9f, 0xe4, 0x60, 0x75, 0x6a, 0xcc, 0x19, 0x23, 0x07, 0xa0, 0xac, 0x25, 0x4b, 0xd7, 0xbe, 0x24,
|
||||
0xd5, 0x08, 0x3a, 0x9a, 0x4c, 0x67, 0x4a, 0xa4, 0xd3, 0xbb, 0xa6, 0x2a, 0x58, 0x9b, 0xbf, 0xb8,
|
||||
0x6a, 0xa8, 0xab, 0x7b, 0x92, 0x95, 0xd0, 0x3a, 0xd1, 0x76, 0x97, 0x81, 0x94, 0xb5, 0x89, 0xaa,
|
||||
0x43, 0xde, 0xac, 0x42, 0x35, 0x35, 0xa3, 0xa1, 0xe7, 0x76, 0xb1, 0x59, 0xe5, 0x4d, 0xb8, 0xa9,
|
||||
0x4b, 0xc3, 0x8d, 0x3b, 0x78, 0xd2, 0xe9, 0xbb, 0xb4, 0x39, 0x58, 0xcd, 0xb2, 0xe1, 0xda, 0x8c,
|
||||
0x31, 0x51, 0x2f, 0x9f, 0x98, 0x1e, 0x2f, 0x03, 0x6c, 0x3d, 0x89, 0xfb, 0xc9, 0x72, 0x9c, 0xc3,
|
||||
0xf2, 0xd6, 0x93, 0x2c, 0x43, 0xb3, 0x5f, 0x9e, 0xa0, 0x26, 0x89, 0x58, 0xc1, 0xfa, 0x9d, 0x5c,
|
||||
0x9c, 0xe7, 0x6e, 0xfe, 0x05, 0x58, 0xd2, 0x7d, 0x3c, 0x12, 0x17, 0x5e, 0x20, 0x1c, 0xbe, 0x0d,
|
||||
0xcb, 0x51, 0x72, 0x5f, 0x21, 0x73, 0x1c, 0x4c, 0x9e, 0xe6, 0xed, 0x31, 0x24, 0x7b, 0x82, 0x28,
|
||||
0xf6, 0x6a, 0xf2, 0x69, 0x70, 0x9c, 0x93, 0x7f, 0x26, 0x68, 0x97, 0x2d, 0x92, 0xc7, 0x25, 0xac,
|
||||
0x6f, 0xc2, 0x2a, 0x29, 0x2f, 0xdd, 0x19, 0x6d, 0x31, 0xa3, 0x3c, 0x68, 0xbd, 0xbb, 0x15, 0xcb,
|
||||
0x83, 0x69, 0x5a, 0x7f, 0x58, 0x06, 0x48, 0x13, 0x01, 0x33, 0xb6, 0xf9, 0x2c, 0x23, 0x68, 0x2a,
|
||||
0x2d, 0x57, 0x78, 0xe9, 0xb4, 0xdc, 0x7b, 0x89, 0xe1, 0xae, 0x23, 0xb2, 0x93, 0x35, 0xba, 0x69,
|
||||
0x9f, 0x26, 0xcd, 0xf5, 0xb1, 0xb2, 0x8f, 0xd2, 0x64, 0xd9, 0xc7, 0xdd, 0xe9, 0x1a, 0xb1, 0x09,
|
||||
0xfd, 0x93, 0x06, 0x19, 0x2a, 0x63, 0x41, 0x86, 0x26, 0x54, 0x43, 0x29, 0x9c, 0xc0, 0xf7, 0x2e,
|
||||
0xe2, 0xec, 0x4f, 0xdc, 0xe6, 0x6f, 0x42, 0x49, 0xd1, 0x95, 0x8b, 0x2a, 0x6d, 0x97, 0x17, 0x2c,
|
||||
0x9c, 0xc6, 0x45, 0x65, 0xe6, 0x46, 0xa6, 0xb0, 0x4b, 0x9f, 0x60, 0x55, 0x3b, 0x03, 0xe1, 0x6b,
|
||||
0xc0, 0x5d, 0xf4, 0xb8, 0x3c, 0x4f, 0x3a, 0x1b, 0x17, 0x5b, 0x3a, 0x29, 0x43, 0xa7, 0x66, 0xd5,
|
||||
0x9e, 0xf1, 0x26, 0x5e, 0xff, 0xc5, 0x74, 0xfd, 0xa9, 0xcb, 0x67, 0x6e, 0x84, 0x23, 0x5d, 0x22,
|
||||
0xe3, 0x20, 0x69, 0xe3, 0xb9, 0x1c, 0xef, 0x51, 0x3d, 0x97, 0x24, 0xbd, 0x69, 0x66, 0xf3, 0x92,
|
||||
0xb7, 0xd6, 0xdf, 0xcb, 0x27, 0x0e, 0x4e, 0x0d, 0x4a, 0xc7, 0x22, 0x72, 0xbb, 0xda, 0x79, 0x35,
|
||||
0x07, 0xbf, 0x76, 0x72, 0x54, 0xe0, 0x04, 0x2c, 0x8f, 0xde, 0x4b, 0x24, 0xd1, 0x4f, 0x59, 0x06,
|
||||
0x48, 0xaf, 0xa1, 0xb0, 0x22, 0xee, 0xcd, 0x78, 0xbd, 0x75, 0x7d, 0x06, 0x91, 0x52, 0xbc, 0xcb,
|
||||
0x49, 0x2a, 0xdf, 0xc8, 0x73, 0x25, 0xdd, 0xcf, 0xaa, 0x88, 0xe3, 0x07, 0x4a, 0xea, 0x68, 0x1f,
|
||||
0x49, 0x27, 0x03, 0x64, 0x13, 0xd7, 0x55, 0xb3, 0x3a, 0xba, 0x13, 0x31, 0x53, 0x1d, 0xa2, 0x8b,
|
||||
0xc8, 0xb5, 0x5a, 0xc4, 0xdd, 0x39, 0xfe, 0x82, 0x2d, 0x61, 0x8f, 0xd2, 0xdb, 0x2d, 0x6c, 0x19,
|
||||
0xb9, 0x0a, 0xaa, 0x1a, 0x58, 0xc1, 0xc7, 0x33, 0xaa, 0x25, 0x60, 0xf8, 0x55, 0x07, 0x15, 0xc6,
|
||||
0x2a, 0xf6, 0x2c, 0x31, 0x0d, 0x18, 0x47, 0x6f, 0x69, 0x28, 0xd0, 0x75, 0x71, 0x87, 0xc2, 0x57,
|
||||
0xec, 0x1a, 0x0e, 0x75, 0xe8, 0x9c, 0xb0, 0xeb, 0xd6, 0x8f, 0xd2, 0xba, 0xd2, 0xd7, 0x13, 0x87,
|
||||
0x61, 0x1e, 0x01, 0xbe, 0xcc, 0xa5, 0xd8, 0x86, 0xd5, 0x50, 0x7e, 0x7f, 0xe4, 0x8e, 0x15, 0x4d,
|
||||
0x17, 0xae, 0x4e, 0xf7, 0x4f, 0x53, 0x58, 0x67, 0xb0, 0x1a, 0x37, 0x9e, 0xba, 0xaa, 0x4f, 0x61,
|
||||
0x17, 0xfe, 0x66, 0xa6, 0xaa, 0x3b, 0x37, 0xf3, 0x36, 0x4c, 0xc2, 0x32, 0xad, 0xe2, 0x4e, 0x42,
|
||||
0xdf, 0xf9, 0x39, 0x42, 0xdf, 0xd6, 0xff, 0x2c, 0x67, 0x22, 0x2f, 0xda, 0x85, 0x72, 0x12, 0x17,
|
||||
0x6a, 0x3a, 0xa1, 0x97, 0x46, 0xb3, 0xf3, 0x2f, 0x13, 0xcd, 0x9e, 0x95, 0x1c, 0xff, 0x00, 0xed,
|
||||
0x63, 0xda, 0x1b, 0x4f, 0xe6, 0x88, 0xd4, 0x8f, 0xe1, 0xf2, 0x0d, 0x4a, 0xcf, 0x89, 0xb6, 0xae,
|
||||
0xdc, 0x28, 0xcd, 0xbc, 0x63, 0x91, 0xcd, 0xc3, 0x19, 0x4c, 0x3b, 0x43, 0x95, 0xd1, 0x24, 0xe5,
|
||||
0x59, 0x9a, 0x04, 0xbd, 0x59, 0xa3, 0x63, 0x92, 0xb6, 0x4e, 0x6c, 0xe8, 0xe7, 0x98, 0x3d, 0xa5,
|
||||
0x65, 0xab, 0xf6, 0x14, 0x1c, 0x2d, 0xac, 0xc1, 0xc8, 0x53, 0xae, 0x89, 0xdd, 0xeb, 0xc6, 0xe4,
|
||||
0x25, 0xb0, 0xda, 0xf4, 0x25, 0xb0, 0x8f, 0x00, 0x22, 0x89, 0x92, 0xbf, 0xe5, 0x76, 0x95, 0xa9,
|
||||
0xef, 0xb8, 0x73, 0xd9, 0xd8, 0x4c, 0xc6, 0x21, 0x43, 0x81, 0xfd, 0x1f, 0x88, 0xf3, 0x4d, 0xb4,
|
||||
0xb4, 0x4d, 0x22, 0x3a, 0x69, 0x4f, 0xea, 0xd7, 0xe5, 0x69, 0xfd, 0xfa, 0x26, 0x94, 0xa2, 0x6e,
|
||||
0x30, 0x94, 0x74, 0x8f, 0xe1, 0xf2, 0xf5, 0x5d, 0x6b, 0x23, 0x92, 0xad, 0x71, 0x29, 0xbe, 0x87,
|
||||
0x1a, 0x28, 0x08, 0xe9, 0x06, 0x43, 0xcd, 0x8e, 0x9b, 0x63, 0x3a, 0xee, 0xe6, 0xb8, 0x8e, 0x6b,
|
||||
0x3a, 0x50, 0x36, 0xb1, 0xfa, 0x19, 0xae, 0x3b, 0x45, 0xf9, 0xf2, 0x99, 0x28, 0x5f, 0x52, 0x45,
|
||||
0x58, 0xc8, 0x56, 0x11, 0x4e, 0x5c, 0x72, 0x2a, 0x4d, 0x5d, 0x72, 0xb2, 0x3e, 0x83, 0x12, 0xf5,
|
||||
0x15, 0x0d, 0x04, 0x3d, 0xcd, 0xda, 0x7e, 0xc4, 0x41, 0xb1, 0x1c, 0xbf, 0x0e, 0x2c, 0x92, 0x64,
|
||||
0x60, 0xc8, 0xb6, 0x18, 0x48, 0x52, 0x80, 0x79, 0xde, 0x80, 0xeb, 0x1a, 0x37, 0x1a, 0x7f, 0x43,
|
||||
0x56, 0x8e, 0xe7, 0x1e, 0x87, 0x22, 0xbc, 0x60, 0x45, 0xeb, 0x23, 0x4a, 0xe5, 0xc6, 0x02, 0x55,
|
||||
0x4f, 0x2e, 0x95, 0x69, 0x95, 0xeb, 0xc8, 0x10, 0x4f, 0x0a, 0x9d, 0x81, 0x37, 0xbe, 0x8f, 0xae,
|
||||
0x4b, 0x22, 0xe7, 0x82, 0x22, 0x34, 0x8b, 0xd9, 0x53, 0xf6, 0xcf, 0x6c, 0xbf, 0x59, 0x1b, 0x19,
|
||||
0x33, 0x6d, 0xbc, 0x10, 0x29, 0x37, 0x6f, 0x21, 0x92, 0xf5, 0x29, 0xac, 0xd8, 0xe3, 0xfa, 0x9a,
|
||||
0xbf, 0x07, 0x95, 0x60, 0x98, 0xe5, 0xf3, 0x22, 0xb9, 0x8c, 0xd1, 0xad, 0x9f, 0xe6, 0x60, 0xb1,
|
||||
0xe5, 0x2b, 0x19, 0xfa, 0xc2, 0xdb, 0xf1, 0x44, 0x8f, 0xbf, 0x1b, 0x6b, 0xa9, 0xd9, 0xbe, 0x75,
|
||||
0x16, 0x77, 0x5c, 0x61, 0x79, 0x26, 0x26, 0xcd, 0x6f, 0xc0, 0xaa, 0x74, 0x5c, 0x15, 0x84, 0xda,
|
||||
0x38, 0x8d, 0xeb, 0xc1, 0xae, 0x03, 0xd3, 0xe0, 0x36, 0x6d, 0x89, 0x8e, 0x5e, 0xe6, 0x06, 0x5c,
|
||||
0x1f, 0x83, 0xc6, 0x96, 0x67, 0x9e, 0xdf, 0x86, 0x46, 0x7a, 0xd2, 0x6c, 0x05, 0xbe, 0x6a, 0xf9,
|
||||
0x8e, 0x3c, 0x27, 0x33, 0x87, 0x15, 0xac, 0xdf, 0xab, 0xc4, 0x06, 0xd6, 0x13, 0x53, 0x2d, 0x16,
|
||||
0x06, 0x41, 0x7a, 0xa3, 0xd0, 0xb4, 0x32, 0x37, 0x57, 0xf3, 0x73, 0xdc, 0x5c, 0xfd, 0x28, 0xbd,
|
||||
0x7d, 0xa8, 0x0f, 0x8a, 0xaf, 0xcc, 0x3c, 0x7d, 0xa8, 0xc8, 0xc5, 0x98, 0xd4, 0x6d, 0x99, 0xb9,
|
||||
0x8a, 0xf8, 0x86, 0xf1, 0xa3, 0x8a, 0xf3, 0xd8, 0xa1, 0x3a, 0xf9, 0xfe, 0xf6, 0x64, 0xad, 0xfc,
|
||||
0x7c, 0xc5, 0x68, 0x53, 0xa6, 0x22, 0xbc, 0xb4, 0xa9, 0xf8, 0xf1, 0x84, 0xcb, 0x52, 0x9d, 0x19,
|
||||
0xd5, 0xba, 0xe2, 0x42, 0xdf, 0xc7, 0x50, 0xe9, 0xbb, 0x91, 0x0a, 0x42, 0x7d, 0xc9, 0x74, 0xfa,
|
||||
0x52, 0x4c, 0x66, 0xb6, 0x76, 0x35, 0x22, 0x55, 0x06, 0xc5, 0x54, 0xfc, 0xbb, 0xb0, 0x4a, 0x13,
|
||||
0x7f, 0x94, 0x5a, 0x04, 0x51, 0xa3, 0x3e, 0xb3, 0x22, 0x2b, 0xc3, 0x6a, 0x63, 0x82, 0xc4, 0x9e,
|
||||
0x66, 0xd2, 0xec, 0x01, 0xa4, 0xeb, 0x33, 0xa5, 0xc5, 0xbe, 0xc0, 0x25, 0xd3, 0x9b, 0x50, 0x8e,
|
||||
0x46, 0xc7, 0x69, 0xf2, 0xca, 0xb4, 0x9a, 0xe7, 0xd0, 0x9c, 0xb2, 0x0e, 0x8e, 0x64, 0xa8, 0xbb,
|
||||
0x7b, 0xe5, 0x4d, 0xd7, 0x8f, 0xb2, 0x0b, 0xaf, 0x85, 0xf3, 0xee, 0x25, 0xab, 0x97, 0x70, 0xce,
|
||||
0x48, 0x40, 0xf3, 0x6d, 0xa8, 0x67, 0x26, 0x15, 0x35, 0xf3, 0xc8, 0x77, 0x82, 0x38, 0x28, 0x8b,
|
||||
0xcf, 0xfa, 0x8a, 0x90, 0x13, 0x87, 0x65, 0xe9, 0xb9, 0x69, 0x03, 0x9b, 0x9c, 0xc0, 0x2b, 0xdc,
|
||||
0xda, 0xaf, 0xc0, 0x52, 0xc6, 0x5c, 0x4b, 0xc2, 0x5f, 0xe3, 0x40, 0xeb, 0x0c, 0x5e, 0xc9, 0xb0,
|
||||
0x3b, 0x92, 0xe1, 0xc0, 0x8d, 0xf0, 0x20, 0xd1, 0xee, 0x1a, 0x45, 0x26, 0x1c, 0xe9, 0x2b, 0x57,
|
||||
0xc5, 0x1a, 0x34, 0x69, 0xf3, 0x5f, 0x87, 0xd2, 0x50, 0x86, 0x83, 0xc8, 0x68, 0xd1, 0x49, 0x09,
|
||||
0x9a, 0xc9, 0x36, 0xb2, 0x35, 0x8d, 0xf5, 0x0f, 0x73, 0x50, 0xdd, 0x97, 0x4a, 0xa0, 0xed, 0xc0,
|
||||
0xf7, 0x27, 0xbe, 0x32, 0x9d, 0x70, 0x8d, 0x51, 0xd7, 0x8c, 0x03, 0xb9, 0xd6, 0x32, 0xf8, 0xa6,
|
||||
0xbd, 0xbb, 0x90, 0x76, 0xac, 0xb9, 0x01, 0x15, 0x03, 0x6e, 0xbe, 0x0b, 0x2b, 0x13, 0x98, 0x34,
|
||||
0x2f, 0xda, 0x6e, 0x6f, 0x5f, 0x0c, 0xe2, 0xda, 0x9c, 0x45, 0x7b, 0x1c, 0xb8, 0x51, 0x83, 0xca,
|
||||
0x50, 0x13, 0x58, 0xff, 0xfa, 0x06, 0xd5, 0x8b, 0xb8, 0x27, 0xe8, 0x48, 0xcf, 0x3a, 0x59, 0xef,
|
||||
0x00, 0xd0, 0xd1, 0xac, 0xab, 0x0a, 0x74, 0x48, 0x32, 0x03, 0xe1, 0x1f, 0x24, 0x21, 0xeb, 0xe2,
|
||||
0x4c, 0xa3, 0x2a, 0xcb, 0x7c, 0x32, 0x6e, 0xdd, 0x80, 0x8a, 0x1b, 0xed, 0xe1, 0xd1, 0x66, 0x6a,
|
||||
0x6d, 0xe2, 0x26, 0xff, 0x16, 0x94, 0xdd, 0xc1, 0x30, 0x08, 0x95, 0x89, 0x69, 0x5f, 0xc9, 0xb5,
|
||||
0x45, 0x98, 0xbb, 0x0b, 0xb6, 0xa1, 0x41, 0x6a, 0x79, 0x4e, 0xd4, 0xd5, 0x17, 0x53, 0x6f, 0x9f,
|
||||
0xc7, 0xd4, 0x9a, 0x86, 0x7f, 0x07, 0x96, 0x7a, 0xba, 0xfa, 0x4d, 0x33, 0x36, 0x4a, 0xe4, 0xeb,
|
||||
0x57, 0x31, 0x79, 0x94, 0x25, 0xd8, 0x5d, 0xb0, 0xc7, 0x39, 0x20, 0x4b, 0x34, 0xe0, 0x65, 0xa4,
|
||||
0x3a, 0xc1, 0x27, 0x81, 0xeb, 0x93, 0xc3, 0xf9, 0x02, 0x96, 0x76, 0x96, 0x00, 0x59, 0x8e, 0x71,
|
||||
0xe0, 0xef, 0xa0, 0xc5, 0x13, 0x29, 0x73, 0xcf, 0xf7, 0xee, 0x55, 0x9c, 0x3a, 0x32, 0x32, 0x37,
|
||||
0x74, 0x23, 0xc5, 0xcf, 0xa1, 0x99, 0xd9, 0x24, 0xe6, 0x23, 0xeb, 0xc3, 0x61, 0x18, 0xa0, 0xd7,
|
||||
0xba, 0x44, 0xdc, 0xde, 0xb9, 0x8a, 0xdb, 0xd1, 0xa5, 0xd4, 0xbb, 0x0b, 0xf6, 0x15, 0xbc, 0x79,
|
||||
0x07, 0xbd, 0x36, 0x33, 0x84, 0x3d, 0x29, 0xce, 0xe2, 0x5b, 0xc2, 0xf7, 0xe7, 0x9a, 0x05, 0xa2,
|
||||
0xd8, 0x5d, 0xb0, 0x27, 0x78, 0xf0, 0xdf, 0x84, 0xd5, 0xb1, 0x6f, 0xd2, 0x8d, 0x42, 0x7d, 0x87,
|
||||
0xf8, 0x9b, 0x73, 0x0f, 0x03, 0x89, 0x76, 0x17, 0xec, 0x69, 0x4e, 0x7c, 0x04, 0x5f, 0x9a, 0x1e,
|
||||
0xd2, 0x96, 0xec, 0x7a, 0xae, 0x2f, 0xcd, 0x75, 0xe3, 0xb7, 0x5f, 0x6e, 0xb6, 0x0c, 0xf1, 0xee,
|
||||
0x82, 0x7d, 0x39, 0x67, 0xfe, 0x97, 0xe0, 0xf6, 0x70, 0xa6, 0x8a, 0xd1, 0xaa, 0xcb, 0xdc, 0x56,
|
||||
0x7e, 0x6f, 0xce, 0x2f, 0x4f, 0xd1, 0xef, 0x2e, 0xd8, 0x57, 0xf2, 0x47, 0xdb, 0x99, 0xbc, 0x63,
|
||||
0x53, 0xa4, 0xab, 0x1b, 0xfc, 0x36, 0xd4, 0x44, 0xd7, 0xdb, 0x95, 0xc2, 0x49, 0xa2, 0xe7, 0x29,
|
||||
0xa0, 0xf9, 0x5f, 0x73, 0x50, 0x36, 0xf2, 0x7e, 0x3b, 0x49, 0xb0, 0x27, 0xaa, 0x3b, 0x05, 0xf0,
|
||||
0x0f, 0xa1, 0x26, 0xc3, 0x30, 0x08, 0x37, 0x03, 0x27, 0xae, 0x10, 0x9c, 0x0c, 0xed, 0x6a, 0x3e,
|
||||
0x6b, 0xdb, 0x31, 0x9a, 0x9d, 0x52, 0xf0, 0x0f, 0x00, 0xf4, 0x3e, 0xef, 0xa4, 0x77, 0x2d, 0x9a,
|
||||
0xb3, 0xe9, 0x75, 0x8a, 0x25, 0xc5, 0x4e, 0x03, 0x63, 0x71, 0x7e, 0x23, 0x6e, 0x26, 0x0e, 0x67,
|
||||
0x29, 0xe3, 0x70, 0xde, 0x36, 0x31, 0x82, 0x03, 0x7c, 0x61, 0x6e, 0x1c, 0x25, 0x80, 0xe6, 0xbf,
|
||||
0xca, 0x41, 0x59, 0x2b, 0x0f, 0xbe, 0x3d, 0x3d, 0xa2, 0xaf, 0xbd, 0x58, 0xe7, 0xac, 0x4d, 0x8e,
|
||||
0xec, 0x5b, 0x00, 0x5a, 0x07, 0x65, 0x46, 0x76, 0x7b, 0x82, 0x8f, 0x21, 0x8d, 0xcb, 0x44, 0x53,
|
||||
0x7c, 0xeb, 0xa1, 0xbe, 0x15, 0x43, 0x71, 0xd8, 0xc7, 0x7b, 0x7b, 0x6c, 0x81, 0xaf, 0xc2, 0xd2,
|
||||
0xe3, 0x83, 0x4f, 0x0f, 0x0e, 0x9f, 0x1e, 0x3c, 0xdb, 0xb6, 0xed, 0x43, 0x5b, 0x87, 0x63, 0x37,
|
||||
0xd6, 0xb7, 0x9e, 0xb5, 0x0e, 0x8e, 0x1e, 0x77, 0x58, 0xbe, 0xf9, 0xb3, 0x1c, 0x2c, 0x8d, 0xe9,
|
||||
0xae, 0xff, 0xbb, 0x4b, 0x97, 0x99, 0xfe, 0xc2, 0xec, 0xe9, 0x2f, 0x5e, 0x36, 0xfd, 0xa5, 0xc9,
|
||||
0xe9, 0xff, 0x47, 0x39, 0x58, 0x1a, 0xd3, 0x91, 0x59, 0xee, 0xb9, 0x71, 0xee, 0xd9, 0x93, 0x3e,
|
||||
0x3f, 0x71, 0xd2, 0x5b, 0xb0, 0x18, 0x3f, 0x1f, 0xa4, 0x11, 0x87, 0x31, 0x58, 0x16, 0x87, 0xca,
|
||||
0xd2, 0x8b, 0xe3, 0x38, 0x54, 0x9a, 0x7e, 0x75, 0x6f, 0xe9, 0x1a, 0x5e, 0x44, 0xb7, 0x94, 0x9b,
|
||||
0x97, 0x6b, 0xd0, 0x2b, 0x86, 0xf0, 0x08, 0xea, 0xc3, 0x74, 0x9b, 0xbe, 0x9c, 0x59, 0x92, 0xa5,
|
||||
0x7c, 0x41, 0x3f, 0x7f, 0x92, 0x83, 0xe5, 0x71, 0x9d, 0xfb, 0xff, 0xf5, 0xb4, 0xfe, 0x61, 0x0e,
|
||||
0x56, 0xa7, 0x34, 0xf9, 0x95, 0x86, 0xdd, 0x64, 0xbf, 0xf2, 0x73, 0xf4, 0xab, 0x30, 0xa3, 0x5f,
|
||||
0x97, 0x6b, 0x92, 0xab, 0x7b, 0xdc, 0x86, 0x2f, 0x5d, 0x7a, 0x26, 0x5c, 0x31, 0xd5, 0x63, 0x4c,
|
||||
0x0b, 0x93, 0x4c, 0xff, 0x7e, 0x0e, 0x6e, 0x5f, 0xa5, 0xef, 0xff, 0x9f, 0xcb, 0xd5, 0x64, 0x0f,
|
||||
0xad, 0x77, 0x93, 0x44, 0x79, 0x1d, 0x2a, 0xe6, 0x4f, 0x7c, 0x4c, 0x6d, 0x72, 0x3f, 0x78, 0xee,
|
||||
0xeb, 0x28, 0xb3, 0x2d, 0x85, 0xb9, 0x1f, 0x6d, 0xcb, 0xa1, 0xe7, 0x52, 0x62, 0xf2, 0x16, 0xc0,
|
||||
0x3a, 0xf9, 0x75, 0xf1, 0x75, 0x85, 0xcd, 0xbd, 0xc3, 0xf6, 0x36, 0x5b, 0xc8, 0x1a, 0xb1, 0x9f,
|
||||
0xc5, 0x8a, 0xd8, 0x3a, 0x82, 0x72, 0x5a, 0xc8, 0xbe, 0x2f, 0xc2, 0x53, 0x47, 0xa7, 0xff, 0x16,
|
||||
0xa1, 0x7a, 0x64, 0x5c, 0x28, 0xfd, 0xa9, 0x4f, 0xda, 0x87, 0x07, 0x3a, 0xa0, 0xbd, 0x75, 0xd8,
|
||||
0xd1, 0xe5, 0xf0, 0xed, 0x27, 0x8f, 0x74, 0x1e, 0xea, 0x91, 0xbd, 0x7e, 0xb4, 0xfb, 0x8c, 0x30,
|
||||
0x4a, 0xd6, 0xcf, 0xf2, 0xf1, 0xa9, 0x66, 0xd9, 0x26, 0xb1, 0x08, 0x50, 0x46, 0x6d, 0x1e, 0x18,
|
||||
0xc6, 0xc9, 0x67, 0xa8, 0x42, 0x76, 0xfb, 0x5c, 0xc7, 0x21, 0x58, 0x9e, 0x97, 0x21, 0x7f, 0x74,
|
||||
0xac, 0x8b, 0x63, 0x77, 0xd5, 0xc0, 0xd3, 0xf7, 0xd7, 0x3a, 0xe7, 0x8a, 0x95, 0xf0, 0x61, 0x33,
|
||||
0x3a, 0x63, 0x65, 0xeb, 0x3f, 0xe6, 0xa0, 0x96, 0xa8, 0xca, 0x97, 0x51, 0xdd, 0x9c, 0xc3, 0x72,
|
||||
0xeb, 0xa0, 0xb3, 0x6d, 0x1f, 0xac, 0xef, 0x19, 0x94, 0x02, 0x6f, 0xc0, 0xf5, 0x83, 0xc3, 0x67,
|
||||
0x87, 0x1b, 0x9f, 0x6c, 0x6f, 0x76, 0xda, 0xcf, 0x3a, 0x87, 0xcf, 0x5a, 0xfb, 0x47, 0x87, 0x76,
|
||||
0x87, 0x95, 0xf8, 0x4d, 0xe0, 0xfa, 0xf9, 0x59, 0xab, 0xfd, 0x6c, 0x73, 0xfd, 0x60, 0x73, 0x7b,
|
||||
0x6f, 0x7b, 0x8b, 0x95, 0xf9, 0xd7, 0xe0, 0xd7, 0xf6, 0x5a, 0xfb, 0xad, 0xce, 0xb3, 0xc3, 0x9d,
|
||||
0x67, 0xf6, 0xe1, 0xd3, 0xf6, 0xb3, 0x43, 0xfb, 0x99, 0xbd, 0xbd, 0xb7, 0xde, 0x69, 0x1d, 0x1e,
|
||||
0xb4, 0x9f, 0x6d, 0x7f, 0x77, 0x73, 0x7b, 0x7b, 0x6b, 0x7b, 0x8b, 0x55, 0xf8, 0x35, 0x58, 0xd9,
|
||||
0x69, 0xed, 0x6d, 0x3f, 0xdb, 0x3b, 0x5c, 0xdf, 0x32, 0xdf, 0xab, 0xf2, 0xdb, 0xd0, 0x68, 0x1d,
|
||||
0xb4, 0x1f, 0xef, 0xec, 0xb4, 0x36, 0x5b, 0xdb, 0x07, 0x9d, 0x67, 0x47, 0xdb, 0xf6, 0x7e, 0xab,
|
||||
0xdd, 0x46, 0x5a, 0x56, 0xb3, 0xbe, 0x0d, 0xe5, 0x96, 0x7f, 0xe6, 0x2a, 0x12, 0x3f, 0xb3, 0x56,
|
||||
0xc6, 0x21, 0x89, 0x9b, 0x24, 0x35, 0x6e, 0xcf, 0xa7, 0x6b, 0xcb, 0x24, 0x7c, 0x8b, 0x76, 0x0a,
|
||||
0xb0, 0xfe, 0x49, 0x1e, 0x96, 0x34, 0x8b, 0xd8, 0xc1, 0xb9, 0x07, 0x2b, 0x26, 0x52, 0xd8, 0x1a,
|
||||
0xdf, 0xe1, 0x93, 0x60, 0xfa, 0x5b, 0x1f, 0x0d, 0xca, 0xec, 0xf3, 0x2c, 0x88, 0x32, 0x4b, 0xc4,
|
||||
0x1c, 0x1d, 0x25, 0x9d, 0x53, 0x4b, 0x01, 0x5f, 0x74, 0x83, 0xa3, 0xf2, 0xd0, 0x88, 0xdd, 0xc0,
|
||||
0xdf, 0x4c, 0x2e, 0x0b, 0x8c, 0xc1, 0xf8, 0x67, 0x70, 0x2b, 0x69, 0x6f, 0xfb, 0xdd, 0xf0, 0x62,
|
||||
0x98, 0xfc, 0xfb, 0x56, 0x65, 0xa6, 0xc7, 0xbd, 0xe3, 0x7a, 0x72, 0x0c, 0xd1, 0xbe, 0x8c, 0x81,
|
||||
0xf5, 0xc7, 0xb9, 0x8c, 0x5b, 0xa8, 0xdd, 0xbe, 0x2b, 0x15, 0xe2, 0xac, 0x14, 0x05, 0x3a, 0x66,
|
||||
0xa6, 0xfb, 0xe6, 0x9c, 0x36, 0x4d, 0x7e, 0x04, 0xdc, 0x9d, 0xee, 0x74, 0x71, 0xce, 0x4e, 0xcf,
|
||||
0xa0, 0x9d, 0x8c, 0x30, 0x97, 0xa6, 0x23, 0xcc, 0x77, 0x00, 0x7a, 0x5e, 0x70, 0x2c, 0xbc, 0x8c,
|
||||
0x1d, 0x96, 0x81, 0x58, 0x1e, 0x54, 0xe3, 0xff, 0xf8, 0xe2, 0x37, 0xa1, 0x4c, 0xff, 0xf2, 0x95,
|
||||
0xc4, 0xdb, 0x74, 0x8b, 0xef, 0xc2, 0xb2, 0x1c, 0xef, 0x73, 0x7e, 0xce, 0x3e, 0x4f, 0xd0, 0x59,
|
||||
0xef, 0xc3, 0xea, 0x14, 0x12, 0x4e, 0xe2, 0x50, 0xa8, 0xe4, 0x86, 0x30, 0x3e, 0x4f, 0xe7, 0x6f,
|
||||
0xad, 0x7f, 0x9f, 0x87, 0xc5, 0x7d, 0xe1, 0xbb, 0x27, 0x32, 0x52, 0x71, 0x6f, 0xa3, 0x6e, 0x5f,
|
||||
0x0e, 0x44, 0xdc, 0x5b, 0xdd, 0x32, 0x4e, 0x78, 0x7e, 0xaa, 0x32, 0x2d, 0x9b, 0x0d, 0xb9, 0x09,
|
||||
0x65, 0x31, 0x52, 0xfd, 0xa4, 0x36, 0xda, 0xb4, 0x70, 0xed, 0x3c, 0xb7, 0x2b, 0xfd, 0x28, 0x96,
|
||||
0xcd, 0xb8, 0x99, 0x56, 0x70, 0x94, 0xaf, 0xa8, 0xe0, 0xa8, 0x4c, 0xcf, 0xff, 0x5d, 0xa8, 0x47,
|
||||
0xdd, 0x50, 0x4a, 0x3f, 0xea, 0x07, 0x2a, 0xfe, 0x7f, 0xb8, 0x2c, 0x88, 0x2a, 0x97, 0x82, 0xe7,
|
||||
0x3e, 0xee, 0xd0, 0x3d, 0xd7, 0x3f, 0x35, 0xe5, 0x3b, 0x63, 0x30, 0x94, 0x41, 0x0a, 0x41, 0xb8,
|
||||
0x3f, 0x90, 0xe4, 0xfe, 0x96, 0xec, 0xa4, 0x4d, 0x41, 0x06, 0xa1, 0x64, 0x2f, 0x08, 0x5d, 0xa9,
|
||||
0x23, 0x6d, 0x35, 0x3b, 0x03, 0x41, 0x5a, 0x4f, 0xf8, 0xbd, 0x91, 0xe8, 0x49, 0x93, 0x0f, 0x4d,
|
||||
0xda, 0xd6, 0x7f, 0x2b, 0x01, 0xec, 0xcb, 0xc1, 0xb1, 0x0c, 0xa3, 0xbe, 0x3b, 0xa4, 0x4c, 0x80,
|
||||
0x6b, 0x8a, 0x48, 0x97, 0x6c, 0x7a, 0xe6, 0xef, 0x8d, 0x15, 0x6b, 0x4f, 0xe7, 0xee, 0x52, 0xf2,
|
||||
0xc9, 0x08, 0x05, 0x4e, 0x8e, 0x50, 0xd2, 0x14, 0xcf, 0xd0, 0xfc, 0x17, 0xed, 0x2c, 0x88, 0xea,
|
||||
0x9a, 0x84, 0x92, 0xdb, 0xbe, 0xa3, 0x23, 0x20, 0x45, 0x3b, 0x69, 0xd3, 0x95, 0x8c, 0x68, 0x7d,
|
||||
0xa4, 0x02, 0x5b, 0xfa, 0xf2, 0x79, 0x72, 0x9f, 0x28, 0x05, 0xf1, 0x7d, 0x58, 0x1a, 0x8a, 0x8b,
|
||||
0x81, 0xf4, 0xd5, 0xbe, 0x54, 0xfd, 0xc0, 0x31, 0x95, 0x2e, 0x5f, 0xbb, 0xbc, 0x83, 0x47, 0x59,
|
||||
0x74, 0x7b, 0x9c, 0x1a, 0x65, 0xc2, 0x8f, 0x68, 0x97, 0xe8, 0x65, 0x34, 0x2d, 0xbe, 0x01, 0xa0,
|
||||
0x9f, 0xc8, 0xb1, 0xa8, 0xce, 0x0e, 0xd4, 0x88, 0x81, 0x8c, 0x64, 0x78, 0xe6, 0x6a, 0x3d, 0xa6,
|
||||
0x5d, 0xa7, 0x94, 0x0a, 0xb5, 0xde, 0x28, 0x92, 0xe1, 0xf6, 0x40, 0xb8, 0x9e, 0x59, 0xe0, 0x14,
|
||||
0xc0, 0xdf, 0x82, 0x1b, 0xd1, 0xe8, 0x18, 0x65, 0xe6, 0x58, 0x76, 0x82, 0x03, 0xf9, 0x3c, 0xf2,
|
||||
0xa4, 0x52, 0x32, 0x34, 0xa9, 0xf5, 0xd9, 0x2f, 0xad, 0x5e, 0x62, 0x15, 0xd0, 0x9f, 0x18, 0xe0,
|
||||
0x53, 0x5a, 0xb2, 0x93, 0x80, 0x4c, 0x3d, 0x13, 0xcb, 0x71, 0x06, 0x8b, 0x1a, 0x64, 0xca, 0x9d,
|
||||
0xf2, 0xfc, 0xab, 0xf0, 0xe5, 0x31, 0x24, 0x5b, 0xe7, 0x49, 0xa3, 0x1d, 0xd7, 0x17, 0x9e, 0xfb,
|
||||
0x03, 0x9d, 0x91, 0x2e, 0x58, 0x43, 0x58, 0x1a, 0x9b, 0x38, 0x3c, 0xe6, 0xf5, 0x93, 0x29, 0x00,
|
||||
0x61, 0xb0, 0xa8, 0xdb, 0x6d, 0x15, 0xba, 0x94, 0x00, 0x48, 0x20, 0x9b, 0xb8, 0xcf, 0x03, 0x96,
|
||||
0xe7, 0xd7, 0x81, 0x69, 0x48, 0xcb, 0x17, 0xc3, 0xe1, 0xfa, 0x70, 0xe8, 0x49, 0x56, 0xa0, 0x7b,
|
||||
0x77, 0x29, 0x54, 0x97, 0x6c, 0xb3, 0xa2, 0xf5, 0x5d, 0xb8, 0x45, 0x33, 0xf3, 0x44, 0x86, 0x89,
|
||||
0xdf, 0x67, 0xc6, 0x7a, 0x03, 0x56, 0xf5, 0xd3, 0x41, 0xa0, 0xf4, 0x6b, 0xb2, 0x85, 0x38, 0x2c,
|
||||
0x6b, 0x30, 0x9a, 0x02, 0x6d, 0xe9, 0x2b, 0x5d, 0x87, 0xa2, 0x61, 0x09, 0x5e, 0xde, 0xfa, 0x69,
|
||||
0x19, 0x78, 0x2a, 0x10, 0x1d, 0x57, 0x86, 0x5b, 0x42, 0x89, 0x4c, 0xe0, 0x6e, 0xe9, 0xd2, 0xd4,
|
||||
0xf3, 0x8b, 0xab, 0xb5, 0x6e, 0x42, 0xd9, 0x8d, 0xd0, 0x53, 0x31, 0xd5, 0x91, 0xa6, 0xc5, 0xf7,
|
||||
0x00, 0x86, 0x32, 0x74, 0x03, 0x87, 0x24, 0xa8, 0x34, 0xb3, 0x66, 0x7e, 0xba, 0x53, 0x6b, 0x47,
|
||||
0x09, 0x8d, 0x9d, 0xa1, 0xc7, 0x7e, 0xe8, 0x96, 0x4e, 0xe4, 0x96, 0xa9, 0xd3, 0x59, 0x10, 0x7f,
|
||||
0x1d, 0xae, 0x0d, 0x43, 0xb7, 0x2b, 0xf5, 0x72, 0x3c, 0x8e, 0x9c, 0x4d, 0xfa, 0x07, 0xaf, 0x0a,
|
||||
0x61, 0xce, 0x7a, 0x85, 0x12, 0x28, 0x7c, 0xb2, 0xdf, 0x23, 0x4a, 0x5d, 0x9a, 0x7b, 0x9f, 0xba,
|
||||
0xda, 0x70, 0xc9, 0x9e, 0xfd, 0x92, 0xdf, 0x07, 0x66, 0x5e, 0xec, 0xbb, 0xfe, 0x9e, 0xf4, 0x7b,
|
||||
0xaa, 0x4f, 0xc2, 0xbd, 0x64, 0x4f, 0xc1, 0x49, 0x83, 0xe9, 0x3f, 0x58, 0xd1, 0x69, 0x8d, 0x9a,
|
||||
0x9d, 0xb4, 0xf5, 0x5d, 0x62, 0x2f, 0x08, 0xdb, 0x2a, 0x34, 0x85, 0x90, 0x49, 0x1b, 0x6d, 0x96,
|
||||
0x88, 0xfa, 0x7a, 0x14, 0x06, 0xce, 0x88, 0x82, 0xee, 0x5a, 0x89, 0x4d, 0x82, 0x53, 0xcc, 0x7d,
|
||||
0xe1, 0x9b, 0x92, 0xb9, 0xa5, 0x2c, 0x66, 0x02, 0x26, 0x17, 0x25, 0x88, 0x52, 0x86, 0x2b, 0xc6,
|
||||
0x45, 0xc9, 0xc0, 0x0c, 0x4e, 0xca, 0x8a, 0x25, 0x38, 0x29, 0x1f, 0x1a, 0xbf, 0x13, 0x06, 0xae,
|
||||
0x93, 0xf2, 0x5a, 0xd5, 0x05, 0x8d, 0x93, 0xf0, 0x0c, 0x6e, 0xca, 0x93, 0x8f, 0xe1, 0x26, 0x70,
|
||||
0xeb, 0x87, 0x39, 0x80, 0x74, 0xf1, 0x51, 0xe4, 0xd3, 0x56, 0xba, 0xc5, 0x6f, 0xc1, 0xb5, 0x2c,
|
||||
0x98, 0x2a, 0xf1, 0x29, 0xff, 0xc9, 0x61, 0x39, 0x7d, 0xb1, 0x25, 0x2e, 0x22, 0x96, 0xd7, 0x95,
|
||||
0x8d, 0x31, 0xec, 0xa9, 0x94, 0x54, 0x43, 0x76, 0x1d, 0x58, 0x0a, 0xa4, 0x5b, 0x53, 0x11, 0x2b,
|
||||
0x8e, 0xa3, 0x7e, 0x4f, 0x8a, 0x30, 0x62, 0x25, 0x6b, 0x17, 0xca, 0x3a, 0xf7, 0x32, 0x23, 0x6b,
|
||||
0xfa, 0x72, 0x25, 0x10, 0x7f, 0x3d, 0x07, 0xb0, 0xa5, 0x8b, 0x57, 0xf1, 0x14, 0x9f, 0xa7, 0x8e,
|
||||
0x5c, 0xff, 0x6d, 0x07, 0x95, 0xf5, 0x16, 0x92, 0xbf, 0xed, 0xc0, 0x26, 0x4a, 0x8e, 0x88, 0x8b,
|
||||
0x86, 0xf4, 0x9e, 0x4b, 0xda, 0xfa, 0x00, 0xd9, 0x0c, 0x7c, 0x5f, 0x76, 0xf1, 0xf8, 0x49, 0x0e,
|
||||
0x90, 0x04, 0x74, 0xff, 0xc7, 0x05, 0x58, 0x1e, 0xcf, 0xdf, 0x51, 0x9d, 0xbf, 0xce, 0x1d, 0x1f,
|
||||
0x7a, 0x4e, 0xa6, 0xf4, 0x91, 0xf1, 0x15, 0xa8, 0x1b, 0x8b, 0x90, 0x00, 0xab, 0xe4, 0x99, 0x04,
|
||||
0x03, 0xc9, 0xee, 0x66, 0xff, 0x93, 0xea, 0x75, 0x74, 0x70, 0xf4, 0xd5, 0x09, 0x36, 0xe4, 0x35,
|
||||
0xf3, 0xef, 0x1c, 0xbf, 0x95, 0xe7, 0x4b, 0x99, 0x02, 0xbc, 0x1f, 0xa3, 0x3a, 0x5c, 0xd9, 0x18,
|
||||
0xf9, 0x8e, 0x27, 0x9d, 0x04, 0xfa, 0x0f, 0xb2, 0xd0, 0xa4, 0x9c, 0xee, 0xb7, 0xd0, 0xab, 0xaa,
|
||||
0xb5, 0x47, 0xc7, 0xa6, 0x94, 0xee, 0x2f, 0x17, 0xf9, 0x4d, 0x58, 0x35, 0x58, 0x69, 0xdd, 0x0c,
|
||||
0xfb, 0x2b, 0xb8, 0x70, 0xcb, 0xeb, 0x5a, 0xa7, 0x98, 0x8e, 0xb2, 0xbf, 0x5a, 0xc4, 0x2e, 0xd0,
|
||||
0x9d, 0xbc, 0xdf, 0x26, 0x3e, 0x49, 0x51, 0x31, 0xfb, 0x9d, 0x22, 0x5f, 0x01, 0x68, 0x77, 0x92,
|
||||
0x0f, 0xfd, 0x5e, 0x91, 0xd7, 0xa1, 0xdc, 0xee, 0x10, 0xb7, 0x1f, 0x16, 0xf9, 0x0d, 0x60, 0xe9,
|
||||
0x5b, 0x53, 0x29, 0xf4, 0xfb, 0xba, 0x33, 0x49, 0xe9, 0xcf, 0x1f, 0x14, 0x71, 0x5c, 0xb1, 0xc1,
|
||||
0xcc, 0xfe, 0x66, 0x91, 0x33, 0xa8, 0x67, 0xfc, 0x5d, 0xf6, 0xb7, 0x8a, 0x9c, 0xc3, 0xd2, 0x3e,
|
||||
0xba, 0xb9, 0x7e, 0xcf, 0x8c, 0xe0, 0x77, 0xe9, 0xcb, 0x3b, 0x49, 0x5d, 0x34, 0xfb, 0x51, 0x91,
|
||||
0xdf, 0x02, 0x9e, 0x8d, 0xf1, 0x99, 0x17, 0x7f, 0xbb, 0x78, 0xff, 0xa7, 0x14, 0x52, 0xc9, 0x66,
|
||||
0xdd, 0xd1, 0x4f, 0xf4, 0x02, 0xbf, 0xa7, 0xf4, 0x5f, 0x77, 0x2d, 0x41, 0x2d, 0xea, 0x07, 0xa1,
|
||||
0xa2, 0x26, 0xdd, 0x0c, 0xf1, 0xe9, 0x7a, 0x9f, 0x2e, 0x97, 0xd4, 0x96, 0x88, 0x76, 0x51, 0x95,
|
||||
0xe8, 0xb1, 0x7a, 0x52, 0xc4, 0x54, 0x4c, 0x0a, 0xad, 0xe8, 0x9a, 0x61, 0x7c, 0x8d, 0x8b, 0x95,
|
||||
0x11, 0x75, 0x14, 0x7a, 0xba, 0xe0, 0x4a, 0xe2, 0x29, 0xa4, 0xff, 0xa3, 0x67, 0xd8, 0xc7, 0xc3,
|
||||
0xae, 0xa6, 0xa1, 0xc1, 0xe7, 0xae, 0xbe, 0x20, 0x64, 0x6a, 0x1c, 0x1c, 0xec, 0x47, 0x92, 0xc6,
|
||||
0x63, 0xf2, 0xfe, 0x1f, 0xe4, 0x60, 0x31, 0xbe, 0x5c, 0xe7, 0xf6, 0x5c, 0x5f, 0x97, 0x6c, 0xc5,
|
||||
0x7f, 0x88, 0xd6, 0xf5, 0xdc, 0x61, 0xfc, 0x07, 0x43, 0x2b, 0x50, 0x77, 0x42, 0xd1, 0x5b, 0xf7,
|
||||
0x9d, 0xad, 0x30, 0x18, 0xea, 0x6e, 0xeb, 0xa0, 0xab, 0x2e, 0x15, 0x7b, 0x2e, 0x8f, 0x11, 0x7d,
|
||||
0x28, 0x43, 0x56, 0xa4, 0xfa, 0x89, 0xbe, 0x08, 0x5d, 0xbf, 0x87, 0xae, 0xb2, 0x1f, 0xe9, 0x92,
|
||||
0xb1, 0x3a, 0x54, 0x46, 0x91, 0xec, 0x8a, 0x48, 0xb2, 0x32, 0x36, 0x8e, 0x47, 0xae, 0xa7, 0x5c,
|
||||
0x5f, 0xff, 0xaf, 0x4f, 0x52, 0x13, 0x56, 0xbd, 0xff, 0x2f, 0x73, 0x50, 0xa7, 0xc5, 0x4b, 0xa3,
|
||||
0x09, 0xa9, 0x3a, 0xa9, 0x43, 0x65, 0x2f, 0xf9, 0x5f, 0x97, 0x32, 0xe4, 0x0f, 0x4f, 0x75, 0x34,
|
||||
0xc1, 0x2c, 0x9e, 0xbe, 0x5f, 0xa3, 0xff, 0xe2, 0xa5, 0xc8, 0xbf, 0x04, 0x37, 0x6c, 0x39, 0x08,
|
||||
0x94, 0x7c, 0x2a, 0x5c, 0x95, 0x2d, 0x97, 0x2e, 0xa1, 0xe5, 0xa1, 0x5f, 0xc5, 0xf5, 0xd1, 0x65,
|
||||
0xb2, 0x3c, 0xf0, 0xb3, 0x31, 0xa4, 0x82, 0x83, 0x26, 0x88, 0x31, 0x45, 0xaa, 0x09, 0xca, 0x27,
|
||||
0x81, 0xeb, 0xe3, 0xd7, 0xe8, 0x42, 0x16, 0x41, 0x28, 0x2c, 0x85, 0x20, 0xb8, 0x7f, 0x00, 0x37,
|
||||
0x67, 0x07, 0x53, 0xf4, 0x55, 0x2d, 0xfa, 0x33, 0x41, 0x2a, 0xa0, 0x7d, 0x1a, 0xba, 0xfa, 0xda,
|
||||
0x4e, 0x0d, 0x4a, 0x87, 0xcf, 0x7d, 0x92, 0x86, 0x55, 0x58, 0x3a, 0x08, 0x32, 0x34, 0xac, 0x70,
|
||||
0xbf, 0x3b, 0x16, 0xff, 0x4a, 0x27, 0x25, 0xee, 0xc4, 0x42, 0xa6, 0x38, 0x3c, 0xa7, 0x23, 0x2b,
|
||||
0xf4, 0xb7, 0xce, 0xfa, 0x1a, 0xab, 0x89, 0x3b, 0x39, 0xfa, 0x1a, 0x6b, 0xd2, 0xcd, 0xa2, 0xfe,
|
||||
0xa3, 0x07, 0xbf, 0x2b, 0x3d, 0xe9, 0xb0, 0xd2, 0xfd, 0xf7, 0x60, 0xc5, 0x0c, 0xb5, 0x2b, 0xa3,
|
||||
0x28, 0x2e, 0xae, 0x3e, 0x0a, 0xdd, 0x33, 0x7d, 0x55, 0x76, 0x11, 0xaa, 0x47, 0x32, 0x8c, 0x02,
|
||||
0x9f, 0xae, 0x09, 0x03, 0x94, 0xdb, 0x7d, 0x11, 0xe2, 0x37, 0xee, 0x7f, 0x03, 0x6a, 0x54, 0x6c,
|
||||
0xfd, 0xa9, 0xeb, 0x3b, 0x38, 0x92, 0x0d, 0x53, 0x5f, 0x58, 0x83, 0xd2, 0x66, 0x70, 0x46, 0xe3,
|
||||
0xab, 0xea, 0xbf, 0x34, 0x63, 0xf9, 0xfb, 0x1f, 0x03, 0xd7, 0x7e, 0x9c, 0x23, 0xcf, 0x5d, 0xbf,
|
||||
0x97, 0xdc, 0x1f, 0x04, 0xba, 0x0c, 0xec, 0xc8, 0x73, 0x32, 0x93, 0xea, 0x50, 0x89, 0x1b, 0xf1,
|
||||
0x95, 0xe4, 0x9d, 0x60, 0xe4, 0xe3, 0xd7, 0x9e, 0xc0, 0x75, 0x2d, 0x1b, 0xf8, 0x79, 0xba, 0xd4,
|
||||
0x71, 0xa9, 0x71, 0xa9, 0x2b, 0xe2, 0xd5, 0x28, 0x4a, 0x70, 0x59, 0x8e, 0xdf, 0x04, 0x9e, 0x18,
|
||||
0x66, 0x29, 0x3c, 0x7f, 0xdf, 0x82, 0x6b, 0x33, 0xac, 0x63, 0xd2, 0x99, 0xda, 0x46, 0x60, 0x0b,
|
||||
0x1b, 0xf7, 0xff, 0xe8, 0x17, 0x77, 0x72, 0x3f, 0xff, 0xc5, 0x9d, 0xdc, 0x7f, 0xfe, 0xc5, 0x9d,
|
||||
0xdc, 0x0f, 0x7f, 0x79, 0x67, 0xe1, 0xe7, 0xbf, 0xbc, 0xb3, 0xf0, 0x1f, 0x7e, 0x79, 0x67, 0xe1,
|
||||
0x33, 0x36, 0xf9, 0x97, 0xe8, 0xc7, 0x65, 0x3a, 0x56, 0xde, 0xfc, 0x3f, 0x01, 0x00, 0x00, 0xff,
|
||||
0xff, 0x28, 0x05, 0xef, 0x24, 0x2d, 0x5d, 0x00, 0x00,
|
||||
0x1c, 0x8d, 0xb9, 0x3f, 0x97, 0x4b, 0xb6, 0xf6, 0xc4, 0x95, 0xcf, 0x6d, 0x4d, 0xc8, 0xef, 0x00,
|
||||
0x88, 0xae, 0x72, 0xcf, 0x24, 0x02, 0xcd, 0x66, 0xcf, 0x40, 0xf8, 0xdb, 0x59, 0xf3, 0xe5, 0xea,
|
||||
0x70, 0x62, 0xc6, 0xae, 0xe1, 0x36, 0xd4, 0x71, 0xeb, 0x0e, 0x0f, 0x43, 0xdc, 0xed, 0x8d, 0x45,
|
||||
0x22, 0x7c, 0x7d, 0xbe, 0xee, 0x3d, 0x4a, 0x08, 0xed, 0x2c, 0x13, 0xfe, 0x18, 0x16, 0x75, 0x68,
|
||||
0xcc, 0x30, 0x5d, 0x22, 0xa6, 0x6f, 0xcc, 0xc7, 0xf4, 0x30, 0xa5, 0xb4, 0xc7, 0xd8, 0x4c, 0x47,
|
||||
0x17, 0x4b, 0x2f, 0x1d, 0x5d, 0x7c, 0x15, 0x96, 0x3b, 0xe3, 0xbb, 0x40, 0x1f, 0x15, 0x13, 0x50,
|
||||
0x6e, 0xc1, 0xa2, 0x1b, 0xa5, 0xc1, 0x4d, 0x0a, 0x75, 0x54, 0xed, 0x31, 0x58, 0xf3, 0xdf, 0x95,
|
||||
0xa1, 0x48, 0x33, 0x3f, 0x19, 0xaa, 0xda, 0x1c, 0x53, 0xe9, 0x0f, 0xe6, 0x5f, 0xea, 0x89, 0x1d,
|
||||
0x4f, 0x1a, 0xa4, 0x90, 0xd1, 0x20, 0xdf, 0x86, 0x52, 0x14, 0x84, 0x2a, 0x5e, 0xde, 0x39, 0x85,
|
||||
0xa8, 0x1d, 0x84, 0xca, 0xd6, 0x84, 0x7c, 0x07, 0x2a, 0x27, 0xae, 0xa7, 0x70, 0x51, 0xf4, 0xe4,
|
||||
0xbd, 0x36, 0x1f, 0x8f, 0x1d, 0x22, 0xb2, 0x63, 0x62, 0xbe, 0x97, 0x15, 0xb6, 0x32, 0x71, 0x5a,
|
||||
0x9b, 0x8f, 0xd3, 0x2c, 0x19, 0xbc, 0x0f, 0xac, 0x1b, 0x9c, 0xc9, 0xd0, 0xce, 0xc4, 0x17, 0xf5,
|
||||
0x21, 0x3d, 0x05, 0xe7, 0x4d, 0xa8, 0xf6, 0x5d, 0x47, 0xa2, 0x9d, 0x43, 0x3a, 0xa6, 0x6a, 0x27,
|
||||
0x6d, 0xfe, 0x29, 0x54, 0xc9, 0x3f, 0x40, 0xad, 0x58, 0x7b, 0xe9, 0xc9, 0xd7, 0xae, 0x4a, 0xcc,
|
||||
0x00, 0x3f, 0x44, 0x1f, 0xdf, 0x71, 0x15, 0x85, 0x99, 0xab, 0x76, 0xd2, 0xc6, 0x0e, 0x93, 0xbc,
|
||||
0x67, 0x3b, 0x5c, 0xd7, 0x1d, 0x9e, 0x84, 0xf3, 0xb7, 0xe0, 0x06, 0xc1, 0x26, 0x0e, 0x49, 0xdc,
|
||||
0x6a, 0xc8, 0x74, 0xf6, 0x4b, 0x34, 0x58, 0x86, 0xa2, 0x27, 0xf7, 0xdc, 0x81, 0xab, 0x1a, 0x4b,
|
||||
0x77, 0x73, 0xf7, 0x4a, 0x76, 0x0a, 0xe0, 0xaf, 0xc1, 0xaa, 0x23, 0x4f, 0xc4, 0xc8, 0x53, 0x1d,
|
||||
0x39, 0x18, 0x7a, 0x42, 0xc9, 0x96, 0x43, 0x32, 0x5a, 0xb3, 0xa7, 0x5f, 0xf0, 0xd7, 0xe1, 0x9a,
|
||||
0x01, 0x1e, 0x26, 0xc9, 0x81, 0x96, 0x43, 0x51, 0xb8, 0x9a, 0x3d, 0xeb, 0x95, 0xb5, 0x6f, 0xd4,
|
||||
0x30, 0x1e, 0xa0, 0xe8, 0xa7, 0xc6, 0x0a, 0x34, 0x52, 0xfa, 0x44, 0x7e, 0x24, 0x3c, 0x4f, 0x86,
|
||||
0x17, 0xda, 0xc9, 0xfd, 0x54, 0xf8, 0xc7, 0xc2, 0x67, 0x05, 0x3a, 0x63, 0x85, 0x27, 0x7d, 0x47,
|
||||
0x84, 0xfa, 0x44, 0x7e, 0x44, 0x07, 0x7a, 0xc9, 0xba, 0x07, 0x45, 0x9a, 0xd2, 0x1a, 0x94, 0xb4,
|
||||
0x97, 0x44, 0x1e, 0xb3, 0xf1, 0x90, 0x48, 0x23, 0xef, 0xe1, 0xf6, 0x63, 0xf9, 0xe6, 0xcf, 0x0a,
|
||||
0x50, 0x8d, 0x27, 0x2f, 0x4e, 0x05, 0xe4, 0xd2, 0x54, 0x00, 0x9a, 0x71, 0xd1, 0x13, 0x37, 0x72,
|
||||
0x8f, 0x8d, 0x59, 0x5a, 0xb5, 0x53, 0x00, 0x5a, 0x42, 0xcf, 0x5d, 0x47, 0xf5, 0x69, 0xcf, 0x94,
|
||||
0x6c, 0xdd, 0xe0, 0xf7, 0x60, 0xc5, 0xc1, 0x79, 0xf0, 0xbb, 0xde, 0xc8, 0x91, 0x1d, 0x3c, 0x45,
|
||||
0x75, 0x98, 0x60, 0x12, 0xcc, 0xbf, 0x07, 0xa0, 0xdc, 0x81, 0xdc, 0x09, 0xc2, 0x81, 0x50, 0xc6,
|
||||
0x37, 0x78, 0xff, 0xe5, 0xa4, 0x7a, 0xad, 0x93, 0x30, 0xb0, 0x33, 0xcc, 0x90, 0x35, 0x7e, 0xcd,
|
||||
0xb0, 0xae, 0x7c, 0x21, 0xd6, 0x5b, 0x09, 0x03, 0x3b, 0xc3, 0xcc, 0xfa, 0x0d, 0x80, 0xf4, 0x0d,
|
||||
0xbf, 0x09, 0x7c, 0x3f, 0xf0, 0x55, 0x7f, 0xfd, 0xf8, 0x38, 0xdc, 0x90, 0x27, 0x41, 0x28, 0xb7,
|
||||
0x04, 0x1e, 0x6b, 0x37, 0x60, 0x35, 0x81, 0xaf, 0x9f, 0x28, 0x19, 0x22, 0x98, 0xa6, 0xbe, 0xdd,
|
||||
0x0f, 0x42, 0xa5, 0x6d, 0x2b, 0x7a, 0x7c, 0xdc, 0x66, 0x05, 0x3c, 0x4a, 0x5b, 0xed, 0x43, 0x56,
|
||||
0xb4, 0xee, 0x01, 0xa4, 0x43, 0x22, 0x1f, 0x84, 0x9e, 0xde, 0x78, 0x68, 0x3c, 0x12, 0x6a, 0x3d,
|
||||
0x7c, 0x8b, 0xe5, 0x9a, 0x7f, 0x54, 0x80, 0x22, 0xaa, 0x1a, 0x74, 0xbf, 0xb2, 0xfb, 0x42, 0x2f,
|
||||
0x5f, 0x16, 0xf4, 0xc5, 0x14, 0x24, 0xf2, 0xce, 0x2a, 0xc8, 0xf7, 0xa0, 0xde, 0x1d, 0x45, 0x2a,
|
||||
0x18, 0xd0, 0xe9, 0x60, 0xf2, 0x28, 0x37, 0xa7, 0x02, 0x19, 0x4f, 0x84, 0x37, 0x92, 0x76, 0x16,
|
||||
0x95, 0xbf, 0x0d, 0xe5, 0x13, 0xbd, 0x10, 0x3a, 0x94, 0xf1, 0x2b, 0x97, 0x1c, 0x20, 0x66, 0xb2,
|
||||
0x0d, 0x32, 0x8e, 0xcb, 0x9d, 0x12, 0xa2, 0x2c, 0xc8, 0x1c, 0x04, 0xe5, 0xe4, 0x20, 0xf8, 0x0d,
|
||||
0x58, 0x96, 0x68, 0x56, 0x1c, 0x79, 0xa2, 0x2b, 0x07, 0xd2, 0x8f, 0x57, 0xfe, 0xad, 0x97, 0x18,
|
||||
0x31, 0xd9, 0x25, 0x34, 0xec, 0x09, 0x5e, 0xd6, 0x57, 0xcd, 0x26, 0xad, 0x40, 0x61, 0x3d, 0xea,
|
||||
0x1a, 0xb7, 0x5b, 0x46, 0x5d, 0x6d, 0xd3, 0x6f, 0xd2, 0x80, 0x59, 0xde, 0x7a, 0x03, 0x6a, 0x09,
|
||||
0x0f, 0xce, 0x60, 0xf1, 0x20, 0x50, 0xed, 0xa1, 0xec, 0xba, 0x27, 0xae, 0x74, 0x74, 0x20, 0xa1,
|
||||
0xad, 0x44, 0xa8, 0x74, 0xe4, 0x6a, 0xdb, 0x77, 0x58, 0xbe, 0xf9, 0x6f, 0x2b, 0x50, 0xd6, 0x1a,
|
||||
0xdf, 0x0c, 0xa9, 0x96, 0x0c, 0xe9, 0x3b, 0x50, 0x0d, 0x86, 0x32, 0x14, 0x2a, 0x08, 0x4d, 0xb8,
|
||||
0xe0, 0xed, 0x97, 0x39, 0x41, 0xd6, 0x0e, 0x0d, 0xb1, 0x9d, 0xb0, 0x99, 0x94, 0x97, 0xfc, 0xb4,
|
||||
0xbc, 0xdc, 0x07, 0x16, 0x1f, 0x16, 0x47, 0x21, 0xd2, 0xa9, 0x0b, 0xe3, 0xfc, 0x4d, 0xc1, 0x79,
|
||||
0x07, 0x6a, 0xdd, 0xc0, 0x77, 0xdc, 0x24, 0x74, 0xb0, 0xfc, 0xf0, 0x9d, 0x97, 0xea, 0xe1, 0x66,
|
||||
0x4c, 0x6d, 0xa7, 0x8c, 0xf8, 0x6b, 0x50, 0x3a, 0x43, 0x41, 0x22, 0x89, 0xb9, 0x5c, 0xcc, 0x34,
|
||||
0x12, 0xff, 0x0c, 0xea, 0xdf, 0x1f, 0xb9, 0xdd, 0xd3, 0xc3, 0x6c, 0x68, 0xea, 0xbd, 0x97, 0xea,
|
||||
0xc5, 0x77, 0x52, 0x7a, 0x3b, 0xcb, 0x2c, 0x23, 0xbc, 0x95, 0x3f, 0x85, 0xf0, 0x56, 0xa7, 0x84,
|
||||
0xd7, 0x7a, 0x05, 0xaa, 0xf1, 0xe2, 0x90, 0x48, 0xf9, 0x28, 0x1d, 0x65, 0xc8, 0x1f, 0x86, 0x2c,
|
||||
0x67, 0xfd, 0x71, 0x0e, 0x6a, 0xc9, 0xc4, 0x8c, 0x87, 0xa1, 0xb6, 0xbf, 0x3f, 0x12, 0x1e, 0xcb,
|
||||
0x91, 0x1f, 0x15, 0x28, 0xdd, 0x22, 0x6d, 0xf2, 0x88, 0x92, 0xb1, 0x21, 0x2b, 0xd0, 0xd9, 0x21,
|
||||
0xa3, 0x88, 0x15, 0x39, 0x87, 0x65, 0x03, 0x3e, 0x0c, 0x35, 0x6a, 0x09, 0xdd, 0x2c, 0x7c, 0x1b,
|
||||
0x03, 0xca, 0xfa, 0xa8, 0x39, 0x95, 0xda, 0x8d, 0x3c, 0x08, 0x14, 0x35, 0xaa, 0xd8, 0x97, 0x96,
|
||||
0xcf, 0x6a, 0xf8, 0xcd, 0x83, 0x40, 0xb5, 0x7c, 0x06, 0xa9, 0xdd, 0x5e, 0x8f, 0x3f, 0x4f, 0xad,
|
||||
0x45, 0xf2, 0x0a, 0x3c, 0xaf, 0xe5, 0xb3, 0x25, 0xf3, 0x42, 0xb7, 0x96, 0x91, 0xe3, 0xf6, 0xb9,
|
||||
0xe8, 0x22, 0xf9, 0x0a, 0x5f, 0x06, 0x40, 0x1a, 0xd3, 0x66, 0xb8, 0x6d, 0xb6, 0xcf, 0xdd, 0x48,
|
||||
0x45, 0x6c, 0xd5, 0xfa, 0x37, 0x39, 0xa8, 0x67, 0x16, 0x01, 0xfd, 0x02, 0x42, 0x44, 0x5d, 0xab,
|
||||
0xdd, 0x84, 0xef, 0xc9, 0x48, 0xc9, 0xd0, 0x89, 0xf5, 0x68, 0x27, 0xc0, 0xc7, 0x3c, 0x7e, 0xaf,
|
||||
0x13, 0x0c, 0x82, 0x30, 0x0c, 0x9e, 0xeb, 0x33, 0x71, 0x4f, 0x44, 0xea, 0xa9, 0x94, 0xa7, 0xac,
|
||||
0x88, 0x43, 0xdd, 0x1c, 0x85, 0xa1, 0xf4, 0x35, 0xa0, 0x44, 0x9d, 0x93, 0xe7, 0xba, 0x55, 0x46,
|
||||
0xa6, 0x88, 0x4c, 0x8a, 0x9a, 0x55, 0x70, 0xb3, 0x1a, 0x6c, 0x0d, 0xa9, 0x22, 0x02, 0xa2, 0xeb,
|
||||
0x66, 0x0d, 0x5d, 0x6f, 0xed, 0xba, 0x1e, 0x9e, 0x6c, 0x89, 0x8b, 0x68, 0xbd, 0x17, 0x30, 0x98,
|
||||
0x04, 0x1e, 0x04, 0xcf, 0x59, 0xbd, 0x39, 0x02, 0x48, 0x8d, 0x75, 0x74, 0x52, 0x50, 0xd6, 0x92,
|
||||
0xe0, 0xb2, 0x69, 0xf1, 0x43, 0x00, 0x7c, 0x22, 0xcc, 0xd8, 0x53, 0x79, 0x09, 0x0b, 0x8a, 0xe8,
|
||||
0xec, 0x0c, 0x8b, 0xe6, 0x5f, 0x84, 0x5a, 0xf2, 0x02, 0x7d, 0x53, 0xb2, 0x75, 0x92, 0xcf, 0xc6,
|
||||
0x4d, 0x3c, 0xb8, 0x5d, 0xdf, 0x91, 0xe7, 0xb4, 0xf7, 0x4b, 0xb6, 0x6e, 0x60, 0x2f, 0xfb, 0xae,
|
||||
0xe3, 0x48, 0x3f, 0x4e, 0x01, 0xe8, 0xd6, 0xac, 0x7c, 0x6b, 0x71, 0x66, 0xbe, 0xb5, 0xf9, 0x9b,
|
||||
0x50, 0xcf, 0x78, 0x13, 0x97, 0x0e, 0x3b, 0xd3, 0xb1, 0xfc, 0x78, 0xc7, 0x6e, 0x43, 0x2d, 0xce,
|
||||
0xf1, 0x47, 0x74, 0xc2, 0xd4, 0xec, 0x14, 0xd0, 0xfc, 0x67, 0x79, 0x34, 0x71, 0x70, 0x68, 0x93,
|
||||
0x1e, 0xc0, 0x0e, 0x94, 0xd1, 0x1d, 0x1e, 0xc5, 0xc9, 0xea, 0x39, 0xad, 0xec, 0x36, 0xd1, 0xec,
|
||||
0x2e, 0xd8, 0x86, 0x9a, 0x7f, 0x08, 0x05, 0x25, 0x7a, 0x26, 0x82, 0xf6, 0xf5, 0xf9, 0x98, 0x74,
|
||||
0x44, 0x6f, 0x77, 0xc1, 0x46, 0x3a, 0xbe, 0x07, 0xd5, 0xae, 0x09, 0x7a, 0x18, 0xc5, 0x35, 0xa7,
|
||||
0x91, 0x1e, 0x87, 0x4a, 0x76, 0x17, 0xec, 0x84, 0x03, 0xff, 0x36, 0x14, 0xd1, 0xec, 0x30, 0x39,
|
||||
0xfd, 0x39, 0x9d, 0x0f, 0xdc, 0x2e, 0xbb, 0x0b, 0x36, 0x51, 0x6e, 0x54, 0xa0, 0x44, 0x7a, 0xb2,
|
||||
0xd9, 0x80, 0xb2, 0x1e, 0xeb, 0xe4, 0xcc, 0x35, 0x6f, 0x41, 0xa1, 0x23, 0x7a, 0x68, 0xfa, 0xb9,
|
||||
0x4e, 0x64, 0x7c, 0x68, 0x7c, 0x6c, 0x7e, 0x25, 0x0d, 0xe0, 0x64, 0x63, 0x83, 0xb9, 0xb1, 0xd8,
|
||||
0x60, 0xb3, 0x0c, 0x45, 0xfc, 0x62, 0xf3, 0xf6, 0x55, 0x66, 0x64, 0xf3, 0x7f, 0xe4, 0xd1, 0xe2,
|
||||
0x54, 0xf2, 0x7c, 0x66, 0xdc, 0xf3, 0x13, 0xa8, 0x0d, 0xc3, 0xa0, 0x2b, 0xa3, 0x28, 0x08, 0x8d,
|
||||
0x89, 0xf2, 0xda, 0x8b, 0x53, 0x8b, 0x6b, 0x47, 0x31, 0x8d, 0x9d, 0x92, 0x5b, 0x7f, 0x23, 0x0f,
|
||||
0xb5, 0xe4, 0x85, 0x36, 0x74, 0x95, 0x3c, 0xd7, 0x31, 0xae, 0x7d, 0x19, 0x0e, 0x84, 0xeb, 0x68,
|
||||
0xed, 0xb1, 0xd9, 0x17, 0xb1, 0x15, 0xf6, 0xbd, 0x60, 0xa4, 0x46, 0xc7, 0x52, 0xc7, 0x36, 0x9e,
|
||||
0xb8, 0x03, 0x19, 0xb0, 0x22, 0x65, 0x15, 0x50, 0xb0, 0xbb, 0x5e, 0x30, 0x72, 0x58, 0x09, 0xdb,
|
||||
0x8f, 0xe8, 0x08, 0xda, 0x17, 0xc3, 0x48, 0xeb, 0xcc, 0x7d, 0x37, 0x0c, 0x58, 0x05, 0x89, 0x76,
|
||||
0xdc, 0xde, 0x40, 0xb0, 0x2a, 0x32, 0xeb, 0x3c, 0x77, 0x15, 0x2a, 0xe1, 0x1a, 0x5f, 0x85, 0xa5,
|
||||
0xc3, 0xa1, 0xf4, 0xdb, 0x2a, 0x94, 0x52, 0xed, 0x8b, 0xa1, 0x0e, 0x76, 0xd9, 0xd2, 0x71, 0x5c,
|
||||
0xa5, 0xf5, 0xe7, 0x8e, 0xe8, 0xca, 0xe3, 0x20, 0x38, 0x65, 0x8b, 0xa8, 0x68, 0x5a, 0x7e, 0xa4,
|
||||
0x44, 0x2f, 0x14, 0x03, 0xad, 0x43, 0x3b, 0xd2, 0x93, 0xd4, 0x5a, 0xa6, 0x6f, 0xbb, 0xaa, 0x3f,
|
||||
0x3a, 0x7e, 0x84, 0x0e, 0xc1, 0x8a, 0x4e, 0x40, 0x38, 0x72, 0x28, 0x51, 0x87, 0x2e, 0x42, 0x75,
|
||||
0xc3, 0xf5, 0xdc, 0x63, 0xd7, 0x73, 0xd9, 0x2a, 0xa2, 0x6e, 0x9f, 0x77, 0x85, 0xe7, 0x3a, 0xa1,
|
||||
0x78, 0xce, 0x78, 0x73, 0x15, 0x56, 0x26, 0x52, 0xa8, 0xcd, 0x8a, 0xf1, 0x31, 0x9a, 0x4b, 0x50,
|
||||
0xcf, 0x24, 0xc5, 0x9a, 0xaf, 0x42, 0x35, 0x4e, 0x99, 0xa1, 0x2f, 0xe6, 0x46, 0x3a, 0xd8, 0x67,
|
||||
0x56, 0x3c, 0x69, 0x37, 0xff, 0x69, 0x0e, 0xca, 0x3a, 0xed, 0xc8, 0x37, 0x92, 0x32, 0x81, 0xdc,
|
||||
0x1c, 0x39, 0x2a, 0x4d, 0x64, 0x32, 0x7c, 0x49, 0xad, 0xc0, 0x75, 0x28, 0x79, 0xe4, 0x74, 0x19,
|
||||
0x5d, 0x44, 0x8d, 0x8c, 0xea, 0x28, 0x64, 0x55, 0x87, 0xf5, 0x6e, 0x92, 0x55, 0x8c, 0x03, 0x4c,
|
||||
0x64, 0x86, 0x75, 0x42, 0x29, 0x75, 0xf0, 0x88, 0x7c, 0xa6, 0x3c, 0x29, 0xfe, 0x60, 0x30, 0x14,
|
||||
0x5d, 0x45, 0x80, 0x82, 0x75, 0x02, 0xd5, 0xa3, 0x20, 0x9a, 0x3c, 0x4e, 0x2b, 0x50, 0xe8, 0x04,
|
||||
0x43, 0x6d, 0xc0, 0x6d, 0x04, 0x8a, 0x0c, 0x38, 0x7d, 0x7a, 0x9e, 0x28, 0x2d, 0x0f, 0xb6, 0xdb,
|
||||
0xeb, 0x2b, 0xed, 0x5d, 0xb5, 0x7c, 0x5f, 0x86, 0xac, 0x84, 0xd3, 0x6f, 0xcb, 0x21, 0x9a, 0x85,
|
||||
0xac, 0x8c, 0x13, 0x4e, 0xf0, 0x1d, 0x37, 0x8c, 0x14, 0xab, 0x58, 0x2d, 0x3c, 0x08, 0xdd, 0x1e,
|
||||
0x9d, 0x5f, 0xf4, 0x40, 0xac, 0x16, 0xb0, 0x43, 0xd4, 0xdc, 0x94, 0x3e, 0x8a, 0x07, 0xa5, 0xad,
|
||||
0x74, 0xdd, 0x08, 0x7d, 0x20, 0x8f, 0x87, 0x0f, 0xb5, 0x3f, 0x19, 0x45, 0xca, 0x3d, 0xb9, 0x60,
|
||||
0x05, 0xeb, 0x29, 0x2c, 0x8d, 0x55, 0x98, 0xf0, 0xeb, 0xc0, 0xc6, 0x00, 0xd8, 0xf5, 0x05, 0x7e,
|
||||
0x0b, 0xae, 0x8d, 0x41, 0xf7, 0x5d, 0xc7, 0xa1, 0xf8, 0xdd, 0xe4, 0x8b, 0x78, 0x80, 0x1b, 0x35,
|
||||
0xa8, 0x74, 0xf5, 0x9a, 0x58, 0x47, 0xb0, 0x44, 0x8b, 0xb4, 0x2f, 0x95, 0x38, 0xf4, 0xbd, 0x8b,
|
||||
0x3f, 0x75, 0x19, 0x90, 0xf5, 0x0d, 0x28, 0x51, 0xbc, 0x1d, 0xb7, 0xfa, 0x49, 0x18, 0x0c, 0x88,
|
||||
0x57, 0xc9, 0xa6, 0x67, 0xe4, 0xae, 0x02, 0xb3, 0xd2, 0x79, 0x15, 0x58, 0x3f, 0x02, 0xa8, 0xac,
|
||||
0x77, 0xbb, 0xc1, 0xc8, 0x57, 0x53, 0x5f, 0x9e, 0x15, 0xd2, 0x7d, 0x1b, 0xca, 0xe2, 0x4c, 0x28,
|
||||
0x11, 0x1a, 0x15, 0x3d, 0x69, 0x8c, 0x19, 0x5e, 0x6b, 0xeb, 0x84, 0x64, 0x1b, 0x64, 0x24, 0xeb,
|
||||
0x06, 0xfe, 0x89, 0xdb, 0x33, 0x5a, 0xf9, 0x32, 0xb2, 0x4d, 0x42, 0xb2, 0x0d, 0x32, 0x92, 0x99,
|
||||
0x53, 0xa5, 0x74, 0x25, 0x99, 0x56, 0xad, 0xc9, 0x21, 0xf2, 0x00, 0x8a, 0xae, 0x7f, 0x12, 0x98,
|
||||
0xf2, 0xbf, 0x57, 0x2e, 0x21, 0xa2, 0x1a, 0x38, 0x42, 0x6c, 0x4a, 0x28, 0xeb, 0x0e, 0xf3, 0xf7,
|
||||
0xa1, 0x44, 0x69, 0x35, 0x93, 0xc8, 0x98, 0xab, 0x62, 0x47, 0x53, 0xf0, 0x9b, 0x71, 0x96, 0x86,
|
||||
0xe6, 0x0b, 0xe1, 0xd4, 0xdc, 0xa8, 0xc6, 0x53, 0xd6, 0xfc, 0x4f, 0x39, 0x28, 0xeb, 0x11, 0xf2,
|
||||
0x57, 0x61, 0x59, 0xfa, 0xb8, 0xd9, 0xe3, 0x73, 0xc3, 0xec, 0xf2, 0x09, 0x28, 0x5a, 0xb1, 0x06,
|
||||
0x22, 0x8f, 0x47, 0x3d, 0x13, 0x01, 0xc8, 0x82, 0xf8, 0x7b, 0x70, 0x4b, 0x37, 0x8f, 0x42, 0x19,
|
||||
0x4a, 0x4f, 0x8a, 0x48, 0x6e, 0xf6, 0x85, 0xef, 0x4b, 0xcf, 0x58, 0x11, 0x97, 0xbd, 0xe6, 0x16,
|
||||
0x2c, 0xea, 0x57, 0xed, 0xa1, 0xe8, 0xca, 0xc8, 0x64, 0x9d, 0xc6, 0x60, 0xfc, 0x9b, 0x50, 0xa2,
|
||||
0x22, 0xcc, 0x86, 0x73, 0xb5, 0xf0, 0x69, 0xac, 0x66, 0x90, 0x1c, 0x73, 0xeb, 0x00, 0x7a, 0x35,
|
||||
0xd0, 0x0b, 0x33, 0xda, 0xe9, 0xcb, 0x57, 0x2e, 0x1f, 0xb9, 0x7c, 0x19, 0x22, 0xec, 0x9f, 0x23,
|
||||
0x3d, 0x49, 0xd5, 0x72, 0x78, 0x0c, 0xe7, 0x29, 0xbe, 0x3f, 0x06, 0x6b, 0xfe, 0x76, 0x11, 0x8a,
|
||||
0xb8, 0x90, 0x88, 0xdc, 0x0f, 0x06, 0x32, 0x89, 0x72, 0x6a, 0xa1, 0x1d, 0x83, 0xa1, 0x1d, 0x25,
|
||||
0x74, 0xa2, 0x39, 0x41, 0xd3, 0xca, 0x6d, 0x12, 0x8c, 0x98, 0xc3, 0x30, 0x38, 0x71, 0xbd, 0x14,
|
||||
0xd3, 0x58, 0x5c, 0x13, 0x60, 0xfe, 0x0e, 0xdc, 0x1c, 0x88, 0xf0, 0x54, 0x2a, 0xd2, 0x47, 0x4f,
|
||||
0x83, 0xf0, 0x34, 0xc2, 0x99, 0x6b, 0x39, 0x26, 0x3c, 0x76, 0xc9, 0x5b, 0x54, 0xf0, 0x8e, 0x3c,
|
||||
0x73, 0x09, 0xb3, 0xaa, 0x8b, 0x2b, 0xe3, 0x36, 0x0a, 0x87, 0xd0, 0x53, 0xd3, 0x36, 0xbc, 0x4c,
|
||||
0xe6, 0x62, 0x1c, 0x8a, 0xc6, 0x9a, 0x2e, 0x3a, 0x89, 0x5a, 0x0e, 0x45, 0xec, 0x6a, 0x76, 0x0a,
|
||||
0x40, 0xd1, 0xa1, 0x8f, 0x3d, 0xd1, 0x6a, 0x7c, 0x49, 0x7b, 0x99, 0x19, 0x10, 0x62, 0x28, 0xd9,
|
||||
0xed, 0xc7, 0x1f, 0xd1, 0xe1, 0xb4, 0x2c, 0x88, 0xdf, 0x01, 0xe8, 0x09, 0x25, 0x9f, 0x8b, 0x8b,
|
||||
0xc7, 0xa1, 0xd7, 0x90, 0x3a, 0x04, 0x9f, 0x42, 0xd0, 0x4f, 0xf5, 0x82, 0xae, 0xf0, 0xda, 0x2a,
|
||||
0x08, 0x45, 0x4f, 0x1e, 0x09, 0xd5, 0x6f, 0xf4, 0xb4, 0x9f, 0x3a, 0x09, 0xc7, 0x11, 0x2b, 0x77,
|
||||
0x20, 0x3f, 0x0b, 0x7c, 0xd9, 0xe8, 0xeb, 0x11, 0xc7, 0x6d, 0xec, 0x89, 0xf0, 0x85, 0x77, 0xa1,
|
||||
0xdc, 0x2e, 0x8e, 0xc5, 0xd5, 0x3d, 0xc9, 0x80, 0x70, 0xac, 0xbe, 0x54, 0xcf, 0x83, 0xf0, 0xb4,
|
||||
0xe5, 0x34, 0x3e, 0xd7, 0x63, 0x4d, 0x00, 0xd6, 0x21, 0x40, 0x2a, 0x44, 0x78, 0x96, 0xac, 0x53,
|
||||
0x9a, 0x80, 0x2d, 0xa0, 0x73, 0x70, 0x24, 0x7d, 0xc7, 0xf5, 0x7b, 0x5b, 0x46, 0x6e, 0x58, 0x0e,
|
||||
0x81, 0x14, 0x02, 0x90, 0x4e, 0x02, 0x24, 0x43, 0x84, 0x5a, 0xd2, 0x61, 0x05, 0xeb, 0x7f, 0xe5,
|
||||
0xa0, 0x9e, 0xc9, 0x8a, 0xff, 0x19, 0x66, 0xf2, 0xf1, 0x64, 0x47, 0x7d, 0x81, 0x13, 0xaa, 0x65,
|
||||
0x2a, 0x69, 0xe3, 0x74, 0x9b, 0xa4, 0x3d, 0xbe, 0xd5, 0x0e, 0x7f, 0x06, 0xf2, 0x85, 0xb2, 0xf8,
|
||||
0xd6, 0x43, 0x13, 0x35, 0xa9, 0x43, 0xe5, 0xb1, 0x7f, 0xea, 0x07, 0xcf, 0x7d, 0x7d, 0x64, 0x53,
|
||||
0x69, 0xc6, 0x58, 0x92, 0x29, 0xae, 0x9e, 0x28, 0x58, 0xff, 0xb8, 0x38, 0x51, 0xc5, 0xb4, 0x0d,
|
||||
0x65, 0xed, 0x06, 0x90, 0x85, 0x3a, 0x5d, 0x76, 0x92, 0x45, 0x36, 0x09, 0x8d, 0x0c, 0xc8, 0x36,
|
||||
0xc4, 0x68, 0x9f, 0x27, 0xa5, 0x7a, 0xf9, 0x99, 0x89, 0x97, 0x31, 0x46, 0xb1, 0x1a, 0x1c, 0xab,
|
||||
0x56, 0x4d, 0x38, 0x34, 0xff, 0x5a, 0x0e, 0xae, 0xcf, 0x42, 0xc9, 0xd6, 0xf4, 0xe6, 0xc6, 0x6b,
|
||||
0x7a, 0xdb, 0x13, 0x35, 0xb2, 0x79, 0x1a, 0xcd, 0x83, 0x97, 0xec, 0xc4, 0x78, 0xc5, 0xac, 0xf5,
|
||||
0x93, 0x1c, 0xac, 0x4e, 0x8d, 0x39, 0x63, 0xe4, 0x00, 0x94, 0xb5, 0x64, 0xe9, 0xda, 0x97, 0xa4,
|
||||
0x1a, 0x41, 0x47, 0x93, 0xe9, 0x4c, 0x89, 0x74, 0x7a, 0xd7, 0x54, 0x05, 0x6b, 0xf3, 0x17, 0x57,
|
||||
0x0d, 0x75, 0x75, 0x4f, 0xb2, 0x12, 0x5a, 0x27, 0xda, 0xee, 0x32, 0x90, 0xb2, 0x36, 0x51, 0x75,
|
||||
0xc8, 0x9b, 0x55, 0xa8, 0xa6, 0x66, 0x34, 0xf4, 0xdc, 0x2e, 0x36, 0xab, 0xbc, 0x09, 0x37, 0x75,
|
||||
0x69, 0xb8, 0x71, 0x07, 0x4f, 0x3a, 0x7d, 0x97, 0x36, 0x07, 0xab, 0x59, 0x36, 0x5c, 0x9b, 0x31,
|
||||
0x26, 0xea, 0xe5, 0x13, 0xd3, 0xe3, 0x65, 0x80, 0xad, 0x27, 0x71, 0x3f, 0x59, 0x8e, 0x73, 0x58,
|
||||
0xde, 0x7a, 0x92, 0x65, 0x68, 0xf6, 0xcb, 0x13, 0xd4, 0x24, 0x11, 0x2b, 0x58, 0xbf, 0x93, 0x8b,
|
||||
0xf3, 0xdc, 0xcd, 0xbf, 0x00, 0x4b, 0xba, 0x8f, 0x47, 0xe2, 0xc2, 0x0b, 0x84, 0xc3, 0xb7, 0x61,
|
||||
0x39, 0x4a, 0xee, 0x2b, 0x64, 0x8e, 0x83, 0xc9, 0xd3, 0xbc, 0x3d, 0x86, 0x64, 0x4f, 0x10, 0xc5,
|
||||
0x5e, 0x4d, 0x3e, 0x0d, 0x8e, 0x73, 0xf2, 0xcf, 0x04, 0xed, 0xb2, 0x45, 0xf2, 0xb8, 0x84, 0xf5,
|
||||
0x4d, 0x58, 0x25, 0xe5, 0xa5, 0x3b, 0xa3, 0x2d, 0x66, 0x94, 0x07, 0xad, 0x77, 0xb7, 0x62, 0x79,
|
||||
0x30, 0x4d, 0xeb, 0x0f, 0xcb, 0x00, 0x69, 0x22, 0x60, 0xc6, 0x36, 0x9f, 0x65, 0x04, 0x4d, 0xa5,
|
||||
0xe5, 0x0a, 0x2f, 0x9d, 0x96, 0x7b, 0x2f, 0x31, 0xdc, 0x75, 0x44, 0x76, 0xb2, 0x46, 0x37, 0xed,
|
||||
0xd3, 0xa4, 0xb9, 0x3e, 0x56, 0xf6, 0x51, 0x9a, 0x2c, 0xfb, 0xb8, 0x3b, 0x5d, 0x23, 0x36, 0xa1,
|
||||
0x7f, 0xd2, 0x20, 0x43, 0x65, 0x2c, 0xc8, 0xd0, 0x84, 0x6a, 0x28, 0x85, 0x13, 0xf8, 0xde, 0x45,
|
||||
0x9c, 0xfd, 0x89, 0xdb, 0xfc, 0x4d, 0x28, 0x29, 0xba, 0x72, 0x51, 0xa5, 0xed, 0xf2, 0x82, 0x85,
|
||||
0xd3, 0xb8, 0xa8, 0xcc, 0xdc, 0xc8, 0x14, 0x76, 0xe9, 0x13, 0xac, 0x6a, 0x67, 0x20, 0x7c, 0x0d,
|
||||
0xb8, 0x8b, 0x1e, 0x97, 0xe7, 0x49, 0x67, 0xe3, 0x62, 0x4b, 0x27, 0x65, 0xe8, 0xd4, 0xac, 0xda,
|
||||
0x33, 0xde, 0xc4, 0xeb, 0xbf, 0x98, 0xae, 0x3f, 0x75, 0xf9, 0xcc, 0x8d, 0x70, 0xa4, 0x4b, 0x64,
|
||||
0x1c, 0x24, 0x6d, 0x3c, 0x97, 0xe3, 0x3d, 0xaa, 0xe7, 0x92, 0xa4, 0x37, 0xcd, 0x6c, 0x5e, 0xf2,
|
||||
0xd6, 0xfa, 0x7b, 0xf9, 0xc4, 0xc1, 0xa9, 0x41, 0xe9, 0x58, 0x44, 0x6e, 0x57, 0x3b, 0xaf, 0xe6,
|
||||
0xe0, 0xd7, 0x4e, 0x8e, 0x0a, 0x9c, 0x80, 0xe5, 0xd1, 0x7b, 0x89, 0x24, 0xfa, 0x29, 0xcb, 0x00,
|
||||
0xe9, 0x35, 0x14, 0x56, 0xc4, 0xbd, 0x19, 0xaf, 0xb7, 0xae, 0xcf, 0x20, 0x52, 0x8a, 0x77, 0x39,
|
||||
0x49, 0xe5, 0x1b, 0x79, 0xae, 0xa4, 0xfb, 0x59, 0x15, 0x71, 0xfc, 0x40, 0x49, 0x1d, 0xed, 0x23,
|
||||
0xe9, 0x64, 0x80, 0x6c, 0xe2, 0xba, 0x6a, 0x56, 0x47, 0x77, 0x22, 0x66, 0xaa, 0x43, 0x74, 0x11,
|
||||
0xb9, 0x56, 0x8b, 0xb8, 0x3b, 0xc7, 0x5f, 0xb0, 0x25, 0xec, 0x51, 0x7a, 0xbb, 0x85, 0x2d, 0x23,
|
||||
0x57, 0x41, 0x55, 0x03, 0x2b, 0xf8, 0x78, 0x46, 0xb5, 0x04, 0x0c, 0xbf, 0xea, 0xa0, 0xc2, 0x58,
|
||||
0xc5, 0x9e, 0x25, 0xa6, 0x01, 0xe3, 0xe8, 0x2d, 0x0d, 0x05, 0xba, 0x2e, 0xee, 0x50, 0xf8, 0x8a,
|
||||
0x5d, 0xc3, 0xa1, 0x0e, 0x9d, 0x13, 0x76, 0xdd, 0xfa, 0x51, 0x5a, 0x57, 0xfa, 0x7a, 0xe2, 0x30,
|
||||
0xcc, 0x23, 0xc0, 0x97, 0xb9, 0x14, 0xdb, 0xb0, 0x1a, 0xca, 0xef, 0x8f, 0xdc, 0xb1, 0xa2, 0xe9,
|
||||
0xc2, 0xd5, 0xe9, 0xfc, 0x69, 0x0a, 0xeb, 0x0c, 0x56, 0xe3, 0xc6, 0x53, 0x57, 0xf5, 0x29, 0xec,
|
||||
0xc2, 0xdf, 0xcc, 0x54, 0x75, 0xe7, 0x66, 0xde, 0x86, 0x49, 0x58, 0xa6, 0x55, 0xdc, 0x49, 0xe8,
|
||||
0x3b, 0x3f, 0x47, 0xe8, 0xdb, 0xfa, 0x9f, 0xe5, 0x4c, 0xe4, 0x45, 0xbb, 0x50, 0x4e, 0xe2, 0x42,
|
||||
0x4d, 0x27, 0xf4, 0xd2, 0x68, 0x76, 0xfe, 0x65, 0xa2, 0xd9, 0xb3, 0x92, 0xe3, 0x1f, 0xa0, 0x7d,
|
||||
0x4c, 0x7b, 0xe3, 0xc9, 0x1c, 0x91, 0xfa, 0x31, 0x5c, 0xbe, 0x41, 0xe9, 0x39, 0xd1, 0xd6, 0x95,
|
||||
0x1b, 0xa5, 0x99, 0x77, 0x2c, 0xb2, 0x79, 0x38, 0x83, 0x69, 0x67, 0xa8, 0x32, 0x9a, 0xa4, 0x3c,
|
||||
0x4b, 0x93, 0xa0, 0x37, 0x6b, 0x74, 0x4c, 0xd2, 0xd6, 0x89, 0x0d, 0xfd, 0x1c, 0xb3, 0xa7, 0xb4,
|
||||
0x6c, 0xd5, 0x9e, 0x82, 0xa3, 0x85, 0x35, 0x18, 0x79, 0xca, 0x35, 0xb1, 0x7b, 0xdd, 0x98, 0xbc,
|
||||
0x04, 0x56, 0x9b, 0xbe, 0x04, 0xf6, 0x11, 0x40, 0x24, 0x51, 0xf2, 0xb7, 0xdc, 0xae, 0x32, 0xf5,
|
||||
0x1d, 0x77, 0x2e, 0x1b, 0x9b, 0xc9, 0x38, 0x64, 0x28, 0xb0, 0xff, 0x03, 0x71, 0xbe, 0x89, 0x96,
|
||||
0xb6, 0x49, 0x44, 0x27, 0xed, 0x49, 0xfd, 0xba, 0x3c, 0xad, 0x5f, 0xdf, 0x84, 0x52, 0xd4, 0x0d,
|
||||
0x86, 0x92, 0xee, 0x31, 0x5c, 0xbe, 0xbe, 0x6b, 0x6d, 0x44, 0xb2, 0x35, 0x2e, 0xc5, 0xf7, 0x50,
|
||||
0x03, 0x05, 0x21, 0xdd, 0x60, 0xa8, 0xd9, 0x71, 0x73, 0x4c, 0xc7, 0xdd, 0x1c, 0xd7, 0x71, 0x4d,
|
||||
0x07, 0xca, 0x26, 0x56, 0x3f, 0xc3, 0x75, 0xa7, 0x28, 0x5f, 0x3e, 0x13, 0xe5, 0x4b, 0xaa, 0x08,
|
||||
0x0b, 0xd9, 0x2a, 0xc2, 0x89, 0x4b, 0x4e, 0xa5, 0xa9, 0x4b, 0x4e, 0xd6, 0x67, 0x50, 0xa2, 0xbe,
|
||||
0xa2, 0x81, 0xa0, 0xa7, 0x59, 0xdb, 0x8f, 0x38, 0x28, 0x96, 0xe3, 0xd7, 0x81, 0x45, 0x92, 0x0c,
|
||||
0x0c, 0xd9, 0x16, 0x03, 0x49, 0x0a, 0x30, 0xcf, 0x1b, 0x70, 0x5d, 0xe3, 0x46, 0xe3, 0x6f, 0xc8,
|
||||
0xca, 0xf1, 0xdc, 0xe3, 0x50, 0x84, 0x17, 0xac, 0x68, 0x7d, 0x44, 0xa9, 0xdc, 0x58, 0xa0, 0xea,
|
||||
0xc9, 0xa5, 0x32, 0xad, 0x72, 0x1d, 0x19, 0xe2, 0x49, 0xa1, 0x33, 0xf0, 0xc6, 0xf7, 0xd1, 0x75,
|
||||
0x49, 0xe4, 0x5c, 0x50, 0x84, 0x66, 0x31, 0x7b, 0xca, 0xfe, 0x99, 0xed, 0x37, 0x6b, 0x23, 0x63,
|
||||
0xa6, 0x8d, 0x17, 0x1a, 0xe5, 0xe6, 0x2d, 0x34, 0xb2, 0x3e, 0x85, 0x15, 0x7b, 0x5c, 0x5f, 0xf3,
|
||||
0xf7, 0xa0, 0x12, 0x0c, 0xb3, 0x7c, 0x5e, 0x24, 0x97, 0x31, 0xba, 0xf5, 0xd3, 0x1c, 0x2c, 0xb6,
|
||||
0x7c, 0x25, 0x43, 0x5f, 0x78, 0x3b, 0x9e, 0xe8, 0xf1, 0x77, 0x63, 0x2d, 0x35, 0xdb, 0xb7, 0xce,
|
||||
0xe2, 0x8e, 0x2b, 0x2c, 0xcf, 0xc4, 0xa4, 0xf9, 0x0d, 0x58, 0x95, 0x8e, 0xab, 0x82, 0x50, 0x1b,
|
||||
0xa7, 0x71, 0x3d, 0xd8, 0x75, 0x60, 0x1a, 0xdc, 0xa6, 0x2d, 0xd1, 0xd1, 0xcb, 0xdc, 0x80, 0xeb,
|
||||
0x63, 0xd0, 0xd8, 0xf2, 0xcc, 0xf3, 0xdb, 0xd0, 0x48, 0x4f, 0x9a, 0xad, 0xc0, 0x57, 0x2d, 0xdf,
|
||||
0x91, 0xe7, 0x64, 0xe6, 0xb0, 0x82, 0xf5, 0x7b, 0x95, 0xd8, 0xc0, 0x7a, 0x62, 0xaa, 0xc5, 0xc2,
|
||||
0x20, 0x48, 0x6f, 0x14, 0x9a, 0x56, 0xe6, 0xe6, 0x6a, 0x7e, 0x8e, 0x9b, 0xab, 0x1f, 0xa5, 0xb7,
|
||||
0x0f, 0xf5, 0x41, 0xf1, 0x95, 0x99, 0xa7, 0x0f, 0x15, 0xb9, 0x18, 0x93, 0xba, 0x2d, 0x33, 0x57,
|
||||
0x11, 0xdf, 0x30, 0x7e, 0x54, 0x71, 0x1e, 0x3b, 0x54, 0x27, 0xdf, 0xdf, 0x9e, 0xac, 0x95, 0x9f,
|
||||
0xaf, 0xd8, 0x6c, 0xca, 0x54, 0x84, 0x97, 0x36, 0x15, 0x3f, 0x9e, 0x70, 0x59, 0xaa, 0x33, 0xa3,
|
||||
0x5a, 0x57, 0x5c, 0xe8, 0xfb, 0x18, 0x2a, 0x7d, 0x37, 0x52, 0x41, 0xa8, 0x2f, 0x99, 0x4e, 0x5f,
|
||||
0x8a, 0xc9, 0xcc, 0xd6, 0xae, 0x46, 0xa4, 0xca, 0xa0, 0x98, 0x8a, 0x7f, 0x17, 0x56, 0x69, 0xe2,
|
||||
0x8f, 0x52, 0x8b, 0x20, 0x6a, 0xd4, 0x67, 0x56, 0x64, 0x65, 0x58, 0x6d, 0x4c, 0x90, 0xd8, 0xd3,
|
||||
0x4c, 0x9a, 0x3d, 0x80, 0x74, 0x7d, 0xa6, 0xb4, 0xd8, 0x17, 0xb8, 0x64, 0x7a, 0x13, 0xca, 0xd1,
|
||||
0xe8, 0x38, 0x4d, 0x5e, 0x99, 0x56, 0xf3, 0x1c, 0x9a, 0x53, 0xd6, 0xc1, 0x91, 0x0c, 0x75, 0x77,
|
||||
0xaf, 0xbc, 0xe9, 0xfa, 0x51, 0x76, 0xe1, 0xb5, 0x70, 0xde, 0xbd, 0x64, 0xf5, 0x12, 0xce, 0x19,
|
||||
0x09, 0x68, 0xbe, 0x0d, 0xf5, 0xcc, 0xa4, 0xa2, 0x66, 0x1e, 0xf9, 0x4e, 0x10, 0x07, 0x65, 0xf1,
|
||||
0x59, 0x5f, 0x11, 0x72, 0xe2, 0xb0, 0x2c, 0x3d, 0x37, 0x6d, 0x60, 0x93, 0x13, 0x78, 0x85, 0x5b,
|
||||
0xfb, 0x15, 0x58, 0xca, 0x98, 0x6b, 0x49, 0xf8, 0x6b, 0x1c, 0x68, 0x9d, 0xc1, 0x2b, 0x19, 0x76,
|
||||
0x47, 0x32, 0x1c, 0xb8, 0x11, 0x1e, 0x24, 0xda, 0x5d, 0xa3, 0xc8, 0x84, 0x23, 0x7d, 0xe5, 0xaa,
|
||||
0x58, 0x83, 0x26, 0x6d, 0xfe, 0xeb, 0x50, 0x1a, 0xca, 0x70, 0x10, 0x19, 0x2d, 0x3a, 0x29, 0x41,
|
||||
0x33, 0xd9, 0x46, 0xb6, 0xa6, 0xb1, 0xfe, 0x61, 0x0e, 0xaa, 0xfb, 0x52, 0x09, 0xb4, 0x1d, 0xf8,
|
||||
0xfe, 0xc4, 0x57, 0xa6, 0x13, 0xae, 0x31, 0xea, 0x9a, 0x71, 0x20, 0xd7, 0x5a, 0x06, 0xdf, 0xb4,
|
||||
0x77, 0x17, 0xd2, 0x8e, 0x35, 0x37, 0xa0, 0x62, 0xc0, 0xcd, 0x77, 0x61, 0x65, 0x02, 0x93, 0xe6,
|
||||
0x45, 0xdb, 0xed, 0xed, 0x8b, 0x41, 0x5c, 0x9b, 0xb3, 0x68, 0x8f, 0x03, 0x37, 0x6a, 0x50, 0x19,
|
||||
0x6a, 0x02, 0xeb, 0x5f, 0xdf, 0xa0, 0x7a, 0x11, 0xf7, 0x04, 0x1d, 0xe9, 0x59, 0x27, 0xeb, 0x1d,
|
||||
0x00, 0x3a, 0x9a, 0x75, 0x55, 0x81, 0x0e, 0x49, 0x66, 0x20, 0xfc, 0x83, 0x24, 0x64, 0x5d, 0x9c,
|
||||
0x69, 0x54, 0x65, 0x99, 0x4f, 0xc6, 0xad, 0x1b, 0x50, 0x71, 0xa3, 0x3d, 0x3c, 0xda, 0x4c, 0xad,
|
||||
0x4d, 0xdc, 0xe4, 0xdf, 0x82, 0xb2, 0x3b, 0x18, 0x06, 0xa1, 0x32, 0x31, 0xed, 0x2b, 0xb9, 0xb6,
|
||||
0x08, 0x73, 0x77, 0xc1, 0x36, 0x34, 0x48, 0x2d, 0xcf, 0x89, 0xba, 0xfa, 0x62, 0xea, 0xed, 0xf3,
|
||||
0x98, 0x5a, 0xd3, 0xf0, 0xef, 0xc0, 0x52, 0x4f, 0x57, 0xbf, 0x69, 0xc6, 0x46, 0x89, 0x7c, 0xfd,
|
||||
0x2a, 0x26, 0x8f, 0xb2, 0x04, 0xbb, 0x0b, 0xf6, 0x38, 0x07, 0x64, 0x89, 0x06, 0xbc, 0x8c, 0x54,
|
||||
0x27, 0xf8, 0x24, 0x70, 0x7d, 0x72, 0x38, 0x5f, 0xc0, 0xd2, 0xce, 0x12, 0x20, 0xcb, 0x31, 0x0e,
|
||||
0xfc, 0x1d, 0xb4, 0x78, 0x22, 0x65, 0xee, 0xf9, 0xde, 0xbd, 0x8a, 0x53, 0x47, 0x46, 0xe6, 0x86,
|
||||
0x6e, 0xa4, 0xf8, 0x39, 0x34, 0x33, 0x9b, 0xc4, 0x7c, 0x64, 0x7d, 0x38, 0x0c, 0x03, 0xf4, 0x5a,
|
||||
0x97, 0x88, 0xdb, 0x3b, 0x57, 0x71, 0x3b, 0xba, 0x94, 0x7a, 0x77, 0xc1, 0xbe, 0x82, 0x37, 0xef,
|
||||
0xa0, 0xd7, 0x66, 0x86, 0xb0, 0x27, 0xc5, 0x59, 0x7c, 0x4b, 0xf8, 0xfe, 0x5c, 0xb3, 0x40, 0x14,
|
||||
0xbb, 0x0b, 0xf6, 0x04, 0x0f, 0xfe, 0x9b, 0xb0, 0x3a, 0xf6, 0x4d, 0xba, 0x51, 0xa8, 0xef, 0x10,
|
||||
0x7f, 0x73, 0xee, 0x61, 0x20, 0xd1, 0xee, 0x82, 0x3d, 0xcd, 0x89, 0x8f, 0xe0, 0x4b, 0xd3, 0x43,
|
||||
0xda, 0x92, 0x5d, 0xcf, 0xf5, 0xa5, 0xb9, 0x6e, 0xfc, 0xf6, 0xcb, 0xcd, 0x96, 0x21, 0xde, 0x5d,
|
||||
0xb0, 0x2f, 0xe7, 0xcc, 0xff, 0x12, 0xdc, 0x1e, 0xce, 0x54, 0x31, 0x5a, 0x75, 0x99, 0xdb, 0xca,
|
||||
0xef, 0xcd, 0xf9, 0xe5, 0x29, 0xfa, 0xdd, 0x05, 0xfb, 0x4a, 0xfe, 0x68, 0x3b, 0x93, 0x77, 0x6c,
|
||||
0x8a, 0x74, 0x75, 0x83, 0xdf, 0x86, 0x9a, 0xe8, 0x7a, 0xbb, 0x52, 0x38, 0x49, 0xf4, 0x3c, 0x05,
|
||||
0x34, 0xff, 0x6b, 0x0e, 0xca, 0x46, 0xde, 0x6f, 0x27, 0x09, 0xf6, 0x44, 0x75, 0xa7, 0x00, 0xfe,
|
||||
0x21, 0xd4, 0x64, 0x18, 0x06, 0xe1, 0x66, 0xe0, 0xc4, 0x15, 0x82, 0x93, 0xa1, 0x5d, 0xcd, 0x67,
|
||||
0x6d, 0x3b, 0x46, 0xb3, 0x53, 0x0a, 0xfe, 0x01, 0x80, 0xde, 0xe7, 0x9d, 0xf4, 0xae, 0x45, 0x73,
|
||||
0x36, 0xbd, 0x4e, 0xb1, 0xa4, 0xd8, 0x69, 0x60, 0x2c, 0xce, 0x6f, 0xc4, 0xcd, 0xc4, 0xe1, 0x2c,
|
||||
0x65, 0x1c, 0xce, 0xdb, 0x26, 0x46, 0x70, 0x80, 0x2f, 0xcc, 0x8d, 0xa3, 0x04, 0xd0, 0xfc, 0x57,
|
||||
0x39, 0x28, 0x6b, 0xe5, 0xc1, 0xb7, 0xa7, 0x47, 0xf4, 0xb5, 0x17, 0xeb, 0x9c, 0xb5, 0xc9, 0x91,
|
||||
0x7d, 0x0b, 0x40, 0xeb, 0xa0, 0xcc, 0xc8, 0x6e, 0x4f, 0xf0, 0x31, 0xa4, 0x71, 0x99, 0x68, 0x8a,
|
||||
0x6f, 0x3d, 0xd4, 0xb7, 0x62, 0x28, 0x0e, 0xfb, 0x78, 0x6f, 0x8f, 0x2d, 0xf0, 0x55, 0x58, 0x7a,
|
||||
0x7c, 0xf0, 0xe9, 0xc1, 0xe1, 0xd3, 0x83, 0x67, 0xdb, 0xb6, 0x7d, 0x68, 0xeb, 0x70, 0xec, 0xc6,
|
||||
0xfa, 0xd6, 0xb3, 0xd6, 0xc1, 0xd1, 0xe3, 0x0e, 0xcb, 0x37, 0x7f, 0x96, 0x83, 0xa5, 0x31, 0xdd,
|
||||
0xf5, 0x7f, 0x77, 0xe9, 0x32, 0xd3, 0x5f, 0x98, 0x3d, 0xfd, 0xc5, 0xcb, 0xa6, 0xbf, 0x34, 0x39,
|
||||
0xfd, 0xff, 0x28, 0x07, 0x4b, 0x63, 0x3a, 0x32, 0xcb, 0x3d, 0x37, 0xce, 0x3d, 0x7b, 0xd2, 0xe7,
|
||||
0x27, 0x4e, 0x7a, 0x0b, 0x16, 0xe3, 0xe7, 0x83, 0x34, 0xe2, 0x30, 0x06, 0xcb, 0xe2, 0x50, 0x59,
|
||||
0x7a, 0x71, 0x1c, 0x87, 0x4a, 0xd3, 0xaf, 0xee, 0x2d, 0x5d, 0xc3, 0x8b, 0xe8, 0x96, 0x72, 0xf3,
|
||||
0x72, 0x0d, 0x7a, 0xc5, 0x10, 0x1e, 0x41, 0x7d, 0x98, 0x6e, 0xd3, 0x97, 0x33, 0x4b, 0xb2, 0x94,
|
||||
0x2f, 0xe8, 0xe7, 0x4f, 0x72, 0xb0, 0x3c, 0xae, 0x73, 0xff, 0xbf, 0x9e, 0xd6, 0x3f, 0xcc, 0xc1,
|
||||
0xea, 0x94, 0x26, 0xbf, 0xd2, 0xb0, 0x9b, 0xec, 0x57, 0x7e, 0x8e, 0x7e, 0x15, 0x66, 0xf4, 0xeb,
|
||||
0x72, 0x4d, 0x72, 0x75, 0x8f, 0xdb, 0xf0, 0xa5, 0x4b, 0xcf, 0x84, 0x2b, 0xa6, 0x7a, 0x8c, 0x69,
|
||||
0x61, 0x92, 0xe9, 0xdf, 0xcf, 0xc1, 0xed, 0xab, 0xf4, 0xfd, 0xff, 0x73, 0xb9, 0x9a, 0xec, 0xa1,
|
||||
0xf5, 0x6e, 0x92, 0x28, 0xaf, 0x43, 0xc5, 0xfc, 0x89, 0x8f, 0xa9, 0x4d, 0xee, 0x07, 0xcf, 0x7d,
|
||||
0x1d, 0x65, 0xb6, 0xa5, 0x30, 0xf7, 0xa3, 0x6d, 0x39, 0xf4, 0x5c, 0x4a, 0x4c, 0xde, 0x02, 0x58,
|
||||
0x27, 0xbf, 0x2e, 0xbe, 0xae, 0xb0, 0xb9, 0x77, 0xd8, 0xde, 0x66, 0x0b, 0x59, 0x23, 0xf6, 0xb3,
|
||||
0x58, 0x11, 0x5b, 0x47, 0x50, 0x4e, 0x0b, 0xd9, 0xf7, 0x45, 0x78, 0xea, 0xe8, 0xf4, 0xdf, 0x22,
|
||||
0x54, 0x8f, 0x8c, 0x0b, 0xa5, 0x3f, 0xf5, 0x49, 0xfb, 0xf0, 0x40, 0x07, 0xb4, 0xb7, 0x0e, 0x3b,
|
||||
0xba, 0x1c, 0xbe, 0xfd, 0xe4, 0x91, 0xce, 0x43, 0x3d, 0xb2, 0xd7, 0x8f, 0x76, 0x9f, 0x11, 0x46,
|
||||
0xc9, 0xfa, 0x59, 0x3e, 0x3e, 0xd5, 0x2c, 0xdb, 0x24, 0x16, 0x01, 0xca, 0xa8, 0xcd, 0x03, 0xc3,
|
||||
0x38, 0xf9, 0x0c, 0x55, 0xc8, 0x6e, 0x9f, 0xeb, 0x38, 0x04, 0xcb, 0xf3, 0x32, 0xe4, 0x8f, 0x8e,
|
||||
0x75, 0x71, 0xec, 0xae, 0x1a, 0x78, 0xfa, 0xfe, 0x5a, 0xe7, 0x5c, 0xb1, 0x12, 0x3e, 0x6c, 0x46,
|
||||
0x67, 0xac, 0x6c, 0xfd, 0xc7, 0x1c, 0xd4, 0x12, 0x55, 0xf9, 0x32, 0xaa, 0x9b, 0x73, 0x58, 0x6e,
|
||||
0x1d, 0x74, 0xb6, 0xed, 0x83, 0xf5, 0x3d, 0x83, 0x52, 0xe0, 0x0d, 0xb8, 0x7e, 0x70, 0xf8, 0xec,
|
||||
0x70, 0xe3, 0x93, 0xed, 0xcd, 0x4e, 0xfb, 0x59, 0xe7, 0xf0, 0x59, 0x6b, 0xff, 0xe8, 0xd0, 0xee,
|
||||
0xb0, 0x12, 0xbf, 0x09, 0x5c, 0x3f, 0x3f, 0x6b, 0xb5, 0x9f, 0x6d, 0xae, 0x1f, 0x6c, 0x6e, 0xef,
|
||||
0x6d, 0x6f, 0xb1, 0x32, 0xff, 0x1a, 0xfc, 0xda, 0x5e, 0x6b, 0xbf, 0xd5, 0x79, 0x76, 0xb8, 0xf3,
|
||||
0xcc, 0x3e, 0x7c, 0xda, 0x7e, 0x76, 0x68, 0x3f, 0xb3, 0xb7, 0xf7, 0xd6, 0x3b, 0xad, 0xc3, 0x83,
|
||||
0xf6, 0xb3, 0xed, 0xef, 0x6e, 0x6e, 0x6f, 0x6f, 0x6d, 0x6f, 0xb1, 0x0a, 0xbf, 0x06, 0x2b, 0x3b,
|
||||
0xad, 0xbd, 0xed, 0x67, 0x7b, 0x87, 0xeb, 0x5b, 0xe6, 0x7b, 0x55, 0x7e, 0x1b, 0x1a, 0xad, 0x83,
|
||||
0xf6, 0xe3, 0x9d, 0x9d, 0xd6, 0x66, 0x6b, 0xfb, 0xa0, 0xf3, 0xec, 0x68, 0xdb, 0xde, 0x6f, 0xb5,
|
||||
0xdb, 0x48, 0xcb, 0x6a, 0xd6, 0xb7, 0xa1, 0xdc, 0xf2, 0xcf, 0x5c, 0x45, 0xe2, 0x67, 0xd6, 0xca,
|
||||
0x38, 0x24, 0x71, 0x93, 0xa4, 0xc6, 0xed, 0xf9, 0x74, 0x6d, 0x99, 0x84, 0x6f, 0xd1, 0x4e, 0x01,
|
||||
0xd6, 0x3f, 0xc9, 0xc3, 0x92, 0x66, 0x11, 0x3b, 0x38, 0xf7, 0x60, 0xc5, 0x44, 0x0a, 0x5b, 0xe3,
|
||||
0x3b, 0x7c, 0x12, 0x4c, 0x7f, 0xeb, 0xa3, 0x41, 0x99, 0x7d, 0x9e, 0x05, 0x51, 0x66, 0x89, 0x98,
|
||||
0xa3, 0xa3, 0xa4, 0x73, 0x6a, 0x29, 0xe0, 0x8b, 0x6e, 0x70, 0x54, 0x1e, 0x1a, 0xb1, 0x1b, 0xf8,
|
||||
0x9b, 0xc9, 0x65, 0x81, 0x31, 0x18, 0xff, 0x0c, 0x6e, 0x25, 0xed, 0x6d, 0xbf, 0x1b, 0x5e, 0x0c,
|
||||
0x93, 0x7f, 0xdf, 0xaa, 0xcc, 0xf4, 0xb8, 0x77, 0x5c, 0x4f, 0x8e, 0x21, 0xda, 0x97, 0x31, 0xb0,
|
||||
0xfe, 0x38, 0x97, 0x71, 0x0b, 0xb5, 0xdb, 0x77, 0xa5, 0x42, 0x9c, 0x95, 0xa2, 0x40, 0xc7, 0xcc,
|
||||
0x74, 0xdf, 0x9c, 0xd3, 0xa6, 0xc9, 0x8f, 0x80, 0xbb, 0xd3, 0x9d, 0x2e, 0xce, 0xd9, 0xe9, 0x19,
|
||||
0xb4, 0x93, 0x11, 0xe6, 0xd2, 0x74, 0x84, 0xf9, 0x0e, 0x40, 0xcf, 0x0b, 0x8e, 0x85, 0x97, 0xb1,
|
||||
0xc3, 0x32, 0x10, 0xcb, 0x83, 0x6a, 0xfc, 0x1f, 0x5f, 0xfc, 0x26, 0x94, 0xe9, 0x5f, 0xbe, 0x92,
|
||||
0x78, 0x9b, 0x6e, 0xf1, 0x5d, 0x58, 0x96, 0xe3, 0x7d, 0xce, 0xcf, 0xd9, 0xe7, 0x09, 0x3a, 0xeb,
|
||||
0x7d, 0x58, 0x9d, 0x42, 0xc2, 0x49, 0x1c, 0x0a, 0x95, 0xdc, 0x10, 0xc6, 0xe7, 0xe9, 0xfc, 0xad,
|
||||
0xf5, 0xef, 0xf3, 0xb0, 0xb8, 0x2f, 0x7c, 0xf7, 0x44, 0x46, 0x2a, 0xee, 0x6d, 0xd4, 0xed, 0xcb,
|
||||
0x81, 0x88, 0x7b, 0xab, 0x5b, 0xc6, 0x09, 0xcf, 0x4f, 0x55, 0xa6, 0x65, 0xb3, 0x21, 0x37, 0xa1,
|
||||
0x2c, 0x46, 0xaa, 0x9f, 0xd4, 0x46, 0x9b, 0x16, 0xae, 0x9d, 0xe7, 0x76, 0xa5, 0x1f, 0xc5, 0xb2,
|
||||
0x19, 0x37, 0xd3, 0x0a, 0x8e, 0xf2, 0x15, 0x15, 0x1c, 0x95, 0xe9, 0xf9, 0xbf, 0x0b, 0xf5, 0xa8,
|
||||
0x1b, 0x4a, 0xe9, 0x47, 0xfd, 0x40, 0xc5, 0xff, 0x0f, 0x97, 0x05, 0x51, 0xe5, 0x52, 0xf0, 0xdc,
|
||||
0xc7, 0x1d, 0xba, 0xe7, 0xfa, 0xa7, 0xa6, 0x7c, 0x67, 0x0c, 0x86, 0x32, 0x48, 0x21, 0x08, 0xf7,
|
||||
0x07, 0x92, 0xdc, 0xdf, 0x92, 0x9d, 0xb4, 0x29, 0xc8, 0x20, 0x94, 0xec, 0x05, 0xa1, 0x2b, 0x75,
|
||||
0xa4, 0xad, 0x66, 0x67, 0x20, 0x48, 0xeb, 0x09, 0xbf, 0x37, 0x12, 0x3d, 0x69, 0xf2, 0xa1, 0x49,
|
||||
0xdb, 0xfa, 0x6f, 0x25, 0x80, 0x7d, 0x39, 0x38, 0x96, 0x61, 0xd4, 0x77, 0x87, 0x94, 0x09, 0x70,
|
||||
0x4d, 0x11, 0xe9, 0x92, 0x4d, 0xcf, 0xfc, 0xbd, 0xb1, 0x62, 0xed, 0xe9, 0xdc, 0x5d, 0x4a, 0x3e,
|
||||
0x19, 0xa1, 0xc0, 0xc9, 0x11, 0x4a, 0x9a, 0xe2, 0x19, 0x9a, 0xff, 0xa2, 0x9d, 0x05, 0x51, 0x5d,
|
||||
0x93, 0x50, 0x72, 0xdb, 0x77, 0x74, 0x04, 0xa4, 0x68, 0x27, 0x6d, 0xba, 0x92, 0x11, 0xad, 0x8f,
|
||||
0x54, 0x60, 0x4b, 0x5f, 0x3e, 0x4f, 0xee, 0x13, 0xa5, 0x20, 0xbe, 0x0f, 0x4b, 0x43, 0x71, 0x31,
|
||||
0x90, 0xbe, 0xda, 0x97, 0xaa, 0x1f, 0x38, 0xa6, 0xd2, 0xe5, 0x6b, 0x97, 0x77, 0xf0, 0x28, 0x8b,
|
||||
0x6e, 0x8f, 0x53, 0xa3, 0x4c, 0xf8, 0x11, 0xed, 0x12, 0xbd, 0x8c, 0xa6, 0xc5, 0x37, 0x00, 0xf4,
|
||||
0x13, 0x39, 0x16, 0xd5, 0xd9, 0x81, 0x1a, 0x31, 0x90, 0x91, 0x0c, 0xcf, 0x5c, 0xad, 0xc7, 0xb4,
|
||||
0xeb, 0x94, 0x52, 0xa1, 0xd6, 0x1b, 0x45, 0x32, 0xdc, 0x1e, 0x08, 0xd7, 0x33, 0x0b, 0x9c, 0x02,
|
||||
0xf8, 0x5b, 0x70, 0x23, 0x1a, 0x1d, 0xa3, 0xcc, 0x1c, 0xcb, 0x4e, 0x70, 0x20, 0x9f, 0x47, 0x9e,
|
||||
0x54, 0x4a, 0x86, 0x26, 0xb5, 0x3e, 0xfb, 0xa5, 0xd5, 0x4b, 0xac, 0x02, 0xfa, 0x13, 0x03, 0x7c,
|
||||
0x4a, 0x4b, 0x76, 0x12, 0x90, 0xa9, 0x67, 0x62, 0x39, 0xce, 0x60, 0x51, 0x83, 0x4c, 0xb9, 0x53,
|
||||
0x9e, 0x7f, 0x15, 0xbe, 0x3c, 0x86, 0x64, 0xeb, 0x3c, 0x69, 0xb4, 0xe3, 0xfa, 0xc2, 0x73, 0x7f,
|
||||
0xa0, 0x33, 0xd2, 0x05, 0x6b, 0x08, 0x4b, 0x63, 0x13, 0x87, 0xc7, 0xbc, 0x7e, 0x32, 0x05, 0x20,
|
||||
0x0c, 0x16, 0x75, 0xbb, 0xad, 0x42, 0x97, 0x12, 0x00, 0x09, 0x64, 0x13, 0xf7, 0x79, 0xc0, 0xf2,
|
||||
0xfc, 0x3a, 0x30, 0x0d, 0x69, 0xf9, 0x62, 0x38, 0x5c, 0x1f, 0x0e, 0x3d, 0xc9, 0x0a, 0x74, 0xef,
|
||||
0x2e, 0x85, 0xea, 0x92, 0x6d, 0x56, 0xb4, 0xbe, 0x0b, 0xb7, 0x68, 0x66, 0x9e, 0xc8, 0x30, 0xf1,
|
||||
0xfb, 0xcc, 0x58, 0x6f, 0xc0, 0xaa, 0x7e, 0x3a, 0x08, 0x94, 0x7e, 0x4d, 0xb6, 0x10, 0x87, 0x65,
|
||||
0x0d, 0x46, 0x53, 0xa0, 0x2d, 0x7d, 0xa5, 0xeb, 0x50, 0x34, 0x2c, 0xc1, 0xcb, 0x5b, 0x3f, 0x2d,
|
||||
0x03, 0x4f, 0x05, 0xa2, 0xe3, 0xca, 0x70, 0x4b, 0x28, 0x91, 0x09, 0xdc, 0x2d, 0x5d, 0x9a, 0x7a,
|
||||
0x7e, 0x71, 0xb5, 0xd6, 0x4d, 0x28, 0xbb, 0x11, 0x7a, 0x2a, 0xa6, 0x3a, 0xd2, 0xb4, 0xf8, 0x1e,
|
||||
0xc0, 0x50, 0x86, 0x6e, 0xe0, 0x90, 0x04, 0x95, 0x66, 0xd6, 0xcc, 0x4f, 0x77, 0x6a, 0xed, 0x28,
|
||||
0xa1, 0xb1, 0x33, 0xf4, 0xd8, 0x0f, 0xdd, 0xd2, 0x89, 0xdc, 0x32, 0x75, 0x3a, 0x0b, 0xe2, 0xaf,
|
||||
0xc3, 0xb5, 0x61, 0xe8, 0x76, 0xa5, 0x5e, 0x8e, 0xc7, 0x91, 0xb3, 0x49, 0xff, 0xe0, 0x55, 0x21,
|
||||
0xcc, 0x59, 0xaf, 0x50, 0x02, 0x85, 0x4f, 0xf6, 0x7b, 0x44, 0xa9, 0x4b, 0x73, 0xef, 0x53, 0x57,
|
||||
0x1b, 0x2e, 0xd9, 0xb3, 0x5f, 0xf2, 0xfb, 0xc0, 0xcc, 0x8b, 0x7d, 0xd7, 0xdf, 0x93, 0x7e, 0x4f,
|
||||
0xf5, 0x49, 0xb8, 0x97, 0xec, 0x29, 0x38, 0x69, 0x30, 0xfd, 0x07, 0x2b, 0x3a, 0xad, 0x51, 0xb3,
|
||||
0x93, 0xb6, 0xbe, 0x4b, 0xec, 0x05, 0x61, 0x5b, 0x85, 0xa6, 0x10, 0x32, 0x69, 0xa3, 0xcd, 0x12,
|
||||
0x51, 0x5f, 0x8f, 0xc2, 0xc0, 0x19, 0x51, 0xd0, 0x5d, 0x2b, 0xb1, 0x49, 0x70, 0x8a, 0xb9, 0x2f,
|
||||
0x7c, 0x53, 0x32, 0xb7, 0x94, 0xc5, 0x4c, 0xc0, 0xe4, 0xa2, 0x04, 0x51, 0xca, 0x70, 0xc5, 0xb8,
|
||||
0x28, 0x19, 0x98, 0xc1, 0x49, 0x59, 0xb1, 0x04, 0x27, 0xe5, 0x43, 0xe3, 0x77, 0xc2, 0xc0, 0x75,
|
||||
0x52, 0x5e, 0xab, 0xba, 0xa0, 0x71, 0x12, 0x9e, 0xc1, 0x4d, 0x79, 0xf2, 0x31, 0xdc, 0x04, 0x6e,
|
||||
0xfd, 0x30, 0x07, 0x90, 0x2e, 0x3e, 0x8a, 0x7c, 0xda, 0x4a, 0xb7, 0xf8, 0x2d, 0xb8, 0x96, 0x05,
|
||||
0x53, 0x25, 0x3e, 0xe5, 0x3f, 0x39, 0x2c, 0xa7, 0x2f, 0xb6, 0xc4, 0x45, 0xc4, 0xf2, 0xba, 0xb2,
|
||||
0x31, 0x86, 0x3d, 0x95, 0x92, 0x6a, 0xc8, 0xae, 0x03, 0x4b, 0x81, 0x74, 0x6b, 0x2a, 0x62, 0xc5,
|
||||
0x71, 0xd4, 0xef, 0x49, 0x11, 0x46, 0xac, 0x64, 0xed, 0x42, 0x59, 0xe7, 0x5e, 0x66, 0x64, 0x4d,
|
||||
0x5f, 0xae, 0x04, 0xe2, 0xaf, 0xe7, 0x00, 0xb6, 0x74, 0xf1, 0x2a, 0x9e, 0xe2, 0xf3, 0xd4, 0x91,
|
||||
0xeb, 0xbf, 0xed, 0xa0, 0xb2, 0xde, 0x42, 0xf2, 0xb7, 0x1d, 0xd8, 0x44, 0xc9, 0x11, 0x71, 0xd1,
|
||||
0x90, 0xde, 0x73, 0x49, 0x5b, 0x1f, 0x20, 0x9b, 0x81, 0xef, 0xcb, 0x2e, 0x1e, 0x3f, 0xc9, 0x01,
|
||||
0x92, 0x80, 0xee, 0xff, 0xb8, 0x00, 0xcb, 0xe3, 0xf9, 0x3b, 0xaa, 0xf3, 0xd7, 0xb9, 0xe3, 0x43,
|
||||
0xcf, 0xc9, 0x94, 0x3e, 0x32, 0xbe, 0x02, 0x75, 0x63, 0x11, 0x12, 0x60, 0x95, 0x3c, 0x93, 0x60,
|
||||
0x20, 0xd9, 0xdd, 0xec, 0x7f, 0x52, 0xbd, 0x8e, 0x0e, 0x8e, 0xbe, 0x3a, 0xc1, 0x86, 0xbc, 0x66,
|
||||
0xfe, 0x9d, 0xe3, 0xb7, 0xf2, 0x7c, 0x29, 0x53, 0x80, 0xf7, 0x63, 0x54, 0x87, 0x2b, 0x1b, 0x23,
|
||||
0xdf, 0xf1, 0xa4, 0x93, 0x40, 0xff, 0x41, 0x16, 0x9a, 0x94, 0xd3, 0xfd, 0x16, 0x7a, 0x55, 0xb5,
|
||||
0xf6, 0xe8, 0xd8, 0x94, 0xd2, 0xfd, 0xe5, 0x22, 0xbf, 0x09, 0xab, 0x06, 0x2b, 0xad, 0x9b, 0x61,
|
||||
0x7f, 0x05, 0x17, 0x6e, 0x79, 0x5d, 0xeb, 0x14, 0xd3, 0x51, 0xf6, 0x57, 0x8b, 0xd8, 0x05, 0xba,
|
||||
0x93, 0xf7, 0xdb, 0xc4, 0x27, 0x29, 0x2a, 0x66, 0xbf, 0x53, 0xe4, 0x2b, 0x00, 0xed, 0x4e, 0xf2,
|
||||
0xa1, 0xdf, 0x2b, 0xf2, 0x3a, 0x94, 0xdb, 0x1d, 0xe2, 0xf6, 0xc3, 0x22, 0xbf, 0x01, 0x2c, 0x7d,
|
||||
0x6b, 0x2a, 0x85, 0x7e, 0x5f, 0x77, 0x26, 0x29, 0xfd, 0xf9, 0x83, 0x22, 0x8e, 0x2b, 0x36, 0x98,
|
||||
0xd9, 0xdf, 0x2c, 0x72, 0x06, 0xf5, 0x8c, 0xbf, 0xcb, 0xfe, 0x56, 0x91, 0x73, 0x58, 0xda, 0x47,
|
||||
0x37, 0xd7, 0xef, 0x99, 0x11, 0xfc, 0x2e, 0x7d, 0x79, 0x27, 0xa9, 0x8b, 0x66, 0x3f, 0x2a, 0xf2,
|
||||
0x5b, 0xc0, 0xb3, 0x31, 0x3e, 0xf3, 0xe2, 0x6f, 0x17, 0xef, 0xff, 0x94, 0x42, 0x2a, 0xd9, 0xac,
|
||||
0x3b, 0xfa, 0x89, 0x5e, 0xe0, 0xf7, 0x94, 0xfe, 0xeb, 0xae, 0x25, 0xa8, 0x45, 0xfd, 0x20, 0x54,
|
||||
0xd4, 0xa4, 0x9b, 0x21, 0x3e, 0x5d, 0xef, 0xd3, 0xe5, 0x92, 0xda, 0x12, 0xd1, 0x2e, 0xaa, 0x12,
|
||||
0x3d, 0x56, 0x4f, 0x8a, 0x98, 0x8a, 0x49, 0xa1, 0x15, 0x5d, 0x33, 0x8c, 0xaf, 0x71, 0xb1, 0x32,
|
||||
0xa2, 0x8e, 0x42, 0x4f, 0x17, 0x5c, 0x49, 0x3c, 0x85, 0xf4, 0x7f, 0xf4, 0x0c, 0xfb, 0x78, 0xd8,
|
||||
0xd5, 0x34, 0x34, 0xf8, 0xdc, 0xd5, 0x17, 0x84, 0x4c, 0x8d, 0x83, 0x83, 0xfd, 0x48, 0xd2, 0x78,
|
||||
0x4c, 0xde, 0xff, 0x83, 0x1c, 0x2c, 0xc6, 0x97, 0xeb, 0xdc, 0x9e, 0xeb, 0xeb, 0x92, 0xad, 0xf8,
|
||||
0x0f, 0xd1, 0xba, 0x9e, 0x3b, 0x8c, 0xff, 0x60, 0x68, 0x05, 0xea, 0x4e, 0x28, 0x7a, 0xeb, 0xbe,
|
||||
0xb3, 0x15, 0x06, 0x43, 0xdd, 0x6d, 0x1d, 0x74, 0xd5, 0xa5, 0x62, 0xcf, 0xe5, 0x31, 0xa2, 0x0f,
|
||||
0x65, 0xc8, 0x8a, 0x54, 0x3f, 0xd1, 0x17, 0xa1, 0xeb, 0xf7, 0xd0, 0x55, 0xf6, 0x23, 0x5d, 0x32,
|
||||
0x56, 0x87, 0xca, 0x28, 0x92, 0x5d, 0x11, 0x49, 0x56, 0xc6, 0xc6, 0xf1, 0xc8, 0xf5, 0x94, 0xeb,
|
||||
0xeb, 0xff, 0xf5, 0x49, 0x6a, 0xc2, 0xaa, 0xf7, 0xff, 0x65, 0x0e, 0xea, 0xb4, 0x78, 0x69, 0x34,
|
||||
0x21, 0x55, 0x27, 0x75, 0xa8, 0xec, 0x25, 0xff, 0xeb, 0x52, 0x86, 0xfc, 0xe1, 0xa9, 0x8e, 0x26,
|
||||
0x98, 0xc5, 0xd3, 0xf7, 0x6b, 0xf4, 0x5f, 0xbc, 0x14, 0xf9, 0x97, 0xe0, 0x86, 0x2d, 0x07, 0x81,
|
||||
0x92, 0x4f, 0x85, 0xab, 0xb2, 0xe5, 0xd2, 0x25, 0xb4, 0x3c, 0xf4, 0xab, 0xb8, 0x3e, 0xba, 0x4c,
|
||||
0x96, 0x07, 0x7e, 0x36, 0x86, 0x54, 0x70, 0xd0, 0x04, 0x31, 0xa6, 0x48, 0x35, 0x41, 0xf9, 0x24,
|
||||
0x70, 0x7d, 0xfc, 0x1a, 0x5d, 0xc8, 0x22, 0x08, 0x85, 0xa5, 0x10, 0x04, 0xf7, 0x0f, 0xe0, 0xe6,
|
||||
0xec, 0x60, 0x8a, 0xbe, 0xaa, 0x45, 0x7f, 0x26, 0x48, 0x05, 0xb4, 0x4f, 0x43, 0x57, 0x5f, 0xdb,
|
||||
0xa9, 0x41, 0xe9, 0xf0, 0xb9, 0x4f, 0xd2, 0xb0, 0x0a, 0x4b, 0x07, 0x41, 0x86, 0x86, 0x15, 0xee,
|
||||
0x77, 0xc7, 0xe2, 0x5f, 0xe9, 0xa4, 0xc4, 0x9d, 0x58, 0xc8, 0x14, 0x87, 0xe7, 0x74, 0x64, 0x85,
|
||||
0xfe, 0xd6, 0x59, 0x5f, 0x63, 0x35, 0x71, 0x27, 0x47, 0x5f, 0x63, 0x4d, 0xba, 0x59, 0xd4, 0x7f,
|
||||
0xf4, 0xe0, 0x77, 0xa5, 0x27, 0x1d, 0x56, 0xba, 0xff, 0x1e, 0xac, 0x98, 0xa1, 0x76, 0x65, 0x14,
|
||||
0xc5, 0xc5, 0xd5, 0x47, 0xa1, 0x7b, 0xa6, 0xaf, 0xca, 0x2e, 0x42, 0xf5, 0x48, 0x86, 0x51, 0xe0,
|
||||
0xd3, 0x35, 0x61, 0x80, 0x72, 0xbb, 0x2f, 0x42, 0xfc, 0xc6, 0xfd, 0x6f, 0x40, 0x8d, 0x8a, 0xad,
|
||||
0x3f, 0x75, 0x7d, 0x07, 0x47, 0xb2, 0x61, 0xea, 0x0b, 0x6b, 0x50, 0xda, 0x0c, 0xce, 0x68, 0x7c,
|
||||
0x55, 0xfd, 0x97, 0x66, 0x2c, 0x7f, 0xff, 0x63, 0xe0, 0xda, 0x8f, 0x73, 0xe4, 0xb9, 0xeb, 0xf7,
|
||||
0x92, 0xfb, 0x83, 0x40, 0x97, 0x81, 0x1d, 0x79, 0x4e, 0x66, 0x52, 0x1d, 0x2a, 0x71, 0x23, 0xbe,
|
||||
0x92, 0xbc, 0x13, 0x8c, 0x7c, 0xfc, 0xda, 0x13, 0xb8, 0xae, 0x65, 0x03, 0x3f, 0x4f, 0x97, 0x3a,
|
||||
0x2e, 0x35, 0x2e, 0x75, 0x45, 0xbc, 0x1a, 0x45, 0x09, 0x2e, 0xcb, 0xf1, 0x9b, 0xc0, 0x13, 0xc3,
|
||||
0x2c, 0x85, 0xe7, 0xef, 0x5b, 0x70, 0x6d, 0x86, 0x75, 0x4c, 0x3a, 0x53, 0xdb, 0x08, 0x6c, 0x61,
|
||||
0xe3, 0xfe, 0x1f, 0xfd, 0xe2, 0x4e, 0xee, 0xe7, 0xbf, 0xb8, 0x93, 0xfb, 0xcf, 0xbf, 0xb8, 0x93,
|
||||
0xfb, 0xe1, 0x2f, 0xef, 0x2c, 0xfc, 0xfc, 0x97, 0x77, 0x16, 0xfe, 0xc3, 0x2f, 0xef, 0x2c, 0x7c,
|
||||
0xc6, 0x26, 0xff, 0x12, 0xfd, 0xb8, 0x4c, 0xc7, 0xca, 0x9b, 0xff, 0x27, 0x00, 0x00, 0xff, 0xff,
|
||||
0x31, 0x0c, 0x9a, 0x8e, 0x2d, 0x5d, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func (m *SmartBlockSnapshotBase) Marshal() (dAtA []byte, err error) {
|
||||
|
|
|
@ -333,9 +333,9 @@ message Block {
|
|||
message Dataview {
|
||||
repeated string source = 1;
|
||||
repeated View views = 2;
|
||||
string activeView = 3; // do not generate changes for this field
|
||||
// deprecated
|
||||
repeated model.Relation relations = 4;
|
||||
string activeView = 3; // saved within a session
|
||||
repeated GroupOrder groupOrders = 12;
|
||||
repeated ObjectOrder objectOrders = 13;
|
||||
repeated anytype.model.RelationLink relationLinks = 5;
|
||||
|
|
|
@ -920,6 +920,54 @@ func (_c *MockSpace_Do_Call) RunAndReturn(run func(string, func(smartblock.Smart
|
|||
return _c
|
||||
}
|
||||
|
||||
// DoCtx provides a mock function with given fields: ctx, objectId, apply
|
||||
func (_m *MockSpace) DoCtx(ctx context.Context, objectId string, apply func(smartblock.SmartBlock) error) error {
|
||||
ret := _m.Called(ctx, objectId, apply)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DoCtx")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, func(smartblock.SmartBlock) error) error); ok {
|
||||
r0 = rf(ctx, objectId, apply)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockSpace_DoCtx_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DoCtx'
|
||||
type MockSpace_DoCtx_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DoCtx is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - objectId string
|
||||
// - apply func(smartblock.SmartBlock) error
|
||||
func (_e *MockSpace_Expecter) DoCtx(ctx interface{}, objectId interface{}, apply interface{}) *MockSpace_DoCtx_Call {
|
||||
return &MockSpace_DoCtx_Call{Call: _e.mock.On("DoCtx", ctx, objectId, apply)}
|
||||
}
|
||||
|
||||
func (_c *MockSpace_DoCtx_Call) Run(run func(ctx context.Context, objectId string, apply func(smartblock.SmartBlock) error)) *MockSpace_DoCtx_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(func(smartblock.SmartBlock) error))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockSpace_DoCtx_Call) Return(_a0 error) *MockSpace_DoCtx_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockSpace_DoCtx_Call) RunAndReturn(run func(context.Context, string, func(smartblock.SmartBlock) error) error) *MockSpace_DoCtx_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// DoLockedIfNotExists provides a mock function with given fields: objectID, proc
|
||||
func (_m *MockSpace) DoLockedIfNotExists(objectID string, proc func() error) error {
|
||||
ret := _m.Called(objectID, proc)
|
||||
|
|
|
@ -47,6 +47,7 @@ type Space interface {
|
|||
CommonSpace() commonspace.Space
|
||||
|
||||
Do(objectId string, apply func(sb smartblock.SmartBlock) error) error
|
||||
DoCtx(ctx context.Context, objectId string, apply func(sb smartblock.SmartBlock) error) error
|
||||
GetRelationIdByKey(ctx context.Context, key domain.RelationKey) (id string, err error)
|
||||
GetTypeIdByKey(ctx context.Context, key domain.TypeKey) (id string, err error)
|
||||
|
||||
|
@ -208,7 +209,11 @@ func (s *space) WaitMandatoryObjects(ctx context.Context) (err error) {
|
|||
}
|
||||
|
||||
func (s *space) Do(objectId string, apply func(sb smartblock.SmartBlock) error) error {
|
||||
sb, err := s.GetObject(context.Background(), objectId)
|
||||
return s.DoCtx(context.Background(), objectId, apply)
|
||||
}
|
||||
|
||||
func (s *space) DoCtx(ctx context.Context, objectId string, apply func(sb smartblock.SmartBlock) error) error {
|
||||
sb, err := s.GetObject(ctx, objectId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -27,7 +27,10 @@ func (s *service) initMarketplaceSpace(ctx context.Context) error {
|
|||
}
|
||||
|
||||
func (s *service) initTechSpace(ctx context.Context) (err error) {
|
||||
s.techSpace, err = s.factory.CreateAndSetTechSpace(ctx)
|
||||
if s.techSpace, err = s.factory.CreateAndSetTechSpace(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
close(s.techSpaceReady)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
7
space/internal/components/dependencies/queryablestore.go
Normal file
7
space/internal/components/dependencies/queryablestore.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package dependencies
|
||||
|
||||
import "github.com/anyproto/anytype-heart/pkg/lib/database"
|
||||
|
||||
type QueryableStore interface {
|
||||
Query(q database.Query) (records []database.Record, err error)
|
||||
}
|
12
space/internal/components/dependencies/spacewithctx.go
Normal file
12
space/internal/components/dependencies/spacewithctx.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package dependencies
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/smartblock"
|
||||
)
|
||||
|
||||
type SpaceWithCtx interface {
|
||||
DoCtx(ctx context.Context, objectId string, apply func(sb smartblock.SmartBlock) error) error
|
||||
Id() string
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
package readonlyfixer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/basic"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/smartblock"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/database"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
"github.com/anyproto/anytype-heart/space/internal/components/dependencies"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
)
|
||||
|
||||
const MName = "ReadonlyRelationsFixer"
|
||||
|
||||
// Migration ReadonlyRelationsFixer performs setting readOnlyValue relation to true for all relations with Status and Tag format
|
||||
// This migration was implemented to fix relations in accounts of users that are not able to modify its value (GO-2331)
|
||||
type Migration struct{}
|
||||
|
||||
func (Migration) Name() string {
|
||||
return MName
|
||||
}
|
||||
|
||||
func (Migration) Run(ctx context.Context, log logger.CtxLogger, store dependencies.QueryableStore, space dependencies.SpaceWithCtx) (toMigrate, migrated int, err error) {
|
||||
spaceId := space.Id()
|
||||
|
||||
relations, err := listReadonlyTagAndStatusRelations(store, spaceId)
|
||||
toMigrate = len(relations)
|
||||
|
||||
if err != nil {
|
||||
return toMigrate, 0, fmt.Errorf("failed to list all relations with tag and status format in space %s: %w", spaceId, err)
|
||||
}
|
||||
|
||||
if toMigrate != 0 {
|
||||
log.Debug(fmt.Sprintf("space %s contains %d relations of tag and status format with relationReadonlyValue=true", spaceId, toMigrate), zap.String("migration", MName))
|
||||
}
|
||||
|
||||
for _, r := range relations {
|
||||
var (
|
||||
name = pbtypes.GetString(r.Details, bundle.RelationKeyName.String())
|
||||
uk = pbtypes.GetString(r.Details, bundle.RelationKeyUniqueKey.String())
|
||||
)
|
||||
|
||||
format := model.RelationFormat_name[int32(pbtypes.GetInt64(r.Details, bundle.RelationKeyRelationFormat.String()))]
|
||||
log.Debug("setting relationReadonlyValue to FALSE for relation", zap.String("name", name), zap.String("uniqueKey", uk), zap.String("format", format), zap.String("migration", MName))
|
||||
|
||||
det := []*model.Detail{{
|
||||
Key: bundle.RelationKeyRelationReadonlyValue.String(),
|
||||
Value: pbtypes.Bool(false),
|
||||
}}
|
||||
e := space.DoCtx(ctx, pbtypes.GetString(r.Details, bundle.RelationKeyId.String()), func(sb smartblock.SmartBlock) error {
|
||||
if ds, ok := sb.(basic.DetailsSettable); ok {
|
||||
return ds.SetDetails(nil, det, false)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if e != nil {
|
||||
err = errors.Join(err, fmt.Errorf("failed to set readOnlyValue=true to relation %s in space %s: %w", uk, spaceId, e))
|
||||
} else {
|
||||
migrated++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func listReadonlyTagAndStatusRelations(store dependencies.QueryableStore, spaceId string) ([]database.Record, error) {
|
||||
return store.Query(database.Query{Filters: []*model.BlockContentDataviewFilter{
|
||||
{
|
||||
RelationKey: bundle.RelationKeyRelationFormat.String(),
|
||||
Condition: model.BlockContentDataviewFilter_In,
|
||||
Value: pbtypes.IntList(int(model.RelationFormat_status), int(model.RelationFormat_tag)),
|
||||
},
|
||||
{
|
||||
RelationKey: bundle.RelationKeySpaceId.String(),
|
||||
Condition: model.BlockContentDataviewFilter_Equal,
|
||||
Value: pbtypes.String(spaceId),
|
||||
},
|
||||
{
|
||||
RelationKey: bundle.RelationKeyRelationReadonlyValue.String(),
|
||||
Condition: model.BlockContentDataviewFilter_Equal,
|
||||
Value: pbtypes.Bool(true),
|
||||
},
|
||||
}})
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
package readonlyfixer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/smartblock"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/objectstore"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
mock_space "github.com/anyproto/anytype-heart/space/clientspace/mock_clientspace"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
)
|
||||
|
||||
func TestFixReadonlyInRelations(t *testing.T) {
|
||||
store := objectstore.NewStoreFixture(t)
|
||||
store.AddObjects(t, []objectstore.TestObject{
|
||||
// space1
|
||||
{
|
||||
bundle.RelationKeySpaceId: pbtypes.String("space1"),
|
||||
bundle.RelationKeyRelationFormat: pbtypes.Int64(int64(model.RelationFormat_status)),
|
||||
bundle.RelationKeyId: pbtypes.String("rel-tag"),
|
||||
bundle.RelationKeyRelationReadonlyValue: pbtypes.Bool(true),
|
||||
},
|
||||
{
|
||||
bundle.RelationKeySpaceId: pbtypes.String("space1"),
|
||||
bundle.RelationKeyRelationFormat: pbtypes.Int64(int64(model.RelationFormat_tag)),
|
||||
bundle.RelationKeyId: pbtypes.String("rel-customTag"),
|
||||
bundle.RelationKeyRelationReadonlyValue: pbtypes.Bool(true),
|
||||
},
|
||||
|
||||
// space2
|
||||
{
|
||||
bundle.RelationKeySpaceId: pbtypes.String("space2"),
|
||||
bundle.RelationKeyRelationFormat: pbtypes.Int64(0),
|
||||
bundle.RelationKeyId: pbtypes.String("rel-id"),
|
||||
bundle.RelationKeyRelationReadonlyValue: pbtypes.Bool(true),
|
||||
},
|
||||
{
|
||||
bundle.RelationKeySpaceId: pbtypes.String("space2"),
|
||||
bundle.RelationKeyRelationFormat: pbtypes.Int64(2),
|
||||
bundle.RelationKeyId: pbtypes.String("rel-relationFormat"),
|
||||
bundle.RelationKeyRelationReadonlyValue: pbtypes.Bool(true),
|
||||
},
|
||||
|
||||
// space3
|
||||
{
|
||||
bundle.RelationKeySpaceId: pbtypes.String("space3"),
|
||||
bundle.RelationKeyRelationFormat: pbtypes.Int64(int64(model.RelationFormat_tag)),
|
||||
bundle.RelationKeyId: pbtypes.String("rel-category"),
|
||||
bundle.RelationKeyRelationReadonlyValue: pbtypes.Bool(false),
|
||||
},
|
||||
{
|
||||
bundle.RelationKeySpaceId: pbtypes.String("space3"),
|
||||
bundle.RelationKeyRelationFormat: pbtypes.Int64(int64(model.RelationFormat_status)),
|
||||
bundle.RelationKeyId: pbtypes.String("rel-genderCustom"),
|
||||
bundle.RelationKeyRelationReadonlyValue: pbtypes.Bool(false),
|
||||
},
|
||||
})
|
||||
fixer := &Migration{}
|
||||
ctx := context.Background()
|
||||
log := logger.NewNamed("test")
|
||||
|
||||
t.Run("fix tag and status relations with readonly=true", func(t *testing.T) {
|
||||
// given
|
||||
spc := mock_space.NewMockSpace(t)
|
||||
spc.EXPECT().Id().Return("space1").Maybe()
|
||||
|
||||
// both relations will be processed
|
||||
spc.EXPECT().DoCtx(mock.Anything, mock.Anything, mock.Anything).RunAndReturn(
|
||||
func(ctx context.Context, objectId string, apply func(smartblock.SmartBlock) error) error {
|
||||
assert.True(t, slices.Contains([]string{"rel-customTag", "rel-tag"}, objectId))
|
||||
return nil
|
||||
},
|
||||
).Times(2)
|
||||
|
||||
// when
|
||||
migrated, toMigrate, err := fixer.Run(ctx, log, store, spc)
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 2, migrated)
|
||||
assert.Equal(t, 2, toMigrate)
|
||||
})
|
||||
|
||||
t.Run("do not process relations of other formats", func(t *testing.T) {
|
||||
// given
|
||||
spc := mock_space.NewMockSpace(t)
|
||||
spc.EXPECT().Id().Return("space2").Maybe()
|
||||
|
||||
// none of relations will be processed
|
||||
// sp.EXPECT().Do(mock.Anything, mock.Anything).Times(1).Return(nil)
|
||||
|
||||
// when
|
||||
migrated, toMigrate, err := fixer.Run(ctx, log, store, spc)
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
assert.Zero(t, migrated)
|
||||
assert.Zero(t, toMigrate)
|
||||
})
|
||||
|
||||
t.Run("do not process relations with readonly=false", func(t *testing.T) {
|
||||
// given
|
||||
spc := mock_space.NewMockSpace(t)
|
||||
spc.EXPECT().Id().Return("space3").Maybe()
|
||||
|
||||
// none of relations will be processed
|
||||
// sp.EXPECT().Do(mock.Anything, mock.Anything).Times(1).Return(nil)
|
||||
|
||||
// when
|
||||
migrated, toMigrate, err := fixer.Run(ctx, log, store, spc)
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
assert.Zero(t, migrated)
|
||||
assert.Zero(t, toMigrate)
|
||||
})
|
||||
}
|
120
space/internal/components/migration/runner.go
Normal file
120
space/internal/components/migration/runner.go
Normal file
|
@ -0,0 +1,120 @@
|
|||
package migration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/objectstore"
|
||||
"github.com/anyproto/anytype-heart/space/clientspace"
|
||||
"github.com/anyproto/anytype-heart/space/internal/components/dependencies"
|
||||
"github.com/anyproto/anytype-heart/space/internal/components/migration/readonlyfixer"
|
||||
"github.com/anyproto/anytype-heart/space/internal/components/migration/systemobjectreviser"
|
||||
"github.com/anyproto/anytype-heart/space/internal/components/spaceloader"
|
||||
)
|
||||
|
||||
const (
|
||||
CName = "common.components.migration"
|
||||
errFormat = "failed to run migration '%s' in space '%s': %w. %d out of %d objects were migrated"
|
||||
)
|
||||
|
||||
var log = logger.NewNamed(CName)
|
||||
|
||||
type Migration interface {
|
||||
Run(context.Context, logger.CtxLogger, dependencies.QueryableStore, dependencies.SpaceWithCtx) (toMigrate, migrated int, err error)
|
||||
Name() string
|
||||
}
|
||||
|
||||
func New() *Runner {
|
||||
return &Runner{}
|
||||
}
|
||||
|
||||
type Runner struct {
|
||||
store objectstore.ObjectStore
|
||||
spaceLoader spaceloader.SpaceLoader
|
||||
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
spc clientspace.Space
|
||||
loadErr error
|
||||
waitLoad chan struct{}
|
||||
started bool
|
||||
|
||||
app.ComponentRunnable
|
||||
}
|
||||
|
||||
func (r *Runner) Name() string {
|
||||
return CName
|
||||
}
|
||||
|
||||
func (r *Runner) Init(a *app.App) error {
|
||||
r.store = app.MustComponent[objectstore.ObjectStore](a)
|
||||
r.spaceLoader = app.MustComponent[spaceloader.SpaceLoader](a)
|
||||
|
||||
r.waitLoad = make(chan struct{})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Runner) Run(context.Context) error {
|
||||
r.started = true
|
||||
r.ctx, r.cancel = context.WithCancel(context.Background())
|
||||
go r.waitSpace()
|
||||
go r.runMigrations()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Runner) Close(context.Context) error {
|
||||
if r.started {
|
||||
r.cancel()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Runner) waitSpace() {
|
||||
r.spc, r.loadErr = r.spaceLoader.WaitLoad(r.ctx)
|
||||
close(r.waitLoad)
|
||||
}
|
||||
|
||||
func (r *Runner) runMigrations() {
|
||||
select {
|
||||
case <-r.ctx.Done():
|
||||
return
|
||||
case <-r.waitLoad:
|
||||
if r.loadErr != nil {
|
||||
log.Error("failed to load space", zap.Error(r.loadErr))
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if err := r.run(systemobjectreviser.Migration{}, readonlyfixer.Migration{}); err != nil {
|
||||
log.Error("failed to run default migrations", zap.String("spaceId", r.spc.Id()), zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Runner) run(migrations ...Migration) (err error) {
|
||||
spaceId := r.spc.Id()
|
||||
|
||||
for _, m := range migrations {
|
||||
if e := r.ctx.Err(); e != nil {
|
||||
err = errors.Join(err, e)
|
||||
return
|
||||
}
|
||||
toMigrate, migrated, e := m.Run(r.ctx, log, r.store, r.spc)
|
||||
if e != nil {
|
||||
err = errors.Join(err, wrapError(e, m.Name(), spaceId, migrated, toMigrate))
|
||||
continue
|
||||
}
|
||||
log.Debug(fmt.Sprintf("migration '%s' in space '%s' is successful. %d out of %d objects were migrated",
|
||||
m.Name(), spaceId, migrated, toMigrate))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func wrapError(err error, migrationName, spaceId string, migrated, toMigrate int) error {
|
||||
return fmt.Errorf(errFormat, migrationName, spaceId, err, migrated, toMigrate)
|
||||
}
|
173
space/internal/components/migration/runner_test.go
Normal file
173
space/internal/components/migration/runner_test.go
Normal file
|
@ -0,0 +1,173 @@
|
|||
package migration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/smartblock"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/database"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/objectstore"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
mock_space "github.com/anyproto/anytype-heart/space/clientspace/mock_clientspace"
|
||||
"github.com/anyproto/anytype-heart/space/internal/components/dependencies"
|
||||
"github.com/anyproto/anytype-heart/space/internal/components/migration/readonlyfixer"
|
||||
"github.com/anyproto/anytype-heart/space/internal/components/migration/systemobjectreviser"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
)
|
||||
|
||||
func TestRunner(t *testing.T) {
|
||||
// TODO: we should revive this test when context query for ObjectStore will be implemented
|
||||
// t.Run("context exceeds + store operation in progress -> context.Canceled", func(t *testing.T) {
|
||||
// // given
|
||||
// store := objectstore.NewStoreFixture(t)
|
||||
// ctx, cancel := context.WithCancel(context.Background())
|
||||
// space := mock_space.NewMockSpace(t)
|
||||
// space.EXPECT().Id().Times(1).Return("")
|
||||
// runner := Runner{ctx: ctx, store: store, spc: space}
|
||||
//
|
||||
// // when
|
||||
// go func() {
|
||||
// time.Sleep(10 * time.Millisecond)
|
||||
// cancel()
|
||||
// }()
|
||||
// err := runner.run(longStoreMigration{})
|
||||
//
|
||||
// // then
|
||||
// assert.Error(t, err)
|
||||
// assert.True(t, errors.Is(err, context.Canceled))
|
||||
// })
|
||||
|
||||
t.Run("context exceeds + space operation in progress -> context.Canceled", func(t *testing.T) {
|
||||
// given
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
space := mock_space.NewMockSpace(t)
|
||||
space.EXPECT().Id().Times(1).Return("")
|
||||
space.EXPECT().DoCtx(mock.Anything, mock.Anything, mock.Anything).RunAndReturn(
|
||||
func(ctx context.Context, _ string, _ func(smartblock.SmartBlock) error) error {
|
||||
timer := time.NewTimer(1 * time.Millisecond)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return context.Canceled
|
||||
case <-timer.C:
|
||||
return nil
|
||||
}
|
||||
},
|
||||
)
|
||||
runner := Runner{ctx: ctx, spc: space}
|
||||
|
||||
// when
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
cancel()
|
||||
}()
|
||||
err := runner.run(longSpaceMigration{})
|
||||
|
||||
// then
|
||||
assert.Error(t, err)
|
||||
assert.True(t, errors.Is(err, context.Canceled))
|
||||
})
|
||||
|
||||
t.Run("context exceeds + migration is finished -> no error", func(t *testing.T) {
|
||||
// given
|
||||
store := objectstore.NewStoreFixture(t)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
space := mock_space.NewMockSpace(t)
|
||||
space.EXPECT().Id().Times(1).Return("")
|
||||
runner := Runner{ctx: ctx, store: store, spc: space}
|
||||
|
||||
// when
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
cancel()
|
||||
}()
|
||||
err := runner.run(instantMigration{})
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("no ctx exceed + migration is finished -> no error", func(t *testing.T) {
|
||||
// given
|
||||
store := objectstore.NewStoreFixture(t)
|
||||
space := mock_space.NewMockSpace(t)
|
||||
space.EXPECT().Id().Return("").Maybe()
|
||||
runner := Runner{ctx: context.Background(), store: store, spc: space}
|
||||
|
||||
// when
|
||||
err := runner.run(systemobjectreviser.Migration{})
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("no ctx exceed + migration failure -> error", func(t *testing.T) {
|
||||
// given
|
||||
store := objectstore.NewStoreFixture(t)
|
||||
store.AddObjects(t, []objectstore.TestObject{{
|
||||
bundle.RelationKeySpaceId: pbtypes.String("space1"),
|
||||
bundle.RelationKeyRelationFormat: pbtypes.Int64(int64(model.RelationFormat_status)),
|
||||
bundle.RelationKeyId: pbtypes.String("rel-tag"),
|
||||
bundle.RelationKeyRelationReadonlyValue: pbtypes.Bool(true),
|
||||
}})
|
||||
spaceErr := errors.New("failed to get object")
|
||||
space := mock_space.NewMockSpace(t)
|
||||
space.EXPECT().Id().Return("space1").Maybe()
|
||||
space.EXPECT().DoCtx(mock.Anything, mock.Anything, mock.Anything).Maybe().Return(spaceErr)
|
||||
runner := Runner{ctx: context.Background(), store: store, spc: space}
|
||||
|
||||
// when
|
||||
err := runner.run(readonlyfixer.Migration{})
|
||||
|
||||
// then
|
||||
assert.Error(t, err)
|
||||
assert.True(t, errors.Is(err, spaceErr))
|
||||
})
|
||||
}
|
||||
|
||||
type longStoreMigration struct{}
|
||||
|
||||
func (longStoreMigration) Name() string {
|
||||
return "long migration"
|
||||
}
|
||||
|
||||
func (longStoreMigration) Run(ctx context.Context, _ logger.CtxLogger, store dependencies.QueryableStore, _ dependencies.SpaceWithCtx) (toMigrate, migrated int, err error) {
|
||||
for {
|
||||
if _, err = store.Query(database.Query{}); err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type longSpaceMigration struct{}
|
||||
|
||||
func (longSpaceMigration) Name() string {
|
||||
return "long migration"
|
||||
}
|
||||
|
||||
func (longSpaceMigration) Run(ctx context.Context, _ logger.CtxLogger, _ dependencies.QueryableStore, space dependencies.SpaceWithCtx) (toMigrate, migrated int, err error) {
|
||||
for {
|
||||
if err = space.DoCtx(ctx, "", func(smartblock.SmartBlock) error {
|
||||
// do smth
|
||||
return nil
|
||||
}); err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type instantMigration struct{}
|
||||
|
||||
func (instantMigration) Name() string {
|
||||
return "instant migration"
|
||||
}
|
||||
|
||||
func (instantMigration) Run(context.Context, logger.CtxLogger, dependencies.QueryableStore, dependencies.SpaceWithCtx) (toMigrate, migrated int, err error) {
|
||||
return 0, 0, nil
|
||||
}
|
|
@ -1,8 +1,14 @@
|
|||
package objectcreator
|
||||
package systemobjectreviser
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/gogo/protobuf/types"
|
||||
"github.com/samber/lo"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/basic"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/smartblock"
|
||||
|
@ -12,26 +18,52 @@ import (
|
|||
"github.com/anyproto/anytype-heart/pkg/lib/database"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/addr"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
"github.com/anyproto/anytype-heart/space/clientspace"
|
||||
"github.com/anyproto/anytype-heart/space/internal/components/dependencies"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
)
|
||||
|
||||
const MName = "SystemObjectReviser"
|
||||
|
||||
var revisionKey = bundle.RelationKeyRevision.String()
|
||||
|
||||
func (s *service) reviseSystemObjects(space clientspace.Space, objects map[string]*types.Struct) {
|
||||
marketObjects, err := s.listAllTypesAndRelations(addr.AnytypeMarketplaceWorkspace)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get relations from marketplace space: %v", err)
|
||||
return
|
||||
}
|
||||
// Migration SystemObjectReviser performs revision of all system object types and relations, so after Migration
|
||||
// objects installed in space should correspond to bundled objects from library.
|
||||
// To modify relations of system objects relation revision should be incremented in types.json or relations.json
|
||||
// For more info see 'System Objects Update' section of docs/Flow.md
|
||||
type Migration struct{}
|
||||
|
||||
for _, details := range objects {
|
||||
reviseSystemObject(space, details, marketObjects)
|
||||
}
|
||||
func (Migration) Name() string {
|
||||
return MName
|
||||
}
|
||||
|
||||
func (s *service) listAllTypesAndRelations(spaceId string) (map[string]*types.Struct, error) {
|
||||
records, err := s.objectStore.Query(database.Query{
|
||||
func (Migration) Run(ctx context.Context, log logger.CtxLogger, store dependencies.QueryableStore, space dependencies.SpaceWithCtx) (toMigrate, migrated int, err error) {
|
||||
spaceObjects, err := listAllTypesAndRelations(store, space.Id())
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("failed to get relations and types from client space: %w", err)
|
||||
}
|
||||
|
||||
marketObjects, err := listAllTypesAndRelations(store, addr.AnytypeMarketplaceWorkspace)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("failed to get relations from marketplace space: %w", err)
|
||||
}
|
||||
|
||||
for _, details := range spaceObjects {
|
||||
shouldBeRevised, e := reviseSystemObject(ctx, log, space, details, marketObjects)
|
||||
if !shouldBeRevised {
|
||||
continue
|
||||
}
|
||||
toMigrate++
|
||||
if e != nil {
|
||||
err = errors.Join(err, fmt.Errorf("failed to revise object: %w", e))
|
||||
} else {
|
||||
migrated++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func listAllTypesAndRelations(store dependencies.QueryableStore, spaceId string) (map[string]*types.Struct, error) {
|
||||
records, err := store.Query(database.Query{
|
||||
Filters: []*model.BlockContentDataviewFilter{
|
||||
{
|
||||
RelationKey: bundle.RelationKeyLayout.String(),
|
||||
|
@ -50,30 +82,32 @@ func (s *service) listAllTypesAndRelations(spaceId string) (map[string]*types.St
|
|||
}
|
||||
|
||||
details := make(map[string]*types.Struct, len(records))
|
||||
for _, rec := range records {
|
||||
id := pbtypes.GetString(rec.Details, bundle.RelationKeyId.String())
|
||||
details[id] = rec.Details
|
||||
for _, record := range records {
|
||||
id := pbtypes.GetString(record.Details, bundle.RelationKeyId.String())
|
||||
details[id] = record.Details
|
||||
}
|
||||
return details, nil
|
||||
}
|
||||
|
||||
func reviseSystemObject(space clientspace.Space, localObject *types.Struct, marketObjects map[string]*types.Struct) {
|
||||
func reviseSystemObject(ctx context.Context, log logger.CtxLogger, space dependencies.SpaceWithCtx, localObject *types.Struct, marketObjects map[string]*types.Struct) (toRevise bool, err error) {
|
||||
source := pbtypes.GetString(localObject, bundle.RelationKeySourceObject.String())
|
||||
marketObject, found := marketObjects[source]
|
||||
if !found || !isSystemObject(localObject) || pbtypes.GetInt64(marketObject, revisionKey) <= pbtypes.GetInt64(localObject, revisionKey) {
|
||||
return
|
||||
return false, nil
|
||||
}
|
||||
details := buildDiffDetails(marketObject, localObject)
|
||||
if len(details) != 0 {
|
||||
if err := space.Do(pbtypes.GetString(localObject, bundle.RelationKeyId.String()), func(sb smartblock.SmartBlock) error {
|
||||
log.Debug("updating system object", zap.String("source", source), zap.String("space", space.Id()))
|
||||
if err := space.DoCtx(ctx, pbtypes.GetString(localObject, bundle.RelationKeyId.String()), func(sb smartblock.SmartBlock) error {
|
||||
if ds, ok := sb.(basic.DetailsSettable); ok {
|
||||
return ds.SetDetails(nil, details, false)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
log.Errorf("failed to update system object %s in space %s: %v", source, space.Id(), err)
|
||||
return true, fmt.Errorf("failed to update system object %s in space %s: %w", source, space.Id(), err)
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func isSystemObject(details *types.Struct) bool {
|
|
@ -1,9 +1,12 @@
|
|||
package objectcreator
|
||||
package systemobjectreviser
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/gogo/protobuf/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
|
@ -11,7 +14,9 @@ import (
|
|||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
)
|
||||
|
||||
func TestUpdateSystemObject(t *testing.T) {
|
||||
func TestReviseSystemObject(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
log := logger.NewNamed("tesr")
|
||||
marketObjects := map[string]*types.Struct{
|
||||
"_otnote": {Fields: map[string]*types.Value{revisionKey: pbtypes.Int64(3)}},
|
||||
"_otpage": {Fields: map[string]*types.Value{revisionKey: pbtypes.Int64(2)}},
|
||||
|
@ -23,48 +28,75 @@ func TestUpdateSystemObject(t *testing.T) {
|
|||
}
|
||||
|
||||
t.Run("system object type is updated if revision is higher", func(t *testing.T) {
|
||||
// given
|
||||
objectType := &types.Struct{Fields: map[string]*types.Value{
|
||||
bundle.RelationKeyRevision.String(): pbtypes.Int64(1),
|
||||
bundle.RelationKeySourceObject.String(): pbtypes.String("_otnote"),
|
||||
bundle.RelationKeyUniqueKey.String(): pbtypes.String("ot-note"),
|
||||
}}
|
||||
space := mock_space.NewMockSpace(t)
|
||||
space.EXPECT().Do(mock.Anything, mock.Anything).Times(1).Return(nil)
|
||||
space.EXPECT().DoCtx(mock.Anything, mock.Anything, mock.Anything).Times(1).Return(nil)
|
||||
space.EXPECT().Id().Times(1).Return("")
|
||||
|
||||
reviseSystemObject(space, objectType, marketObjects)
|
||||
// when
|
||||
toRevise, err := reviseSystemObject(ctx, log, space, objectType, marketObjects)
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, toRevise)
|
||||
})
|
||||
|
||||
t.Run("system object type is updated if no revision is set", func(t *testing.T) {
|
||||
// given
|
||||
objectType := &types.Struct{Fields: map[string]*types.Value{
|
||||
bundle.RelationKeySourceObject.String(): pbtypes.String("_otpage"),
|
||||
bundle.RelationKeyUniqueKey.String(): pbtypes.String("ot-page"),
|
||||
}}
|
||||
space := mock_space.NewMockSpace(t)
|
||||
space.EXPECT().Do(mock.Anything, mock.Anything).Times(1).Return(nil)
|
||||
space.EXPECT().DoCtx(mock.Anything, mock.Anything, mock.Anything).Times(1).Return(nil)
|
||||
space.EXPECT().Id().Times(1).Return("")
|
||||
|
||||
reviseSystemObject(space, objectType, marketObjects)
|
||||
// when
|
||||
toRevise, err := reviseSystemObject(ctx, log, space, objectType, marketObjects)
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, toRevise)
|
||||
})
|
||||
|
||||
t.Run("custom object type is not updated", func(t *testing.T) {
|
||||
// given
|
||||
objectType := &types.Struct{Fields: map[string]*types.Value{
|
||||
bundle.RelationKeyUniqueKey.String(): pbtypes.String("ot-kitty"),
|
||||
}}
|
||||
space := mock_space.NewMockSpace(t) // if unexpected space.Do will be called, test will fail
|
||||
|
||||
reviseSystemObject(space, objectType, marketObjects)
|
||||
// when
|
||||
toRevise, err := reviseSystemObject(ctx, log, space, objectType, marketObjects)
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, toRevise)
|
||||
})
|
||||
|
||||
t.Run("non system object type is not updated", func(t *testing.T) {
|
||||
// given
|
||||
objectType := &types.Struct{Fields: map[string]*types.Value{
|
||||
bundle.RelationKeySourceObject.String(): pbtypes.String("_otcontact"),
|
||||
bundle.RelationKeyUniqueKey.String(): pbtypes.String("ot-contact"),
|
||||
}}
|
||||
space := mock_space.NewMockSpace(t) // if unexpected space.Do will be called, test will fail
|
||||
|
||||
reviseSystemObject(space, objectType, marketObjects)
|
||||
// when
|
||||
toRevise, err := reviseSystemObject(ctx, log, space, objectType, marketObjects)
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, toRevise)
|
||||
})
|
||||
|
||||
t.Run("system object type with same revision is not updated", func(t *testing.T) {
|
||||
// given
|
||||
objectType := &types.Struct{Fields: map[string]*types.Value{
|
||||
bundle.RelationKeyRevision.String(): pbtypes.Int64(3),
|
||||
bundle.RelationKeySourceObject.String(): pbtypes.String("_otnote"),
|
||||
|
@ -72,42 +104,68 @@ func TestUpdateSystemObject(t *testing.T) {
|
|||
}}
|
||||
space := mock_space.NewMockSpace(t) // if unexpected space.Do will be called, test will fail
|
||||
|
||||
reviseSystemObject(space, objectType, marketObjects)
|
||||
// when
|
||||
toRevise, err := reviseSystemObject(ctx, log, space, objectType, marketObjects)
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, toRevise)
|
||||
})
|
||||
|
||||
t.Run("system relation is updated if revision is higher", func(t *testing.T) {
|
||||
// given
|
||||
rel := &types.Struct{Fields: map[string]*types.Value{
|
||||
bundle.RelationKeyRevision.String(): pbtypes.Int64(1),
|
||||
bundle.RelationKeySourceObject.String(): pbtypes.String("_brdescription"),
|
||||
bundle.RelationKeyUniqueKey.String(): pbtypes.String("rel-description"),
|
||||
}}
|
||||
space := mock_space.NewMockSpace(t)
|
||||
space.EXPECT().Do(mock.Anything, mock.Anything).Times(1).Return(nil)
|
||||
space.EXPECT().DoCtx(mock.Anything, mock.Anything, mock.Anything).Times(1).Return(nil)
|
||||
space.EXPECT().Id().Times(1).Return("")
|
||||
|
||||
reviseSystemObject(space, rel, marketObjects)
|
||||
// when
|
||||
toRevise, err := reviseSystemObject(ctx, log, space, rel, marketObjects)
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, toRevise)
|
||||
})
|
||||
|
||||
t.Run("system relation is updated if no revision is set", func(t *testing.T) {
|
||||
// given
|
||||
rel := &types.Struct{Fields: map[string]*types.Value{
|
||||
bundle.RelationKeySourceObject.String(): pbtypes.String("_brid"),
|
||||
bundle.RelationKeyUniqueKey.String(): pbtypes.String("rel-id"),
|
||||
}}
|
||||
space := mock_space.NewMockSpace(t)
|
||||
space.EXPECT().Do(mock.Anything, mock.Anything).Times(1).Return(nil)
|
||||
space.EXPECT().DoCtx(mock.Anything, mock.Anything, mock.Anything).Times(1).Return(nil)
|
||||
space.EXPECT().Id().Times(1).Return("")
|
||||
|
||||
reviseSystemObject(space, rel, marketObjects)
|
||||
// when
|
||||
toRevise, err := reviseSystemObject(ctx, log, space, rel, marketObjects)
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, toRevise)
|
||||
})
|
||||
|
||||
t.Run("custom relation is not updated", func(t *testing.T) {
|
||||
// given
|
||||
rel := &types.Struct{Fields: map[string]*types.Value{
|
||||
bundle.RelationKeyUniqueKey.String(): pbtypes.String("rel-custom"),
|
||||
}}
|
||||
space := mock_space.NewMockSpace(t) // if unexpected space.Do will be called, test will fail
|
||||
|
||||
reviseSystemObject(space, rel, marketObjects)
|
||||
// when
|
||||
toRevise, err := reviseSystemObject(ctx, log, space, rel, marketObjects)
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, toRevise)
|
||||
})
|
||||
|
||||
t.Run("non system relation is not updated", func(t *testing.T) {
|
||||
// given
|
||||
rel := &types.Struct{Fields: map[string]*types.Value{
|
||||
bundle.RelationKeyRevision.String(): pbtypes.Int64(1),
|
||||
bundle.RelationKeySourceObject.String(): pbtypes.String("_brlyrics"),
|
||||
|
@ -115,10 +173,16 @@ func TestUpdateSystemObject(t *testing.T) {
|
|||
}}
|
||||
space := mock_space.NewMockSpace(t) // if unexpected space.Do will be called, test will fail
|
||||
|
||||
reviseSystemObject(space, rel, marketObjects)
|
||||
// when
|
||||
toRevise, err := reviseSystemObject(ctx, log, space, rel, marketObjects)
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, toRevise)
|
||||
})
|
||||
|
||||
t.Run("system relation with same revision is not updated", func(t *testing.T) {
|
||||
// given
|
||||
rel := &types.Struct{Fields: map[string]*types.Value{
|
||||
bundle.RelationKeyRevision.String(): pbtypes.Int64(3),
|
||||
bundle.RelationKeySourceObject.String(): pbtypes.String("_brisReadonly"),
|
||||
|
@ -126,10 +190,16 @@ func TestUpdateSystemObject(t *testing.T) {
|
|||
}}
|
||||
space := mock_space.NewMockSpace(t) // if unexpected space.Do will be called, test will fail
|
||||
|
||||
reviseSystemObject(space, rel, marketObjects)
|
||||
// when
|
||||
toRevise, err := reviseSystemObject(ctx, log, space, rel, marketObjects)
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, toRevise)
|
||||
})
|
||||
|
||||
t.Run("relation with absent maxCount is updated", func(t *testing.T) {
|
||||
// given
|
||||
rel := &types.Struct{Fields: map[string]*types.Value{
|
||||
bundle.RelationKeyRevision.String(): pbtypes.Int64(2),
|
||||
bundle.RelationKeySourceObject.String(): pbtypes.String("_brisReadonly"),
|
||||
|
@ -137,8 +207,14 @@ func TestUpdateSystemObject(t *testing.T) {
|
|||
bundle.RelationKeyRelationMaxCount.String(): pbtypes.Int64(1),
|
||||
}}
|
||||
space := mock_space.NewMockSpace(t)
|
||||
space.EXPECT().Do(mock.Anything, mock.Anything).Times(1).Return(nil)
|
||||
space.EXPECT().DoCtx(mock.Anything, mock.Anything, mock.Anything).Times(1).Return(nil)
|
||||
space.EXPECT().Id().Times(1).Return("")
|
||||
|
||||
reviseSystemObject(space, rel, marketObjects)
|
||||
// when
|
||||
toRevise, err := reviseSystemObject(ctx, log, space, rel, marketObjects)
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, toRevise)
|
||||
})
|
||||
}
|
|
@ -3,6 +3,7 @@ package marketplacespace
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/anyproto/any-sync/accountservice"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
|
@ -21,6 +22,7 @@ func NewSpaceController(a *app.App, personalSpaceId string) spacecontroller.Spac
|
|||
return &spaceController{
|
||||
app: a,
|
||||
personalSpaceId: personalSpaceId,
|
||||
indexer: app.MustComponent[dependencies.SpaceIndexer](a),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,17 +30,18 @@ type spaceController struct {
|
|||
app *app.App
|
||||
personalSpaceId string
|
||||
vs clientspace.Space
|
||||
reindexOnce sync.Once
|
||||
indexer dependencies.SpaceIndexer
|
||||
}
|
||||
|
||||
func (s *spaceController) Start(ctx context.Context) (err error) {
|
||||
indexer := app.MustComponent[dependencies.SpaceIndexer](s.app)
|
||||
func (s *spaceController) Start(context.Context) (err error) {
|
||||
s.vs = clientspace.NewVirtualSpace(
|
||||
addr.AnytypeMarketplaceWorkspace,
|
||||
clientspace.VirtualSpaceDeps{
|
||||
ObjectFactory: app.MustComponent[objectcache.ObjectFactory](s.app),
|
||||
AccountService: app.MustComponent[accountservice.Service](s.app),
|
||||
PersonalSpaceId: s.personalSpaceId,
|
||||
Indexer: app.MustComponent[dependencies.SpaceIndexer](s.app),
|
||||
Indexer: s.indexer,
|
||||
Installer: app.MustComponent[dependencies.BundledObjectsInstaller](s.app),
|
||||
TypePrefix: addr.BundledObjectTypeURLPrefix,
|
||||
RelationPrefix: addr.BundledRelationURLPrefix,
|
||||
|
@ -53,10 +56,6 @@ func (s *spaceController) Start(ctx context.Context) (err error) {
|
|||
if err != nil {
|
||||
return fmt.Errorf("register builtin templates: %w", err)
|
||||
}
|
||||
err = indexer.ReindexMarketplaceSpace(s.vs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reindex marketplace space: %w", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -64,7 +63,14 @@ func (s *spaceController) Mode() mode.Mode {
|
|||
return mode.ModeLoading
|
||||
}
|
||||
|
||||
func (s *spaceController) WaitLoad(ctx context.Context) (sp clientspace.Space, err error) {
|
||||
func (s *spaceController) WaitLoad(context.Context) (sp clientspace.Space, err error) {
|
||||
s.reindexOnce.Do(func() {
|
||||
// TODO: GO-3557 Need to confirm moving ReindexMarketplaceSpace from Start to WaitLoad with mcrakhman
|
||||
err = s.indexer.ReindexMarketplaceSpace(s.vs)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.vs, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/anyproto/anytype-heart/space/internal/components/aclobjectmanager"
|
||||
"github.com/anyproto/anytype-heart/space/internal/components/builder"
|
||||
"github.com/anyproto/anytype-heart/space/internal/components/invitemigrator"
|
||||
"github.com/anyproto/anytype-heart/space/internal/components/migration"
|
||||
"github.com/anyproto/anytype-heart/space/internal/components/participantwatcher"
|
||||
"github.com/anyproto/anytype-heart/space/internal/components/spaceloader"
|
||||
"github.com/anyproto/anytype-heart/space/internal/spaceprocess/components/aclindexcleaner"
|
||||
|
@ -43,7 +44,8 @@ func New(app *app.App, params Params) Loader {
|
|||
Register(aclnotifications.NewAclNotificationSender()).
|
||||
Register(aclobjectmanager.New(params.OwnerMetadata)).
|
||||
Register(invitemigrator.New()).
|
||||
Register(participantwatcher.New())
|
||||
Register(participantwatcher.New()).
|
||||
Register(migration.New())
|
||||
return &loader{
|
||||
app: child,
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/anyproto/anytype-heart/space/clientspace"
|
||||
"github.com/anyproto/anytype-heart/space/internal/components/builder"
|
||||
"github.com/anyproto/anytype-heart/space/internal/components/migration"
|
||||
"github.com/anyproto/anytype-heart/space/internal/components/spaceloader"
|
||||
"github.com/anyproto/anytype-heart/space/internal/spaceprocess/components/aclindexcleaner"
|
||||
"github.com/anyproto/anytype-heart/space/internal/spaceprocess/loader"
|
||||
|
@ -32,7 +33,8 @@ func New(app *app.App, params Params) Remover {
|
|||
child := app.ChildApp()
|
||||
child.Register(aclindexcleaner.New()).
|
||||
Register(builder.New()).
|
||||
Register(spaceloader.New(params.StopIfMandatoryFail, true))
|
||||
Register(spaceloader.New(params.StopIfMandatoryFail, true)).
|
||||
Register(migration.New())
|
||||
return &remover{
|
||||
app: child,
|
||||
}
|
||||
|
|
|
@ -83,6 +83,7 @@ type NotificationSender interface {
|
|||
|
||||
type service struct {
|
||||
techSpace *clientspace.TechSpace
|
||||
techSpaceReady chan struct{}
|
||||
factory spacefactory.SpaceFactory
|
||||
spaceCore spacecore.SpaceCoreService
|
||||
accountService accountservice.Service
|
||||
|
@ -137,6 +138,7 @@ func (s *service) Init(a *app.App) (err error) {
|
|||
s.spaceControllers = make(map[string]spacecontroller.SpaceController)
|
||||
s.updater = app.MustComponent[coordinatorStatusUpdater](a)
|
||||
s.waiting = make(map[string]controllerWaiter)
|
||||
s.techSpaceReady = make(chan struct{})
|
||||
s.personalSpaceId, err = s.spaceCore.DeriveID(context.Background(), spacecore.SpaceType)
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -209,8 +211,8 @@ func (s *service) Wait(ctx context.Context, spaceId string) (sp clientspace.Spac
|
|||
}
|
||||
|
||||
func (s *service) Get(ctx context.Context, spaceId string) (sp clientspace.Space, err error) {
|
||||
if spaceId == s.techSpace.TechSpaceId() {
|
||||
return s.techSpace, nil
|
||||
if spaceId == s.techSpaceId {
|
||||
return s.getTechSpace(ctx)
|
||||
}
|
||||
ctrl, err := s.getCtrl(ctx, spaceId)
|
||||
if err != nil {
|
||||
|
@ -355,5 +357,14 @@ func (s *service) AllSpaceIds() (ids []string) {
|
|||
}
|
||||
|
||||
func (s *service) TechSpaceId() string {
|
||||
return s.techSpace.TechSpaceId()
|
||||
return s.techSpaceId
|
||||
}
|
||||
|
||||
func (s *service) getTechSpace(ctx context.Context) (*clientspace.TechSpace, error) {
|
||||
select {
|
||||
case <-s.techSpaceReady:
|
||||
return s.techSpace, nil
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/accountservice/mock_accountservice"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
|
@ -46,12 +47,37 @@ const (
|
|||
|
||||
// TODO Revive tests
|
||||
func TestService_Init(t *testing.T) {
|
||||
t.Skip("@roman should revive this test")
|
||||
t.Run("tech space getter", func(t *testing.T) {
|
||||
serv := New().(*service)
|
||||
serv.techSpaceId = "tech.space"
|
||||
factory := mock_spacefactory.NewMockSpaceFactory(t)
|
||||
serv.factory = factory
|
||||
serv.techSpaceReady = make(chan struct{})
|
||||
|
||||
// not initialized - expect context deadline
|
||||
ctx, ctxCancel := context.WithTimeout(context.Background(), time.Millisecond)
|
||||
defer ctxCancel()
|
||||
_, err := serv.Get(ctx, serv.techSpaceId)
|
||||
require.ErrorIs(t, err, context.DeadlineExceeded)
|
||||
|
||||
// initialized - expect space
|
||||
ctx2, ctxCancel2 := context.WithTimeout(context.Background(), time.Millisecond)
|
||||
defer ctxCancel2()
|
||||
|
||||
factory.EXPECT().CreateAndSetTechSpace(ctx2).Return(&clientspace.TechSpace{}, nil)
|
||||
require.NoError(t, serv.initTechSpace(ctx2))
|
||||
|
||||
s, err := serv.Get(ctx2, serv.techSpaceId)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, s)
|
||||
})
|
||||
t.Run("existing account", func(t *testing.T) {
|
||||
t.Skip("@roman should revive this test")
|
||||
fx := newFixture(t, false)
|
||||
defer fx.finish(t)
|
||||
})
|
||||
t.Run("new account", func(t *testing.T) {
|
||||
t.Skip("@roman should revive this test")
|
||||
fx := newFixture(t, true)
|
||||
defer fx.finish(t)
|
||||
})
|
||||
|
|
58
util/bufferpool/buffer.go
Normal file
58
util/bufferpool/buffer.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package bufferpool
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Buffer interface {
|
||||
io.Writer
|
||||
io.Closer
|
||||
GetReadSeekCloser() (io.ReadSeekCloser, error)
|
||||
}
|
||||
|
||||
type buffer struct {
|
||||
*bytes.Buffer
|
||||
buf []byte
|
||||
pool *sync.Pool
|
||||
m sync.Mutex
|
||||
closed bool
|
||||
}
|
||||
|
||||
// GetReadSeekCloser returns a ReadSeekCloser that reads from the buffer.
|
||||
// GetReadSeekCloser after Close will return EOF.
|
||||
// It's a responsibility of the caller to Close the ReadSeekCloser to put the buffer back into the pool.
|
||||
func (b *buffer) GetReadSeekCloser() (io.ReadSeekCloser, error) {
|
||||
b.m.Lock()
|
||||
defer b.m.Unlock()
|
||||
if !b.closed {
|
||||
b.closed = true
|
||||
return newPoolReadSeekCloser(b.Buffer.Bytes(), b.pool), nil
|
||||
}
|
||||
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
// Close puts the buffer back into the pool.
|
||||
// Close after GetReadSeekCloser does nothing.
|
||||
func (b *buffer) Write(p []byte) (n int, err error) {
|
||||
b.m.Lock()
|
||||
defer b.m.Unlock()
|
||||
if b.closed {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return b.Buffer.Write(p)
|
||||
}
|
||||
|
||||
// Close puts the buffer back into the pool.
|
||||
// Close after GetReadSeekCloser does nothing.
|
||||
func (b *buffer) Close() error {
|
||||
b.m.Lock()
|
||||
defer b.m.Unlock()
|
||||
if !b.closed {
|
||||
b.pool.Put(b.buf)
|
||||
b.closed = true
|
||||
}
|
||||
return nil
|
||||
}
|
34
util/bufferpool/pool.go
Normal file
34
util/bufferpool/pool.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package bufferpool
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Pool interface {
|
||||
Get() Buffer
|
||||
}
|
||||
|
||||
func NewPool() Pool {
|
||||
return &bufferPoolWrapper{pool: &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return []byte{}
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
type bufferPoolWrapper struct {
|
||||
pool *sync.Pool
|
||||
}
|
||||
|
||||
func (bp *bufferPoolWrapper) Get() Buffer {
|
||||
b := bp.pool.Get().([]byte)
|
||||
|
||||
buff := &buffer{
|
||||
Buffer: bytes.NewBuffer(b[:0]),
|
||||
buf: b,
|
||||
pool: bp.pool,
|
||||
}
|
||||
|
||||
return buff
|
||||
}
|
84
util/bufferpool/pool_test.go
Normal file
84
util/bufferpool/pool_test.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
package bufferpool
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewPool(t *testing.T) {
|
||||
pool := NewPool()
|
||||
require.NotNil(t, pool, "NewPool should not return nil")
|
||||
}
|
||||
|
||||
func TestBuffer_Write(t *testing.T) {
|
||||
pool := NewPool()
|
||||
buf := pool.Get()
|
||||
data := []byte("Hello, World!")
|
||||
n, err := buf.Write(data)
|
||||
require.NoError(t, err, "Write should not return an error")
|
||||
assert.Equal(t, len(data), n, "Write should return the number of bytes written")
|
||||
|
||||
err = buf.Close()
|
||||
require.NoError(t, err, "Close should not return an error")
|
||||
}
|
||||
|
||||
func TestBuffer_Close(t *testing.T) {
|
||||
pool := NewPool()
|
||||
buf := pool.Get()
|
||||
|
||||
err := buf.Close()
|
||||
require.NoError(t, err, "Close should not return an error")
|
||||
n, err := buf.Write([]byte("Hello, World!"))
|
||||
assert.ErrorIs(t, err, io.EOF, "Read after Close should return an error")
|
||||
require.Zero(t, n, "Write after Close should not write any bytes")
|
||||
}
|
||||
|
||||
func TestBuffer_GetReadSeekCloser(t *testing.T) {
|
||||
pool := NewPool()
|
||||
buf := pool.Get()
|
||||
|
||||
data := []byte("Hello, World!")
|
||||
_, err := buf.Write(data)
|
||||
require.NoError(t, err, "Write should not return an error")
|
||||
|
||||
rsc, err := buf.GetReadSeekCloser()
|
||||
require.NoError(t, err, "GetReadSeekCloser should not return an error")
|
||||
assert.NotNil(t, rsc, "GetReadSeekCloser should not return nil")
|
||||
|
||||
readData := make([]byte, len(data))
|
||||
readData2 := make([]byte, len(data))
|
||||
|
||||
n, err := rsc.Read(readData)
|
||||
require.NoError(t, err, "Read should not return an error")
|
||||
assert.Equal(t, len(data), n, "Read should return the number of bytes read")
|
||||
assert.Equal(t, data, readData, "Read data should match written data")
|
||||
|
||||
n2, err := rsc.Seek(0, io.SeekStart)
|
||||
require.NoError(t, err, "Seek should not return an error")
|
||||
assert.Equal(t, int64(0), n2, "Seek should return the new offset")
|
||||
|
||||
_, err = rsc.Read(readData2)
|
||||
require.NoError(t, err, "Read after seek should not return an error")
|
||||
assert.Equal(t, data, readData2, "Read data after seek should match written data")
|
||||
|
||||
err = rsc.Close()
|
||||
require.NoError(t, err, "Close should not return an error")
|
||||
|
||||
_, err = rsc.Read(readData)
|
||||
assert.Error(t, err, "Read after Close should return an error")
|
||||
|
||||
// take the existing buffer from the pool
|
||||
buf = pool.Get()
|
||||
// check underlying buffer is returned to the pool
|
||||
assert.GreaterOrEqual(t, cap(buf.(*buffer).buf), 13, "we should get the same buffer from the pool")
|
||||
assert.GreaterOrEqual(t, buf.(*buffer).Buffer.Cap(), 13, "we should get the same buffer from the pool")
|
||||
assert.Equalf(t, 0, len(buf.(*buffer).Buffer.Bytes()), "we should get the reseted buffer from the pool")
|
||||
assert.Equal(t, []byte("Hello, World!"), buf.(*buffer).buf[0:13])
|
||||
assert.Equal(t, []byte("Hello, World!"), buf.(*buffer).Buffer.Bytes()[0:13])
|
||||
|
||||
err = rsc.Close()
|
||||
require.NoError(t, err, "Close after Close should not return an error")
|
||||
}
|
47
util/bufferpool/reader.go
Normal file
47
util/bufferpool/reader.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package bufferpool
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// poolReadSeekCloser is a custom type that wraps a byte slice and a sync.Pool.
|
||||
type poolReadSeekCloser struct {
|
||||
*bytes.Reader
|
||||
buf []byte
|
||||
pool *sync.Pool
|
||||
m sync.RWMutex
|
||||
closed bool
|
||||
}
|
||||
|
||||
// NewPoolReadSeekCloser creates a new poolReadSeekCloser.
|
||||
func newPoolReadSeekCloser(buf []byte, pool *sync.Pool) io.ReadSeekCloser {
|
||||
return &poolReadSeekCloser{
|
||||
Reader: bytes.NewReader(buf),
|
||||
buf: buf,
|
||||
pool: pool,
|
||||
}
|
||||
}
|
||||
|
||||
// Close puts the buffer back into the pool.
|
||||
func (prsc *poolReadSeekCloser) Close() error {
|
||||
prsc.m.Lock()
|
||||
defer prsc.m.Unlock()
|
||||
if prsc.closed {
|
||||
return nil
|
||||
}
|
||||
|
||||
prsc.closed = true
|
||||
prsc.pool.Put(prsc.buf)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (prsc *poolReadSeekCloser) Read(p []byte) (n int, err error) {
|
||||
prsc.m.RLock()
|
||||
defer prsc.m.RUnlock()
|
||||
if prsc.closed {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return prsc.Reader.Read(p)
|
||||
}
|
|
@ -32,14 +32,14 @@ func (c *cache) Name() string {
|
|||
return CName
|
||||
}
|
||||
|
||||
func (c *cache) Fetch(ctx context.Context, url string) (lp model.LinkPreview, body []byte, err error) {
|
||||
func (c *cache) Fetch(ctx context.Context, url string) (linkPreview model.LinkPreview, responseBody []byte, isFile bool, err error) {
|
||||
if res, ok := c.cache.Get(url); ok {
|
||||
return res.(model.LinkPreview), nil, nil
|
||||
return res.(model.LinkPreview), nil, false, nil
|
||||
}
|
||||
lp, body, err = c.lp.Fetch(ctx, url)
|
||||
linkPreview, responseBody, _, err = c.lp.Fetch(ctx, url)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.cache.Add(url, lp)
|
||||
c.cache.Add(url, linkPreview)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ func TestCache_Fetch(t *testing.T) {
|
|||
ts := newTestServer("text/html", strings.NewReader(tetsHtml))
|
||||
lp := NewWithCache()
|
||||
lp.Init(nil)
|
||||
info, _, err := lp.Fetch(ctx, ts.URL)
|
||||
info, _, _, err := lp.Fetch(ctx, ts.URL)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, model.LinkPreview{
|
||||
Url: ts.URL,
|
||||
|
@ -27,7 +27,7 @@ func TestCache_Fetch(t *testing.T) {
|
|||
|
||||
ts.Close()
|
||||
|
||||
info, _, err = lp.Fetch(ctx, ts.URL)
|
||||
info, _, _, err = lp.Fetch(ctx, ts.URL)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, model.LinkPreview{
|
||||
Url: ts.URL,
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
@ -40,7 +41,7 @@ const (
|
|||
var log = logging.Logger("link-preview")
|
||||
|
||||
type LinkPreview interface {
|
||||
Fetch(ctx context.Context, url string) (model.LinkPreview, []byte, error)
|
||||
Fetch(ctx context.Context, url string) (linkPreview model.LinkPreview, responseBody []byte, isFile bool, err error)
|
||||
app.Component
|
||||
}
|
||||
|
||||
|
@ -57,27 +58,27 @@ func (l *linkPreview) Name() (name string) {
|
|||
return CName
|
||||
}
|
||||
|
||||
func (l *linkPreview) Fetch(ctx context.Context, fetchUrl string) (model.LinkPreview, []byte, error) {
|
||||
func (l *linkPreview) Fetch(ctx context.Context, fetchUrl string) (linkPreview model.LinkPreview, responseBody []byte, isFile bool, err error) {
|
||||
rt := &proxyRoundTripper{RoundTripper: http.DefaultTransport}
|
||||
client := &http.Client{Transport: rt}
|
||||
og := opengraph.New(fetchUrl)
|
||||
og.URL = fetchUrl
|
||||
og.Intent.Context = ctx
|
||||
og.Intent.HTTPClient = client
|
||||
err := og.Fetch()
|
||||
err = og.Fetch()
|
||||
if err != nil {
|
||||
if resp := rt.lastResponse; resp != nil && resp.StatusCode == http.StatusOK {
|
||||
preview, err := l.makeNonHtml(fetchUrl, resp)
|
||||
preview, isFile, err := l.makeNonHtml(fetchUrl, resp)
|
||||
if err != nil {
|
||||
return preview, nil, err
|
||||
return preview, nil, false, err
|
||||
}
|
||||
return preview, rt.lastBody, nil
|
||||
return preview, rt.lastBody, isFile, nil
|
||||
}
|
||||
return model.LinkPreview{}, nil, err
|
||||
return model.LinkPreview{}, nil, false, err
|
||||
}
|
||||
|
||||
if resp := rt.lastResponse; resp != nil && resp.StatusCode != http.StatusOK {
|
||||
return model.LinkPreview{}, nil, fmt.Errorf("invalid http code %d", resp.StatusCode)
|
||||
return model.LinkPreview{}, nil, false, fmt.Errorf("invalid http code %d", resp.StatusCode)
|
||||
}
|
||||
res := l.convertOGToInfo(fetchUrl, og)
|
||||
if len(res.Description) == 0 {
|
||||
|
@ -93,7 +94,7 @@ func (l *linkPreview) Fetch(ctx context.Context, fetchUrl string) (model.LinkPre
|
|||
if err != nil {
|
||||
log.Errorf("failed to decode request %s", err)
|
||||
}
|
||||
return res, decodedResponse, err
|
||||
return res, decodedResponse, false, nil
|
||||
}
|
||||
|
||||
func decodeResponse(response *proxyRoundTripper) ([]byte, error) {
|
||||
|
@ -149,7 +150,7 @@ func (l *linkPreview) findContent(data []byte) (content string) {
|
|||
return
|
||||
}
|
||||
|
||||
func (l *linkPreview) makeNonHtml(fetchUrl string, resp *http.Response) (i model.LinkPreview, err error) {
|
||||
func (l *linkPreview) makeNonHtml(fetchUrl string, resp *http.Response) (i model.LinkPreview, isFile bool, err error) {
|
||||
ct := resp.Header.Get("Content-Type")
|
||||
i.Url = fetchUrl
|
||||
i.Title = filepath.Base(fetchUrl)
|
||||
|
@ -161,6 +162,7 @@ func (l *linkPreview) makeNonHtml(fetchUrl string, resp *http.Response) (i model
|
|||
} else {
|
||||
i.Type = model.LinkPreview_Unknown
|
||||
}
|
||||
isFile = checkFileType(fetchUrl, resp, ct)
|
||||
pURL, e := uri.ParseURI(fetchUrl)
|
||||
if e == nil {
|
||||
pURL.Path = "favicon.ico"
|
||||
|
@ -170,6 +172,17 @@ func (l *linkPreview) makeNonHtml(fetchUrl string, resp *http.Response) (i model
|
|||
return
|
||||
}
|
||||
|
||||
func checkFileType(url string, resp *http.Response, contentType string) bool {
|
||||
ext := filepath.Ext(url)
|
||||
mimeType := mime.TypeByExtension(ext)
|
||||
return isContentFile(resp, contentType, mimeType)
|
||||
}
|
||||
|
||||
func isContentFile(resp *http.Response, contentType, mimeType string) bool {
|
||||
return contentType != "" || strings.Contains(resp.Header.Get("Content-Disposition"), "filename") ||
|
||||
mimeType != ""
|
||||
}
|
||||
|
||||
type proxyRoundTripper struct {
|
||||
http.RoundTripper
|
||||
lastResponse *http.Response
|
||||
|
|
|
@ -23,8 +23,9 @@ func TestLinkPreview_Fetch(t *testing.T) {
|
|||
lp := New()
|
||||
lp.Init(nil)
|
||||
|
||||
info, _, err := lp.Fetch(ctx, ts.URL)
|
||||
info, _, isFile, err := lp.Fetch(ctx, ts.URL)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, isFile)
|
||||
assert.Equal(t, model.LinkPreview{
|
||||
Url: ts.URL,
|
||||
FaviconUrl: ts.URL + "/favicon.ico",
|
||||
|
@ -41,7 +42,7 @@ func TestLinkPreview_Fetch(t *testing.T) {
|
|||
lp := New()
|
||||
lp.Init(nil)
|
||||
|
||||
info, _, err := lp.Fetch(ctx, ts.URL)
|
||||
info, _, isFile, err := lp.Fetch(ctx, ts.URL)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, model.LinkPreview{
|
||||
Url: ts.URL,
|
||||
|
@ -51,6 +52,7 @@ func TestLinkPreview_Fetch(t *testing.T) {
|
|||
ImageUrl: "http://site.com/images/example.jpg",
|
||||
Type: model.LinkPreview_Page,
|
||||
}, info)
|
||||
assert.False(t, isFile)
|
||||
})
|
||||
|
||||
t.Run("binary image", func(t *testing.T) {
|
||||
|
@ -60,7 +62,7 @@ func TestLinkPreview_Fetch(t *testing.T) {
|
|||
url := ts.URL + "/filename.jpg"
|
||||
lp := New()
|
||||
lp.Init(nil)
|
||||
info, _, err := lp.Fetch(ctx, url)
|
||||
info, _, isFile, err := lp.Fetch(ctx, url)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, model.LinkPreview{
|
||||
Url: url,
|
||||
|
@ -69,23 +71,48 @@ func TestLinkPreview_Fetch(t *testing.T) {
|
|||
ImageUrl: url,
|
||||
Type: model.LinkPreview_Image,
|
||||
}, info)
|
||||
assert.True(t, isFile)
|
||||
})
|
||||
|
||||
t.Run("binary", func(t *testing.T) {
|
||||
tr := testReader(0)
|
||||
ts := newTestServer("binary/octed-stream", &tr)
|
||||
defer ts.Close()
|
||||
url := ts.URL + "/filename.jpg"
|
||||
lp := New()
|
||||
lp.Init(nil)
|
||||
info, _, err := lp.Fetch(ctx, url)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, model.LinkPreview{
|
||||
Url: url,
|
||||
Title: "filename.jpg",
|
||||
FaviconUrl: ts.URL + "/favicon.ico",
|
||||
Type: model.LinkPreview_Unknown,
|
||||
}, info)
|
||||
t.Run("check content is file by extension", func(t *testing.T) {
|
||||
// given
|
||||
resp := &http.Response{Header: map[string][]string{}}
|
||||
|
||||
// when
|
||||
isFile := checkFileType("http://site.com/images/example.jpg", resp, "")
|
||||
|
||||
// then
|
||||
assert.True(t, isFile)
|
||||
})
|
||||
t.Run("check content is file by content-type", func(t *testing.T) {
|
||||
// given
|
||||
resp := &http.Response{Header: map[string][]string{}}
|
||||
|
||||
// when
|
||||
isFile := checkFileType("htt://example.com/filepath", resp, "application/pdf")
|
||||
|
||||
// then
|
||||
assert.True(t, isFile)
|
||||
})
|
||||
t.Run("check content is file by content-disposition", func(t *testing.T) {
|
||||
// given
|
||||
resp := &http.Response{Header: map[string][]string{"Content-Disposition": {"attachment filename=\"user.csv\""}}}
|
||||
|
||||
// when
|
||||
isFile := checkFileType("htt://example.com/filepath", resp, "")
|
||||
|
||||
// then
|
||||
assert.True(t, isFile)
|
||||
})
|
||||
t.Run("check content is not file", func(t *testing.T) {
|
||||
// given
|
||||
resp := &http.Response{Header: map[string][]string{}}
|
||||
|
||||
// when
|
||||
isFile := checkFileType("htt://example.com/notfile", resp, "")
|
||||
|
||||
// then
|
||||
assert.False(t, isFile)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
204
util/linkpreview/mock_linkpreview/mock_LinkPreview.go
Normal file
204
util/linkpreview/mock_linkpreview/mock_LinkPreview.go
Normal file
|
@ -0,0 +1,204 @@
|
|||
// Code generated by mockery. DO NOT EDIT.
|
||||
|
||||
package mock_linkpreview
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
app "github.com/anyproto/any-sync/app"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
model "github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
)
|
||||
|
||||
// MockLinkPreview is an autogenerated mock type for the LinkPreview type
|
||||
type MockLinkPreview struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockLinkPreview_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockLinkPreview) EXPECT() *MockLinkPreview_Expecter {
|
||||
return &MockLinkPreview_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Fetch provides a mock function with given fields: ctx, url
|
||||
func (_m *MockLinkPreview) Fetch(ctx context.Context, url string) (model.LinkPreview, []byte, bool, error) {
|
||||
ret := _m.Called(ctx, url)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Fetch")
|
||||
}
|
||||
|
||||
var r0 model.LinkPreview
|
||||
var r1 []byte
|
||||
var r2 bool
|
||||
var r3 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) (model.LinkPreview, []byte, bool, error)); ok {
|
||||
return rf(ctx, url)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) model.LinkPreview); ok {
|
||||
r0 = rf(ctx, url)
|
||||
} else {
|
||||
r0 = ret.Get(0).(model.LinkPreview)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string) []byte); ok {
|
||||
r1 = rf(ctx, url)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).([]byte)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(2).(func(context.Context, string) bool); ok {
|
||||
r2 = rf(ctx, url)
|
||||
} else {
|
||||
r2 = ret.Get(2).(bool)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(3).(func(context.Context, string) error); ok {
|
||||
r3 = rf(ctx, url)
|
||||
} else {
|
||||
r3 = ret.Error(3)
|
||||
}
|
||||
|
||||
return r0, r1, r2, r3
|
||||
}
|
||||
|
||||
// MockLinkPreview_Fetch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Fetch'
|
||||
type MockLinkPreview_Fetch_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Fetch is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - url string
|
||||
func (_e *MockLinkPreview_Expecter) Fetch(ctx interface{}, url interface{}) *MockLinkPreview_Fetch_Call {
|
||||
return &MockLinkPreview_Fetch_Call{Call: _e.mock.On("Fetch", ctx, url)}
|
||||
}
|
||||
|
||||
func (_c *MockLinkPreview_Fetch_Call) Run(run func(ctx context.Context, url string)) *MockLinkPreview_Fetch_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockLinkPreview_Fetch_Call) Return(_a0 model.LinkPreview, _a1 []byte, _a2 bool, _a3 error) *MockLinkPreview_Fetch_Call {
|
||||
_c.Call.Return(_a0, _a1, _a2, _a3)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockLinkPreview_Fetch_Call) RunAndReturn(run func(context.Context, string) (model.LinkPreview, []byte, bool, error)) *MockLinkPreview_Fetch_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Init provides a mock function with given fields: a
|
||||
func (_m *MockLinkPreview) Init(a *app.App) error {
|
||||
ret := _m.Called(a)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Init")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*app.App) error); ok {
|
||||
r0 = rf(a)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockLinkPreview_Init_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Init'
|
||||
type MockLinkPreview_Init_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Init is a helper method to define mock.On call
|
||||
// - a *app.App
|
||||
func (_e *MockLinkPreview_Expecter) Init(a interface{}) *MockLinkPreview_Init_Call {
|
||||
return &MockLinkPreview_Init_Call{Call: _e.mock.On("Init", a)}
|
||||
}
|
||||
|
||||
func (_c *MockLinkPreview_Init_Call) Run(run func(a *app.App)) *MockLinkPreview_Init_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(*app.App))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockLinkPreview_Init_Call) Return(err error) *MockLinkPreview_Init_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockLinkPreview_Init_Call) RunAndReturn(run func(*app.App) error) *MockLinkPreview_Init_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Name provides a mock function with given fields:
|
||||
func (_m *MockLinkPreview) Name() string {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Name")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockLinkPreview_Name_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Name'
|
||||
type MockLinkPreview_Name_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Name is a helper method to define mock.On call
|
||||
func (_e *MockLinkPreview_Expecter) Name() *MockLinkPreview_Name_Call {
|
||||
return &MockLinkPreview_Name_Call{Call: _e.mock.On("Name")}
|
||||
}
|
||||
|
||||
func (_c *MockLinkPreview_Name_Call) Run(run func()) *MockLinkPreview_Name_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockLinkPreview_Name_Call) Return(name string) *MockLinkPreview_Name_Call {
|
||||
_c.Call.Return(name)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockLinkPreview_Name_Call) RunAndReturn(run func() string) *MockLinkPreview_Name_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockLinkPreview creates a new instance of MockLinkPreview. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockLinkPreview(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockLinkPreview {
|
||||
mock := &MockLinkPreview{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
|
@ -233,6 +233,21 @@ func (mr *MockObjectStoreMockRecorder) GetAccountStatus() *gomock.Call {
|
|||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccountStatus", reflect.TypeOf((*MockObjectStore)(nil).GetAccountStatus))
|
||||
}
|
||||
|
||||
// GetActiveViews mocks base method.
|
||||
func (m *MockObjectStore) GetActiveViews(arg0 string) (map[string]string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetActiveViews", arg0)
|
||||
ret0, _ := ret[0].(map[string]string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetActiveViews indicates an expected call of GetActiveViews.
|
||||
func (mr *MockObjectStoreMockRecorder) GetActiveViews(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActiveViews", reflect.TypeOf((*MockObjectStore)(nil).GetActiveViews), arg0)
|
||||
}
|
||||
|
||||
// GetByIDs mocks base method.
|
||||
func (m *MockObjectStore) GetByIDs(arg0 string, arg1 []string) ([]*model.ObjectInfo, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
@ -767,6 +782,34 @@ func (mr *MockObjectStoreMockRecorder) SaveVirtualSpace(arg0 any) *gomock.Call {
|
|||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveVirtualSpace", reflect.TypeOf((*MockObjectStore)(nil).SaveVirtualSpace), arg0)
|
||||
}
|
||||
|
||||
// SetActiveView mocks base method.
|
||||
func (m *MockObjectStore) SetActiveView(arg0, arg1, arg2 string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SetActiveView", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SetActiveView indicates an expected call of SetActiveView.
|
||||
func (mr *MockObjectStoreMockRecorder) SetActiveView(arg0, arg1, arg2 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetActiveView", reflect.TypeOf((*MockObjectStore)(nil).SetActiveView), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// SetActiveViews mocks base method.
|
||||
func (m *MockObjectStore) SetActiveViews(arg0 string, arg1 map[string]string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SetActiveViews", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SetActiveViews indicates an expected call of SetActiveViews.
|
||||
func (mr *MockObjectStoreMockRecorder) SetActiveViews(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetActiveViews", reflect.TypeOf((*MockObjectStore)(nil).SetActiveViews), arg0, arg1)
|
||||
}
|
||||
|
||||
// SubscribeForAll mocks base method.
|
||||
func (m *MockObjectStore) SubscribeForAll(arg0 func(database.Record)) {
|
||||
m.ctrl.T.Helper()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue