diff --git a/cmd/grpcserver/grpc.go b/cmd/grpcserver/grpc.go index 0ef4c2194..6e23abf88 100644 --- a/cmd/grpcserver/grpc.go +++ b/cmd/grpcserver/grpc.go @@ -4,6 +4,7 @@ package main import ( + "bufio" "context" "errors" "fmt" @@ -13,6 +14,7 @@ import ( _ "net/http/pprof" "os" "os/signal" + "runtime" "strconv" "syscall" "time" @@ -225,8 +227,25 @@ func main() { startReportMemory(mw) + shutdown := func() { + server.Stop() + proxy.Close() + mw.AppShutdown(context.Background(), &pb.RpcAppShutdownRequest{}) + } // do not change this, js client relies on this msg to ensure that server is up and parse address fmt.Println(grpcWebStartedMessagePrefix + webaddr) + if runtime.GOOS == "windows" { + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + message := scanner.Text() + if message == "shutdown" { + fmt.Println("[anytype-heart] Shutdown: received shutdown msg, closing components...") + // Perform cleanup or exit + shutdown() + return + } + } + } // run rest api server var mwInternal core.MiddlewareInternal = mw @@ -242,9 +261,8 @@ func main() { } continue } - server.Stop() - proxy.Close() - mw.AppShutdown(context.Background(), &pb.RpcAppShutdownRequest{}) + fmt.Printf("[anytype-heart] Shutdown: received OS signal (%s), closing components...\n", sig.String()) + shutdown() return } } diff --git a/core/anytype/bootstrap.go b/core/anytype/bootstrap.go index 7037a279d..26beb7b85 100644 --- a/core/anytype/bootstrap.go +++ b/core/anytype/bootstrap.go @@ -274,7 +274,6 @@ func Bootstrap(a *app.App, components ...app.Component) { Register(recordsbatcher.New()). Register(configfetcher.New()). Register(process.New()). - Register(core.New()). Register(core.NewTempDirService()). Register(treemanager.New()). Register(block.New()). diff --git a/core/core.go b/core/core.go index f7b4e308f..0a913d74c 100644 --- a/core/core.go +++ b/core/core.go @@ -39,7 +39,14 @@ func New() *Middleware { } func (mw *Middleware) AppShutdown(cctx context.Context, request *pb.RpcAppShutdownRequest) *pb.RpcAppShutdownResponse { - mw.applicationService.Stop() + err := mw.applicationService.Stop() + // using fmt.Println instead of log because we want it only in stdout + if err != nil { + fmt.Println("[anytype-heart] Shutdown: error during closing components: ", err) + } else { + fmt.Println("[anytype-heart] Shutdown: graceful shutdown finished") + } + // intentionally do not pass the error to the client return &pb.RpcAppShutdownResponse{ Error: &pb.RpcAppShutdownResponseError{ Code: pb.RpcAppShutdownResponseError_NULL, diff --git a/pkg/lib/core/core.go b/pkg/lib/core/core.go deleted file mode 100644 index eece10cbb..000000000 --- a/pkg/lib/core/core.go +++ /dev/null @@ -1,99 +0,0 @@ -package core - -import ( - "context" - "fmt" - "sync" - - "github.com/anyproto/any-sync/app" - "github.com/libp2p/go-libp2p/core/peer" - - "github.com/anyproto/anytype-heart/metrics" - "github.com/anyproto/anytype-heart/pkg/lib/logging" -) - -var log = logging.Logger("anytype-core") - -const ( - CName = "anytype" -) - -type Service interface { - Stop() error - IsStarted() bool - app.ComponentRunnable -} - -var _ app.Component = (*Anytype)(nil) - -var _ Service = (*Anytype)(nil) - -type Anytype struct { - lock sync.RWMutex - isStarted bool // use under the lock - shutdownStartsCh chan struct { - } // closed when node shutdown starts - -} - -func New() *Anytype { - return &Anytype{ - shutdownStartsCh: make(chan struct{}), - } -} - -func (a *Anytype) Init(ap *app.App) (err error) { - return -} - -func (a *Anytype) Name() string { - return CName -} - -func (a *Anytype) Run(ctx context.Context) (err error) { - a.start() - return nil -} - -// TODO: refactor to call tech space -func (a *Anytype) IsStarted() bool { - return true - a.lock.Lock() - defer a.lock.Unlock() - - return a.isStarted -} - -func (a *Anytype) HandlePeerFound(p peer.AddrInfo) { - // TODO: [MR] mdns -} - -func (a *Anytype) start() { - a.lock.Lock() - defer a.lock.Unlock() - - if a.isStarted { - return - } - - a.isStarted = true -} - -func (a *Anytype) Close(ctx context.Context) (err error) { - metrics.Service.Close() - return a.Stop() -} - -func (a *Anytype) Stop() error { - fmt.Printf("stopping the library...\n") - defer fmt.Println("library has been successfully stopped") - a.lock.Lock() - defer a.lock.Unlock() - a.isStarted = false - - if a.shutdownStartsCh != nil { - close(a.shutdownStartsCh) - } - - return nil -} diff --git a/pkg/lib/core/tmp_dir.go b/pkg/lib/core/tmp_dir.go index de97df4ae..9a07eb6d2 100644 --- a/pkg/lib/core/tmp_dir.go +++ b/pkg/lib/core/tmp_dir.go @@ -8,8 +8,11 @@ import ( "github.com/anyproto/any-sync/app" "github.com/anyproto/anytype-heart/core/wallet" + "github.com/anyproto/anytype-heart/pkg/lib/logging" ) +var log = logging.Logger("anytype-core") + const tmpDir = "tmp" type TempDirProvider interface { diff --git a/pkg/lib/database/database.go b/pkg/lib/database/database.go index 8d2cbe6fa..caac26dc8 100644 --- a/pkg/lib/database/database.go +++ b/pkg/lib/database/database.go @@ -190,6 +190,7 @@ func FiltersFromProto(filters []*model.BlockContentDataviewFilter) []FilterReque QuickOption: f.QuickOption, Format: f.Format, IncludeTime: f.IncludeTime, + NestedFilters: FiltersFromProto(f.NestedFilters), }) } return res diff --git a/pkg/lib/database/database_test.go b/pkg/lib/database/database_test.go index 7a96338db..d46bf0732 100644 --- a/pkg/lib/database/database_test.go +++ b/pkg/lib/database/database_test.go @@ -361,3 +361,150 @@ func Test_NewFilters(t *testing.T) { assert.Len(t, filters.FilterObj.(FiltersAnd)[0].(FiltersOr), 2) }) } + +func TestFiltersFromProto(t *testing.T) { + t.Run("no filters", func(t *testing.T) { + // given + var protoFilters []*model.BlockContentDataviewFilter + + // when + result := FiltersFromProto(protoFilters) + + // then + assert.NotNil(t, result) + assert.Len(t, result, 0) + }) + + t.Run("single filter without nesting", func(t *testing.T) { + // given + protoFilters := []*model.BlockContentDataviewFilter{ + { + Id: "filter1", + Operator: model.BlockContentDataviewFilter_No, + RelationKey: "relationKey1", + Condition: model.BlockContentDataviewFilter_Equal, + Value: domain.String("value1").ToProto(), + Format: model.RelationFormat_shorttext, + }, + } + + // when + result := FiltersFromProto(protoFilters) + + // then + assert.Len(t, result, 1) + assert.Equal(t, "filter1", result[0].Id) + assert.Equal(t, domain.RelationKey("relationKey1"), result[0].RelationKey) + assert.Equal(t, model.BlockContentDataviewFilter_Equal, result[0].Condition) + assert.Equal(t, domain.String("value1"), result[0].Value) + assert.Equal(t, model.RelationFormat_shorttext, result[0].Format) + assert.Empty(t, result[0].NestedFilters) + }) + + t.Run("nested filters", func(t *testing.T) { + // given + protoFilters := []*model.BlockContentDataviewFilter{ + { + Id: "filter1", + Operator: model.BlockContentDataviewFilter_And, + NestedFilters: []*model.BlockContentDataviewFilter{ + { + Id: "nestedFilter1", + Operator: model.BlockContentDataviewFilter_No, + RelationKey: "relationKey2", + Condition: model.BlockContentDataviewFilter_NotEqual, + Value: domain.String("value2").ToProto(), + Format: model.RelationFormat_date, + }, + { + Id: "nestedFilter2", + Operator: model.BlockContentDataviewFilter_No, + RelationKey: "relationKey3", + Condition: model.BlockContentDataviewFilter_Equal, + Value: domain.String("value3").ToProto(), + Format: model.RelationFormat_status, + }, + }, + }, + } + + // when + result := FiltersFromProto(protoFilters) + + // then + assert.Len(t, result, 1) + assert.Equal(t, "filter1", result[0].Id) + assert.NotNil(t, result[0].NestedFilters) + + nested := result[0].NestedFilters + assert.Len(t, nested, 2) + assert.Equal(t, "nestedFilter1", nested[0].Id) + assert.Equal(t, domain.RelationKey("relationKey2"), nested[0].RelationKey) + assert.Equal(t, model.BlockContentDataviewFilter_NotEqual, nested[0].Condition) + assert.Equal(t, domain.String("value2"), nested[0].Value) + assert.Equal(t, model.RelationFormat_date, nested[0].Format) + assert.Equal(t, "nestedFilter2", nested[1].Id) + assert.Equal(t, domain.RelationKey("relationKey3"), nested[1].RelationKey) + assert.Equal(t, model.BlockContentDataviewFilter_Equal, nested[1].Condition) + assert.Equal(t, domain.String("value3"), nested[1].Value) + assert.Equal(t, model.RelationFormat_status, nested[1].Format) + }) + + t.Run("deeply nested filters", func(t *testing.T) { + // given + protoFilters := []*model.BlockContentDataviewFilter{ + { + Id: "filter1", + Operator: model.BlockContentDataviewFilter_And, + NestedFilters: []*model.BlockContentDataviewFilter{ + { + Id: "nestedFilter1", + Operator: model.BlockContentDataviewFilter_Or, + NestedFilters: []*model.BlockContentDataviewFilter{ + { + Id: "deepNestedFilter1", + Operator: model.BlockContentDataviewFilter_No, + RelationKey: "relationKey3", + Condition: model.BlockContentDataviewFilter_Equal, + Value: domain.String("value3").ToProto(), + Format: model.RelationFormat_status, + }, + { + Id: "deepNestedFilter2", + Operator: model.BlockContentDataviewFilter_No, + RelationKey: "relationKey4", + Condition: model.BlockContentDataviewFilter_NotEqual, + Value: domain.String("value4").ToProto(), + Format: model.RelationFormat_shorttext, + }, + }, + }, + }, + }, + } + + // when + result := FiltersFromProto(protoFilters) + + // then + assert.Len(t, result, 1) + assert.NotNil(t, result[0].NestedFilters) + + nested := result[0].NestedFilters + assert.Len(t, nested, 1) + + deepNested := nested[0].NestedFilters + assert.Len(t, deepNested, 2) + assert.Equal(t, "deepNestedFilter1", deepNested[0].Id) + assert.Equal(t, domain.RelationKey("relationKey3"), deepNested[0].RelationKey) + assert.Equal(t, model.BlockContentDataviewFilter_Equal, deepNested[0].Condition) + assert.Equal(t, domain.String("value3"), deepNested[0].Value) + assert.Equal(t, model.RelationFormat_status, deepNested[0].Format) + assert.Equal(t, "deepNestedFilter2", deepNested[1].Id) + assert.Equal(t, domain.RelationKey("relationKey4"), deepNested[1].RelationKey) + assert.Equal(t, model.BlockContentDataviewFilter_NotEqual, deepNested[1].Condition) + assert.Equal(t, domain.String("value4"), deepNested[1].Value) + assert.Equal(t, model.RelationFormat_shorttext, deepNested[1].Format) + + }) +}