mirror of
https://github.com/anyproto/anytype-heart.git
synced 2025-06-10 01:51:07 +09:00
[GO-678-dep-licence] Merge branch 'master' into GO-678-dep-licence
This commit is contained in:
commit
ee9c6944d2
60 changed files with 29839 additions and 5965 deletions
|
@ -4,6 +4,7 @@ import (
|
|||
"archive/zip"
|
||||
"bytes"
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
@ -35,8 +36,6 @@ import (
|
|||
"github.com/anytypeio/go-anytype-middleware/pkg/lib/pb/model"
|
||||
"github.com/anytypeio/go-anytype-middleware/pkg/lib/threads"
|
||||
"github.com/anytypeio/go-anytype-middleware/util/pbtypes"
|
||||
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
const CName = "builtinobjects"
|
||||
|
@ -59,7 +58,7 @@ type BuiltinObjects interface {
|
|||
}
|
||||
|
||||
type objectCreator interface {
|
||||
CreateSmartBlockFromState(ctx context.Context, sbType coresb.SmartBlockType, details *types.Struct, relationIds []string, createState *state.State) (id string, newDetails *types.Struct, err error)
|
||||
CreateSmartBlockFromState(ctx context.Context, sbType coresb.SmartBlockType, details *types.Struct, createState *state.State) (id string, newDetails *types.Struct, err error)
|
||||
}
|
||||
|
||||
type builtinObjects struct {
|
||||
|
@ -117,6 +116,11 @@ func (b *builtinObjects) inject(ctx context.Context) (err error) {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if sbt == smartblock.SmartBlockTypeSubObject {
|
||||
// preserve original id for subobjects, it makes no sense to replace them and also it breaks the grouping
|
||||
b.idsMap[id] = id
|
||||
continue
|
||||
}
|
||||
tid, err := threads.ThreadCreateID(thread.AccessControlled, sbt)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -165,13 +169,16 @@ func (b *builtinObjects) createObject(ctx context.Context, rd io.ReadCloser) (er
|
|||
}
|
||||
m.Fields = &types.Struct{Fields: f}
|
||||
f["analyticsContext"] = pbtypes.String(analyticsContext)
|
||||
f["analyticsOriginalId"] = pbtypes.String(oldId)
|
||||
if f["analyticsOriginalId"] == nil {
|
||||
// in case we already have analyticsOriginalId do not update it
|
||||
f["analyticsOriginalId"] = pbtypes.String(oldId)
|
||||
}
|
||||
|
||||
st.Set(simple.New(m))
|
||||
rels := relationutils.MigrateRelationModels(st.OldExtraRelations())
|
||||
st.AddRelationLinks(rels...)
|
||||
|
||||
st.RemoveDetail(bundle.RelationKeyCreator.String(), bundle.RelationKeyLastModifiedBy.String())
|
||||
st.RemoveDetail(bundle.RelationKeyCreator.String(), bundle.RelationKeyLastModifiedBy.String(), bundle.RelationKeyLastOpenedDate.String(), bundle.RelationKeyLinks.String())
|
||||
st.SetLocalDetail(bundle.RelationKeyCreator.String(), pbtypes.String(addr.AnytypeProfileId))
|
||||
st.SetLocalDetail(bundle.RelationKeyLastModifiedBy.String(), pbtypes.String(addr.AnytypeProfileId))
|
||||
st.InjectDerivedDetails()
|
||||
|
@ -225,7 +232,7 @@ func (b *builtinObjects) createObject(ctx context.Context, rd io.ReadCloser) (er
|
|||
log.With("object", oldId).Errorf("failed to find relation %s: %s", k, err.Error())
|
||||
continue
|
||||
}
|
||||
if rel.Format != model.RelationFormat_object {
|
||||
if rel.Format != model.RelationFormat_object && rel.Format != model.RelationFormat_tag && rel.Format != model.RelationFormat_status {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -250,7 +257,7 @@ func (b *builtinObjects) createObject(ctx context.Context, rd io.ReadCloser) (er
|
|||
return err
|
||||
}
|
||||
|
||||
_, _, err = b.objectCreator.CreateSmartBlockFromState(ctx, sbt, nil, nil, st)
|
||||
_, _, err = b.objectCreator.CreateSmartBlockFromState(ctx, sbt, nil, st)
|
||||
if isFavorite {
|
||||
err = b.service.SetPageIsFavorite(pb.RpcObjectSetIsFavoriteRequest{ContextId: newId, IsFavorite: true})
|
||||
if err != nil {
|
||||
|
|
Binary file not shown.
|
@ -5,9 +5,7 @@ import (
|
|||
|
||||
"github.com/gogo/protobuf/types"
|
||||
|
||||
"github.com/anytypeio/go-anytype-middleware/pb"
|
||||
"github.com/anytypeio/go-anytype-middleware/pkg/lib/pb/model"
|
||||
"github.com/anytypeio/go-anytype-middleware/util/slice"
|
||||
)
|
||||
|
||||
var bytesPool = &sync.Pool{
|
||||
|
@ -142,37 +140,3 @@ func CopyFilter(in *model.BlockContentDataviewFilter) (out *model.BlockContentDa
|
|||
bytesPool.Put(buf)
|
||||
return
|
||||
}
|
||||
|
||||
func EventsToSliceChange(changes []*pb.EventBlockDataviewSliceChange) []slice.Change {
|
||||
sliceOpMap := map[pb.EventBlockDataviewSliceOperation]slice.DiffOperation{
|
||||
pb.EventBlockDataview_SliceOperationNone: slice.OperationNone,
|
||||
pb.EventBlockDataview_SliceOperationAdd: slice.OperationAdd,
|
||||
pb.EventBlockDataview_SliceOperationMove: slice.OperationMove,
|
||||
pb.EventBlockDataview_SliceOperationRemove: slice.OperationRemove,
|
||||
pb.EventBlockDataview_SliceOperationReplace: slice.OperationReplace,
|
||||
}
|
||||
|
||||
var res []slice.Change
|
||||
for _, eventCh := range changes {
|
||||
res = append(res, slice.Change{Op: sliceOpMap[eventCh.Op], Ids: eventCh.Ids, AfterId: eventCh.AfterId})
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func SliceChangeToEvents(changes []slice.Change) []*pb.EventBlockDataviewSliceChange {
|
||||
eventsOpMap := map[slice.DiffOperation]pb.EventBlockDataviewSliceOperation{
|
||||
slice.OperationNone: pb.EventBlockDataview_SliceOperationNone,
|
||||
slice.OperationAdd: pb.EventBlockDataview_SliceOperationAdd,
|
||||
slice.OperationMove: pb.EventBlockDataview_SliceOperationMove,
|
||||
slice.OperationRemove: pb.EventBlockDataview_SliceOperationRemove,
|
||||
slice.OperationReplace: pb.EventBlockDataview_SliceOperationReplace,
|
||||
}
|
||||
|
||||
var res []*pb.EventBlockDataviewSliceChange
|
||||
for _, sliceCh := range changes {
|
||||
res = append(res, &pb.EventBlockDataviewSliceChange{Op: eventsOpMap[sliceCh.Op], Ids: sliceCh.Ids, AfterId: sliceCh.AfterId})
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
|
|
@ -65,6 +65,13 @@ func IntList(ints ...int) *types.Value {
|
|||
}
|
||||
}
|
||||
|
||||
func NilToNullWrapper(v *types.Value) *types.Value {
|
||||
if v == nil || v.Kind == nil {
|
||||
return Null()
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func Bool(v bool) *types.Value {
|
||||
return &types.Value{
|
||||
Kind: &types.Value_BoolValue{BoolValue: v},
|
||||
|
@ -197,11 +204,11 @@ func GetStringListValue(v *types.Value) []string {
|
|||
return nil
|
||||
}
|
||||
for _, v := range list.ListValue.Values {
|
||||
if _, ok = v.GetKind().(*types.Value_StringValue); ok {
|
||||
if _, ok := v.GetKind().(*types.Value_StringValue); ok {
|
||||
stringsSlice = append(stringsSlice, v.GetStringValue())
|
||||
}
|
||||
}
|
||||
} else if val, ok := v.Kind.(*types.Value_StringValue); ok {
|
||||
} else if val, ok := v.Kind.(*types.Value_StringValue); ok && val.StringValue != "" {
|
||||
return []string{val.StringValue}
|
||||
}
|
||||
|
||||
|
@ -421,3 +428,15 @@ func StructCompareIgnoreKeys(st1 *types.Struct, st2 *types.Struct, ignoreKeys []
|
|||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ValueListWrapper wraps single value into the list. If value is already a list, it is returned as is.
|
||||
// Null and struct values are not supported
|
||||
func ValueListWrapper(value *types.Value) (*types.ListValue, error) {
|
||||
switch v := value.Kind.(type) {
|
||||
case *types.Value_ListValue:
|
||||
return v.ListValue, nil
|
||||
case *types.Value_StringValue, *types.Value_NumberValue, *types.Value_BoolValue:
|
||||
return &types.ListValue{Values: []*types.Value{value}}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("not supported type")
|
||||
}
|
||||
|
|
|
@ -1,134 +1,294 @@
|
|||
package slice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mb0/diff"
|
||||
)
|
||||
|
||||
type DiffOperation int
|
||||
|
||||
const (
|
||||
OperationNone DiffOperation = iota
|
||||
OperationAdd
|
||||
OperationMove
|
||||
OperationRemove
|
||||
OperationReplace
|
||||
)
|
||||
|
||||
type Change struct {
|
||||
Op DiffOperation
|
||||
Ids []string
|
||||
AfterId string
|
||||
func StringIdentity[T ~string](x T) string {
|
||||
return string(x)
|
||||
}
|
||||
|
||||
type MixedInput struct {
|
||||
A []string
|
||||
B []string
|
||||
func Equal[T comparable](a, b T) bool {
|
||||
return a == b
|
||||
}
|
||||
|
||||
func (m *MixedInput) Equal(a, b int) bool {
|
||||
return m.A[a] == m.B[b]
|
||||
type Change[T any] struct {
|
||||
changeAdd *ChangeAdd[T]
|
||||
changeRemove *ChangeRemove
|
||||
changeMove *ChangeMove
|
||||
changeReplace *ChangeReplace[T]
|
||||
}
|
||||
|
||||
func Diff(origin, changed []string) []Change {
|
||||
m := &MixedInput{
|
||||
func (c Change[T]) String() string {
|
||||
switch {
|
||||
case c.changeAdd != nil:
|
||||
return c.changeAdd.String()
|
||||
case c.changeRemove != nil:
|
||||
return c.changeRemove.String()
|
||||
case c.changeMove != nil:
|
||||
return c.changeMove.String()
|
||||
case c.changeReplace != nil:
|
||||
return c.changeReplace.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func MakeChangeAdd[T any](items []T, afterID string) Change[T] {
|
||||
return Change[T]{
|
||||
changeAdd: &ChangeAdd[T]{items, afterID},
|
||||
}
|
||||
}
|
||||
|
||||
func MakeChangeRemove[T any](ids []string) Change[T] {
|
||||
return Change[T]{
|
||||
changeRemove: &ChangeRemove{ids},
|
||||
}
|
||||
}
|
||||
|
||||
func MakeChangeMove[T any](ids []string, afterID string) Change[T] {
|
||||
return Change[T]{
|
||||
changeMove: &ChangeMove{ids, afterID},
|
||||
}
|
||||
}
|
||||
|
||||
func MakeChangeReplace[T any](item T, id string) Change[T] {
|
||||
return Change[T]{
|
||||
changeReplace: &ChangeReplace[T]{item, id},
|
||||
}
|
||||
}
|
||||
|
||||
func (c Change[T]) Len() int {
|
||||
if c.changeAdd != nil {
|
||||
return len(c.changeAdd.Items)
|
||||
}
|
||||
if c.changeRemove != nil {
|
||||
return len(c.changeRemove.IDs)
|
||||
}
|
||||
if c.changeMove != nil {
|
||||
return len(c.changeMove.IDs)
|
||||
}
|
||||
if c.changeReplace != nil {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *Change[T]) Add() *ChangeAdd[T] {
|
||||
return c.changeAdd
|
||||
}
|
||||
|
||||
func (c *Change[T]) Remove() *ChangeRemove {
|
||||
return c.changeRemove
|
||||
}
|
||||
|
||||
func (c *Change[T]) Move() *ChangeMove {
|
||||
return c.changeMove
|
||||
}
|
||||
|
||||
func (c *Change[T]) Replace() *ChangeReplace[T] {
|
||||
return c.changeReplace
|
||||
}
|
||||
|
||||
type ChangeAdd[T any] struct {
|
||||
Items []T
|
||||
AfterID string
|
||||
}
|
||||
|
||||
func (c ChangeAdd[T]) String() string {
|
||||
return fmt.Sprintf("add %v after %s", c.Items, c.AfterID)
|
||||
}
|
||||
|
||||
type ChangeMove struct {
|
||||
IDs []string
|
||||
AfterID string
|
||||
}
|
||||
|
||||
func (c ChangeMove) String() string {
|
||||
return fmt.Sprintf("move %v after %s", c.IDs, c.AfterID)
|
||||
}
|
||||
|
||||
type ChangeRemove struct {
|
||||
IDs []string
|
||||
}
|
||||
|
||||
func (c ChangeRemove) String() string {
|
||||
return fmt.Sprintf("remove %v", c.IDs)
|
||||
}
|
||||
|
||||
type ChangeReplace[T any] struct {
|
||||
Item T
|
||||
ID string
|
||||
}
|
||||
|
||||
func (c ChangeReplace[T]) String() string {
|
||||
return fmt.Sprintf("replace %v after %s", c.Item, c.ID)
|
||||
}
|
||||
|
||||
type MixedInput[T any] struct {
|
||||
A []T
|
||||
B []T
|
||||
getID func(T) string
|
||||
}
|
||||
|
||||
func (m *MixedInput[T]) Equal(a, b int) bool {
|
||||
return m.getID(m.A[a]) == m.getID(m.B[b])
|
||||
}
|
||||
|
||||
func Diff[T any](origin, changed []T, getID func(T) string, equal func(T, T) bool) []Change[T] {
|
||||
m := &MixedInput[T]{
|
||||
origin,
|
||||
changed,
|
||||
getID,
|
||||
}
|
||||
|
||||
var result []Change
|
||||
var result []Change[T]
|
||||
|
||||
changes := diff.Diff(len(m.A), len(m.B), m)
|
||||
delMap := make(map[string]bool)
|
||||
for _, c := range changes {
|
||||
if c.Del > 0 {
|
||||
for _, id := range m.A[c.A:c.A+c.Del] {
|
||||
delMap[id] = true
|
||||
}
|
||||
delMap := make(map[string]struct{})
|
||||
|
||||
changedMap := make(map[string]T)
|
||||
for _, c := range changed {
|
||||
changedMap[getID(c)] = c
|
||||
}
|
||||
for _, c := range origin {
|
||||
v, ok := changedMap[getID(c)]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if !equal(c, v) {
|
||||
result = append(result, MakeChangeReplace[T](v, getID(c)))
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range changes {
|
||||
if c.Ins > 0 {
|
||||
inserts := m.B[c.B:c.B+c.Ins]
|
||||
afterId := ""
|
||||
if c.A > 0 {
|
||||
afterId = m.A[c.A-1]
|
||||
}
|
||||
var oneCh Change
|
||||
for _, id := range inserts {
|
||||
if delMap[id] { // move
|
||||
if oneCh.Op != OperationMove {
|
||||
if len(oneCh.Ids) > 0 {
|
||||
result = append(result, oneCh)
|
||||
}
|
||||
oneCh = Change{Op: OperationMove, AfterId: afterId}
|
||||
}
|
||||
oneCh.Ids = append(oneCh.Ids, id)
|
||||
delete(delMap, id)
|
||||
} else { // insert new
|
||||
if oneCh.Op != OperationAdd {
|
||||
if len(oneCh.Ids) > 0 {
|
||||
result = append(result, oneCh)
|
||||
}
|
||||
oneCh = Change{Op: OperationAdd, AfterId: afterId}
|
||||
}
|
||||
oneCh.Ids = append(oneCh.Ids, id)
|
||||
}
|
||||
afterId = id
|
||||
}
|
||||
|
||||
if len(oneCh.Ids) > 0 {
|
||||
result = append(result, oneCh)
|
||||
}
|
||||
if c.Del <= 0 {
|
||||
continue
|
||||
}
|
||||
for _, it := range m.A[c.A : c.A+c.Del] {
|
||||
delMap[getID(it)] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range changes {
|
||||
if c.Ins <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
inserts := m.B[c.B : c.B+c.Ins]
|
||||
var afterID string
|
||||
if c.A > 0 {
|
||||
afterID = getID(m.A[c.A-1])
|
||||
}
|
||||
var oneCh Change[T]
|
||||
for _, it := range inserts {
|
||||
id := getID(it)
|
||||
|
||||
// If inserted item is found in deleted map then it is a Move operation
|
||||
// nolint:nestif
|
||||
if _, ok := delMap[id]; ok {
|
||||
mv := oneCh.Move()
|
||||
if mv == nil {
|
||||
if oneCh.Len() > 0 {
|
||||
result = append(result, oneCh)
|
||||
}
|
||||
oneCh = MakeChangeMove[T](nil, afterID)
|
||||
mv = oneCh.Move()
|
||||
}
|
||||
mv.IDs = append(mv.IDs, getID(it))
|
||||
delete(delMap, id)
|
||||
} else { // insert new
|
||||
add := oneCh.Add()
|
||||
if add == nil {
|
||||
if oneCh.Len() > 0 {
|
||||
result = append(result, oneCh)
|
||||
}
|
||||
oneCh = MakeChangeAdd[T](nil, afterID)
|
||||
add = oneCh.Add()
|
||||
}
|
||||
add.Items = append(add.Items, it)
|
||||
}
|
||||
afterID = id
|
||||
}
|
||||
|
||||
if oneCh.Len() > 0 {
|
||||
result = append(result, oneCh)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if len(delMap) > 0 { // remove
|
||||
delIds := make([]string, 0, len(delMap))
|
||||
delIDs := make([]string, 0, len(delMap))
|
||||
for id := range delMap {
|
||||
delIds = append(delIds, id)
|
||||
delIDs = append(delIDs, id)
|
||||
}
|
||||
result = append(result, Change{Op: OperationRemove, Ids: delIds})
|
||||
result = append(result, MakeChangeRemove[T](delIDs))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func ApplyChanges(origin []string, changes []Change) []string {
|
||||
result := make([]string, len(origin))
|
||||
copy(result, origin)
|
||||
func findPos[T any](s []T, getID func(T) string, id string) int {
|
||||
for i, sv := range s {
|
||||
if getID(sv) == id {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func ApplyChanges[T any](origin []T, changes []Change[T], getID func(T) string) []T {
|
||||
res := make([]T, len(origin))
|
||||
copy(res, origin)
|
||||
|
||||
itemsMap := make(map[string]T, len(origin))
|
||||
for _, it := range origin {
|
||||
itemsMap[getID(it)] = it
|
||||
}
|
||||
|
||||
for _, ch := range changes {
|
||||
switch ch.Op {
|
||||
case OperationAdd:
|
||||
if add := ch.Add(); add != nil {
|
||||
pos := -1
|
||||
if ch.AfterId != "" {
|
||||
pos = FindPos(result, ch.AfterId)
|
||||
if pos < 0 {
|
||||
if add.AfterID != "" {
|
||||
pos = findPos(res, getID, add.AfterID)
|
||||
}
|
||||
res = Insert(res, pos+1, add.Items...)
|
||||
}
|
||||
|
||||
if move := ch.Move(); move != nil {
|
||||
res = FilterMut(res, func(id T) bool {
|
||||
return FindPos(move.IDs, getID(id)) < 0
|
||||
})
|
||||
|
||||
pos := -1
|
||||
if move.AfterID != "" {
|
||||
pos = findPos(res, getID, move.AfterID)
|
||||
}
|
||||
items := make([]T, 0, len(move.IDs))
|
||||
for _, id := range move.IDs {
|
||||
v, ok := itemsMap[id]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
items = append(items, v)
|
||||
}
|
||||
result = Insert(result, pos+1, ch.Ids...)
|
||||
case OperationMove:
|
||||
withoutMoved := Filter(result, func(id string) bool {
|
||||
return FindPos(ch.Ids, id) < 0
|
||||
res = Insert(res, pos+1, items...)
|
||||
}
|
||||
|
||||
if rm := ch.Remove(); rm != nil {
|
||||
res = FilterMut(res, func(id T) bool {
|
||||
return FindPos(rm.IDs, getID(id)) < 0
|
||||
})
|
||||
pos := -1
|
||||
if ch.AfterId != "" {
|
||||
pos = FindPos(withoutMoved, ch.AfterId)
|
||||
if pos < 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if replace := ch.Replace(); replace != nil {
|
||||
itemsMap[replace.ID] = replace.Item
|
||||
pos := findPos(res, getID, replace.ID)
|
||||
if pos >= 0 && pos < len(res) {
|
||||
res[pos] = replace.Item
|
||||
}
|
||||
result = Insert(withoutMoved, pos+1, ch.Ids...)
|
||||
case OperationRemove:
|
||||
result = Filter(result, func(id string) bool{
|
||||
return FindPos(ch.Ids, id) < 0
|
||||
})
|
||||
case OperationReplace:
|
||||
result = ch.Ids
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
return res
|
||||
}
|
||||
|
|
|
@ -1,57 +1,101 @@
|
|||
package slice
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
"time"
|
||||
|
||||
"github.com/globalsign/mgo/bson"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test_Diff(t *testing.T) {
|
||||
origin := []string{"000", "001", "002", "003", "004", "005", "006", "007", "008", "009"}
|
||||
changed := []string{"000", "008", "001", "002", "003", "005", "006", "007", "009", "004"}
|
||||
|
||||
chs := Diff(origin, changed)
|
||||
chs := Diff(origin, changed, StringIdentity[string], Equal[string])
|
||||
|
||||
assert.Equal(t, chs, []Change{
|
||||
{Op: OperationMove, Ids: []string{"008"}, AfterId: "000"},
|
||||
{Op: OperationMove, Ids: []string{"004"}, AfterId: "009"}},
|
||||
)
|
||||
assert.Equal(t, chs, []Change[string]{
|
||||
MakeChangeMove[string]([]string{"008"}, "000"),
|
||||
MakeChangeMove[string]([]string{"004"}, "009"),
|
||||
})
|
||||
}
|
||||
|
||||
type testItem struct {
|
||||
id string
|
||||
someField int
|
||||
}
|
||||
|
||||
func Test_Replace(t *testing.T) {
|
||||
origin := []testItem{
|
||||
{"000", 100},
|
||||
{"001", 101},
|
||||
{"002", 102},
|
||||
}
|
||||
changed := []testItem{
|
||||
{"001", 101},
|
||||
{"002", 102},
|
||||
{"000", 103},
|
||||
}
|
||||
|
||||
getID := func(a testItem) string {
|
||||
return a.id
|
||||
}
|
||||
chs := Diff(origin, changed, getID, func(a, b testItem) bool {
|
||||
if a.id != b.id {
|
||||
return false
|
||||
}
|
||||
return a.someField == b.someField
|
||||
})
|
||||
|
||||
assert.Equal(t, []Change[testItem]{
|
||||
MakeChangeReplace(testItem{"000", 103}, "000"),
|
||||
MakeChangeMove[testItem]([]string{"000"}, "002"),
|
||||
}, chs)
|
||||
|
||||
got := ApplyChanges(origin, chs, getID)
|
||||
|
||||
assert.Equal(t, changed, got)
|
||||
}
|
||||
|
||||
func Test_ChangesApply(t *testing.T) {
|
||||
origin := []string{"000", "001", "002", "003", "004", "005", "006", "007", "008", "009"}
|
||||
changed := []string{"000", "008", "001", "002", "003", "005", "006", "007", "009", "004", "new"}
|
||||
|
||||
chs := Diff(origin, changed)
|
||||
chs := Diff(origin, changed, StringIdentity[string], Equal[string])
|
||||
|
||||
res := ApplyChanges(origin, chs)
|
||||
res := ApplyChanges(origin, chs, StringIdentity[string])
|
||||
|
||||
assert.Equal(t, changed, res)
|
||||
}
|
||||
|
||||
type uniqueID string
|
||||
|
||||
func (id uniqueID) Generate(rand *rand.Rand, size int) reflect.Value {
|
||||
return reflect.ValueOf(uniqueID(bson.NewObjectId().Hex()))
|
||||
}
|
||||
|
||||
func Test_SameLength(t *testing.T) {
|
||||
for i := 0; i < 10000; i++ {
|
||||
l := randNum(5, 200)
|
||||
origin := getRandArray(l)
|
||||
changed := make([]string, len(origin))
|
||||
f := func(origin []uniqueID) bool {
|
||||
changed := make([]uniqueID, len(origin))
|
||||
copy(changed, origin)
|
||||
rand.Shuffle(len(changed),
|
||||
func(i, j int) { changed[i], changed[j] = changed[j], changed[i] })
|
||||
|
||||
chs := Diff(origin, changed)
|
||||
res := ApplyChanges(origin, chs)
|
||||
chs := Diff(origin, changed, StringIdentity[uniqueID], Equal[uniqueID])
|
||||
res := ApplyChanges(origin, chs, StringIdentity[uniqueID])
|
||||
|
||||
assert.Equal(t, res, changed)
|
||||
return assert.Equal(t, res, changed)
|
||||
}
|
||||
|
||||
assert.NoError(t, quick.Check(f, nil))
|
||||
}
|
||||
|
||||
func Test_DifferentLength(t *testing.T) {
|
||||
for i := 0; i < 10000; i++ {
|
||||
l := randNum(5, 200)
|
||||
origin := getRandArray(l)
|
||||
changed := make([]string, len(origin))
|
||||
f := func(origin []uniqueID) bool {
|
||||
changed := make([]uniqueID, len(origin))
|
||||
copy(changed, origin)
|
||||
rand.Shuffle(len(changed),
|
||||
func(i, j int) { changed[i], changed[j] = changed[j], changed[i] })
|
||||
|
@ -73,28 +117,83 @@ func Test_DifferentLength(t *testing.T) {
|
|||
continue
|
||||
}
|
||||
insIdx := randNum(0, l)
|
||||
changed = Insert(changed, insIdx, []string{bson.NewObjectId().Hex()}...)
|
||||
changed = Insert(changed, insIdx, []uniqueID{uniqueID(bson.NewObjectId().Hex())}...)
|
||||
}
|
||||
|
||||
chs := Diff(origin, changed)
|
||||
res := ApplyChanges(origin, chs)
|
||||
chs := Diff(origin, changed, StringIdentity[uniqueID], Equal[uniqueID])
|
||||
res := ApplyChanges(origin, chs, StringIdentity[uniqueID])
|
||||
|
||||
assert.Equal(t, res, changed)
|
||||
return assert.Equal(t, res, changed)
|
||||
}
|
||||
|
||||
assert.NoError(t, quick.Check(f, nil))
|
||||
}
|
||||
|
||||
func randNum(min, max int) int{
|
||||
// nolint:gosec
|
||||
func randNum(min, max int) int {
|
||||
if max <= min {
|
||||
return max
|
||||
}
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
return rand.Intn(max - min) + min
|
||||
return rand.Intn(max-min) + min
|
||||
}
|
||||
|
||||
func getRandArray(len int) []string {
|
||||
res := make([]string, len)
|
||||
for i := 0; i < len; i++ {
|
||||
res[i] = bson.NewObjectId().Hex()
|
||||
// nolint:gosec
|
||||
func genTestItems(count int) []*testItem {
|
||||
items := make([]*testItem, count)
|
||||
for i := 0; i < count; i++ {
|
||||
items[i] = &testItem{id: bson.NewObjectId().Hex(), someField: rand.Intn(1000)}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
/*
|
||||
Original
|
||||
BenchmarkApplyChanges-8 3135 433618 ns/op 540552 B/op 558 allocs/op
|
||||
|
||||
Use FilterMut that reuses original slice capacity
|
||||
BenchmarkApplyChanges-8 4134 346602 ns/op 90448 B/op 206 allocs/op
|
||||
|
||||
Remove dropping items in Move operations if afterID is not found (can cause data loss)
|
||||
BenchmarkApplyChanges-8 2691 421134 ns/op 236785 B/op 385 allocs/op
|
||||
*/
|
||||
func BenchmarkApplyChanges(b *testing.B) {
|
||||
const itemsCount = 100
|
||||
items := genTestItems(itemsCount)
|
||||
|
||||
changes := make([]Change[*testItem], 500)
|
||||
for i := 0; i < 500; i++ {
|
||||
switch rand.Intn(4) {
|
||||
case 0:
|
||||
it := items[rand.Intn(itemsCount)]
|
||||
newItem := &(*it)
|
||||
newItem.someField = rand.Intn(1000)
|
||||
changes[i] = MakeChangeReplace(newItem, it.id)
|
||||
case 1:
|
||||
idx := rand.Intn(itemsCount + 1)
|
||||
var id string
|
||||
// Let it be a chance to use empty AfterID
|
||||
if idx < itemsCount {
|
||||
id = items[idx].id
|
||||
}
|
||||
changes[i] = MakeChangeAdd(genTestItems(rand.Intn(2)+1), id)
|
||||
case 2:
|
||||
changes[i] = MakeChangeRemove[*testItem]([]string{items[rand.Intn(itemsCount)].id})
|
||||
case 3:
|
||||
idx := rand.Intn(itemsCount + 1)
|
||||
var id string
|
||||
// Let it be a chance to use empty AfterID
|
||||
if idx < itemsCount {
|
||||
id = items[idx].id
|
||||
}
|
||||
changes[i] = MakeChangeMove[*testItem]([]string{items[rand.Intn(itemsCount)].id}, id)
|
||||
}
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = ApplyChanges(items, changes, func(a *testItem) string {
|
||||
return a.id
|
||||
})
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ func DifferenceRemovedAdded(a, b []string) (removed []string, added []string) {
|
|||
return
|
||||
}
|
||||
|
||||
func FindPos(s []string, v string) int {
|
||||
func FindPos[T comparable](s []T, v T) int {
|
||||
for i, sv := range s {
|
||||
if sv == v {
|
||||
return i
|
||||
|
@ -51,6 +51,15 @@ func FindPos(s []string, v string) int {
|
|||
return -1
|
||||
}
|
||||
|
||||
func Find[T comparable](s []T, cond func(T) bool) int {
|
||||
for i, sv := range s {
|
||||
if cond(sv) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Difference returns the elements in `a` that aren't in `b`.
|
||||
func Difference(a, b []string) []string {
|
||||
var diff = make([]string, 0, len(a))
|
||||
|
@ -62,7 +71,7 @@ func Difference(a, b []string) []string {
|
|||
return diff
|
||||
}
|
||||
|
||||
func Insert(s []string, pos int, v ...string) []string {
|
||||
func Insert[T any](s []T, pos int, v ...T) []T {
|
||||
if len(s) <= pos {
|
||||
return append(s, v...)
|
||||
}
|
||||
|
@ -73,7 +82,7 @@ func Insert(s []string, pos int, v ...string) []string {
|
|||
}
|
||||
|
||||
// Remove reuses provided slice capacity. Provided s slice should not be used after without reassigning to the func return!
|
||||
func Remove(s []string, v string) []string {
|
||||
func Remove[T comparable](s []T, v T) []T {
|
||||
var n int
|
||||
for _, x := range s {
|
||||
if x != v {
|
||||
|
@ -84,8 +93,30 @@ func Remove(s []string, v string) []string {
|
|||
return s[:n]
|
||||
}
|
||||
|
||||
func Filter(vals []string, cond func(string) bool) []string {
|
||||
var result = make([]string, 0, len(vals))
|
||||
// RemoveIndex reuses provided slice capacity. Provided s slice should not be used after without reassigning to the func return!
|
||||
func RemoveIndex[T any](s []T, idx int) []T {
|
||||
var n int
|
||||
for i, x := range s {
|
||||
if i != idx {
|
||||
s[n] = x
|
||||
n++
|
||||
}
|
||||
}
|
||||
return s[:n]
|
||||
}
|
||||
|
||||
func Filter[T any](vals []T, cond func(T) bool) []T {
|
||||
var result = make([]T, 0, len(vals))
|
||||
for i := range vals {
|
||||
if cond(vals[i]) {
|
||||
result = append(result, vals[i])
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func FilterMut[T any](vals []T, cond func(T) bool) []T {
|
||||
result := vals[:0]
|
||||
for i := range vals {
|
||||
if cond(vals[i]) {
|
||||
result = append(result, vals[i])
|
||||
|
|
15
util/time/time.go
Normal file
15
util/time/time.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package time
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gogo/protobuf/types"
|
||||
|
||||
"github.com/anytypeio/go-anytype-middleware/util/pbtypes"
|
||||
)
|
||||
|
||||
func CutValueToDay(val *types.Value) *types.Value {
|
||||
t := time.Unix(int64(val.GetNumberValue()), 0)
|
||||
roundTime := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
|
||||
return pbtypes.Int64(roundTime.Unix())
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue