1
0
Fork 0
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:
Pavel Zavyalov 2023-01-19 16:33:52 +06:00
commit ee9c6944d2
No known key found for this signature in database
GPG key ID: D12476F8A1107A26
60 changed files with 29839 additions and 5965 deletions

View file

@ -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 {

View file

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

View file

@ -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")
}

View file

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

View file

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

View file

@ -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
View 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())
}