mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-01-29 16:31:20 -05:00
Merge pull request #3725 from aduffeck/search-grants
More fixes to searching in shares
This commit is contained in:
@@ -26,7 +26,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/blevesearch/bleve/v2"
|
||||
"github.com/blevesearch/bleve/v2/analysis/analyzer/custom"
|
||||
"github.com/blevesearch/bleve/v2/analysis/analyzer/keyword"
|
||||
"github.com/blevesearch/bleve/v2/analysis/token/lowercase"
|
||||
"github.com/blevesearch/bleve/v2/analysis/tokenizer/single"
|
||||
"github.com/blevesearch/bleve/v2/mapping"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
@@ -57,7 +60,11 @@ type Index struct {
|
||||
|
||||
// NewPersisted returns a new instance of Index with the data being persisted in the given directory
|
||||
func NewPersisted(path string) (*Index, error) {
|
||||
bi, err := bleve.New(path, BuildMapping())
|
||||
mapping, err := BuildMapping()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bi, err := bleve.New(path, mapping)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -157,14 +164,14 @@ func (i *Index) Purge(id *sprovider.ResourceId) error {
|
||||
return i.bleveIndex.Delete(idToBleveId(id))
|
||||
}
|
||||
|
||||
// Purge removes an entity from the index
|
||||
func (i *Index) Move(ri *sprovider.ResourceInfo) error {
|
||||
// Move update the path of an entry and all its children
|
||||
func (i *Index) Move(ri *sprovider.ResourceInfo, fullPath string) error {
|
||||
doc, err := i.getEntity(idToBleveId(ri.Id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldName := doc.Path
|
||||
newName := utils.MakeRelativePath(ri.Path)
|
||||
newName := utils.MakeRelativePath(fullPath)
|
||||
|
||||
doc, err = i.updateEntity(idToBleveId(ri.Id), func(doc *indexDocument) {
|
||||
doc.Path = newName
|
||||
@@ -204,7 +211,7 @@ func (i *Index) Search(ctx context.Context, req *searchsvc.SearchIndexRequest) (
|
||||
deletedQuery := bleve.NewBoolFieldQuery(false)
|
||||
deletedQuery.SetField("Deleted")
|
||||
query := bleve.NewConjunctionQuery(
|
||||
bleve.NewQueryStringQuery("Name:"+req.Query),
|
||||
bleve.NewQueryStringQuery("Name:"+strings.ToLower(req.Query)),
|
||||
deletedQuery, // Skip documents that have been marked as deleted
|
||||
bleve.NewQueryStringQuery("RootID:"+req.Ref.ResourceId.StorageId+"!"+req.Ref.ResourceId.OpaqueId), // Limit search to the space
|
||||
bleve.NewQueryStringQuery("Path:"+utils.MakeRelativePath(path.Join(req.Ref.Path, "/"))+"*"), // Limit search to this directory in the space
|
||||
@@ -232,10 +239,29 @@ func (i *Index) Search(ctx context.Context, req *searchsvc.SearchIndexRequest) (
|
||||
}
|
||||
|
||||
// BuildMapping builds a bleve index mapping which can be used for indexing
|
||||
func BuildMapping() mapping.IndexMapping {
|
||||
func BuildMapping() (mapping.IndexMapping, error) {
|
||||
nameMapping := bleve.NewTextFieldMapping()
|
||||
nameMapping.Analyzer = "lowercaseKeyword"
|
||||
|
||||
docMapping := bleve.NewDocumentMapping()
|
||||
docMapping.AddFieldMappingsAt("Name", nameMapping)
|
||||
|
||||
indexMapping := bleve.NewIndexMapping()
|
||||
indexMapping.DefaultAnalyzer = keyword.Name
|
||||
return indexMapping
|
||||
indexMapping.DefaultMapping = docMapping
|
||||
err := indexMapping.AddCustomAnalyzer("lowercaseKeyword",
|
||||
map[string]interface{}{
|
||||
"type": custom.Name,
|
||||
"tokenizer": single.Name,
|
||||
"token_filters": []string{
|
||||
lowercase.Name,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return indexMapping, nil
|
||||
}
|
||||
|
||||
func toEntity(ref *sprovider.Reference, ri *sprovider.ResourceInfo) *indexDocument {
|
||||
|
||||
@@ -37,7 +37,7 @@ var _ = Describe("Index", func() {
|
||||
StorageId: "storageid",
|
||||
OpaqueId: "someopaqueid",
|
||||
},
|
||||
Path: "foo.pdf",
|
||||
Path: "Foo.pdf",
|
||||
Size: 12345,
|
||||
Type: sprovider.ResourceType_RESOURCE_TYPE_FILE,
|
||||
MimeType: "application/pdf",
|
||||
@@ -45,7 +45,7 @@ var _ = Describe("Index", func() {
|
||||
}
|
||||
parentRef = &sprovider.Reference{
|
||||
ResourceId: rootId,
|
||||
Path: "./sudbir",
|
||||
Path: "./my/sudbir",
|
||||
}
|
||||
parentRi = &sprovider.ResourceInfo{
|
||||
Id: &sprovider.ResourceId{
|
||||
@@ -59,7 +59,7 @@ var _ = Describe("Index", func() {
|
||||
}
|
||||
childRef = &sprovider.Reference{
|
||||
ResourceId: rootId,
|
||||
Path: "./sudbir/child.pdf",
|
||||
Path: "./my/sudbir/child.pdf",
|
||||
}
|
||||
childRi = &sprovider.ResourceInfo{
|
||||
Id: &sprovider.ResourceId{
|
||||
@@ -92,8 +92,10 @@ var _ = Describe("Index", func() {
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
bleveIndex, err = bleve.NewMemOnly(index.BuildMapping())
|
||||
mapping, err := index.BuildMapping()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
bleveIndex, err = bleve.NewMemOnly(mapping)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
i, err = index.New(bleveIndex)
|
||||
@@ -201,6 +203,21 @@ var _ = Describe("Index", func() {
|
||||
}
|
||||
})
|
||||
|
||||
It("is case-insensitive", func() {
|
||||
res, err := i.Search(ctx, &searchsvc.SearchIndexRequest{
|
||||
Ref: &searchmsg.Reference{
|
||||
ResourceId: &searchmsg.ResourceID{
|
||||
StorageId: ref.ResourceId.StorageId,
|
||||
OpaqueId: ref.ResourceId.OpaqueId,
|
||||
},
|
||||
},
|
||||
Query: "Foo*",
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(res).ToNot(BeNil())
|
||||
Expect(len(res.Matches)).To(Equal(1))
|
||||
})
|
||||
|
||||
Context("and an additional file in a subdirectory", func() {
|
||||
var (
|
||||
nestedRef *sprovider.Reference
|
||||
@@ -349,7 +366,7 @@ var _ = Describe("Index", func() {
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
parentRi.Path = "newname"
|
||||
err = i.Move(parentRi)
|
||||
err = i.Move(parentRi, "./somewhere/else/newname")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
assertDocCount(rootId, "subdir", 0)
|
||||
@@ -365,7 +382,7 @@ var _ = Describe("Index", func() {
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(len(res.Matches)).To(Equal(1))
|
||||
Expect(res.Matches[0].Entity.Ref.Path).To(Equal("./newname/child.pdf"))
|
||||
Expect(res.Matches[0].Entity.Ref.Path).To(Equal("./somewhere/else/newname/child.pdf"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -65,13 +65,13 @@ func (_m *IndexClient) DocCount() (uint64, error) {
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Move provides a mock function with given fields: ri
|
||||
func (_m *IndexClient) Move(ri *providerv1beta1.ResourceInfo) error {
|
||||
ret := _m.Called(ri)
|
||||
// Move provides a mock function with given fields: ri, path
|
||||
func (_m *IndexClient) Move(ri *providerv1beta1.ResourceInfo, path string) error {
|
||||
ret := _m.Called(ri, path)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*providerv1beta1.ResourceInfo) error); ok {
|
||||
r0 = rf(ri)
|
||||
if rf, ok := ret.Get(0).(func(*providerv1beta1.ResourceInfo, string) error); ok {
|
||||
r0 = rf(ri, path)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@ import (
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
|
||||
"github.com/cs3org/reva/v2/pkg/errtypes"
|
||||
"github.com/cs3org/reva/v2/pkg/events"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
@@ -59,17 +61,25 @@ func (p *Provider) handleEvent(ev interface{}) {
|
||||
p.logger.Error().Err(err).Msg("failed to stat the changed resource")
|
||||
return
|
||||
}
|
||||
|
||||
switch statRes.Status.Code {
|
||||
case rpc.Code_CODE_OK:
|
||||
err = p.indexClient.Move(statRes.Info)
|
||||
if err != nil {
|
||||
p.logger.Error().Err(err).Msg("failed to restore the changed resource in the index")
|
||||
}
|
||||
default:
|
||||
if statRes.Status.Code != rpc.Code_CODE_OK {
|
||||
p.logger.Error().Interface("statRes", statRes).Msg("failed to stat the changed resource")
|
||||
return
|
||||
}
|
||||
|
||||
gpRes, err := p.getPath(statRes.Info.Id, owner)
|
||||
if err != nil {
|
||||
p.logger.Error().Err(err).Interface("ref", ref).Msg("failed to get path for moved resource")
|
||||
return
|
||||
}
|
||||
if gpRes.Status.Code != rpcv1beta1.Code_CODE_OK {
|
||||
p.logger.Error().Interface("status", gpRes.Status).Interface("ref", ref).Msg("failed to get path for moved resource")
|
||||
return
|
||||
}
|
||||
|
||||
err = p.indexClient.Move(statRes.Info, gpRes.Path)
|
||||
if err != nil {
|
||||
p.logger.Error().Err(err).Msg("failed to restore the changed resource in the index")
|
||||
}
|
||||
return
|
||||
case events.ContainerCreated:
|
||||
ref = e.Ref
|
||||
@@ -111,18 +121,38 @@ func (p *Provider) handleEvent(ev interface{}) {
|
||||
}
|
||||
|
||||
func (p *Provider) statResource(ref *provider.Reference, owner *user.User) (*provider.StatResponse, error) {
|
||||
// Get auth
|
||||
ownerCtx, err := p.getAuthContext(owner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Stat changed resource resource
|
||||
return p.gwClient.Stat(ownerCtx, &provider.StatRequest{Ref: ref})
|
||||
}
|
||||
|
||||
func (p *Provider) getPath(id *provider.ResourceId, owner *user.User) (*provider.GetPathResponse, error) {
|
||||
ownerCtx, err := p.getAuthContext(owner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Stat changed resource resource
|
||||
return p.gwClient.GetPath(ownerCtx, &provider.GetPathRequest{ResourceId: id})
|
||||
}
|
||||
|
||||
func (p *Provider) getAuthContext(owner *user.User) (context.Context, error) {
|
||||
ownerCtx := ctxpkg.ContextSetUser(context.Background(), owner)
|
||||
authRes, err := p.gwClient.Authenticate(ownerCtx, &gateway.AuthenticateRequest{
|
||||
Type: "machine",
|
||||
ClientId: "userid:" + owner.Id.OpaqueId,
|
||||
ClientSecret: p.machineAuthAPIKey,
|
||||
})
|
||||
if err != nil || authRes.GetStatus().GetCode() != rpc.Code_CODE_OK {
|
||||
p.logger.Error().Err(err).Interface("authRes", authRes).Msg("error using machine auth")
|
||||
if err == nil && authRes.GetStatus().GetCode() != rpc.Code_CODE_OK {
|
||||
err = errtypes.NewErrtypeFromStatus(authRes.Status)
|
||||
}
|
||||
ownerCtx = metadata.AppendToOutgoingContext(ownerCtx, ctxpkg.TokenHeader, authRes.Token)
|
||||
|
||||
// Stat changed resource resource
|
||||
return p.gwClient.Stat(ownerCtx, &provider.StatRequest{Ref: ref})
|
||||
if err != nil {
|
||||
p.logger.Error().Err(err).Interface("authRes", authRes).Msg("error using machine auth")
|
||||
return nil, err
|
||||
}
|
||||
return metadata.AppendToOutgoingContext(ownerCtx, ctxpkg.TokenHeader, authRes.Token), nil
|
||||
}
|
||||
|
||||
175
extensions/search/pkg/search/provider/events_test.go
Normal file
175
extensions/search/pkg/search/provider/events_test.go
Normal file
@@ -0,0 +1,175 @@
|
||||
package provider_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
sprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/cs3org/reva/v2/pkg/events"
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/status"
|
||||
cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks"
|
||||
"github.com/owncloud/ocis/v2/extensions/search/pkg/search/mocks"
|
||||
provider "github.com/owncloud/ocis/v2/extensions/search/pkg/search/provider"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/log"
|
||||
)
|
||||
|
||||
var _ = Describe("Searchprovider", func() {
|
||||
var (
|
||||
p *provider.Provider
|
||||
gwClient *cs3mocks.GatewayAPIClient
|
||||
indexClient *mocks.IndexClient
|
||||
|
||||
ctx context.Context
|
||||
eventsChan chan interface{}
|
||||
|
||||
logger = log.NewLogger()
|
||||
user = &userv1beta1.User{
|
||||
Id: &userv1beta1.UserId{
|
||||
OpaqueId: "user",
|
||||
},
|
||||
}
|
||||
|
||||
ref = &sprovider.Reference{
|
||||
ResourceId: &sprovider.ResourceId{
|
||||
StorageId: "storageid",
|
||||
OpaqueId: "rootopaqueid",
|
||||
},
|
||||
Path: "./foo.pdf",
|
||||
}
|
||||
ri = &sprovider.ResourceInfo{
|
||||
Id: &sprovider.ResourceId{
|
||||
StorageId: "storageid",
|
||||
OpaqueId: "opaqueid",
|
||||
},
|
||||
Path: "foo.pdf",
|
||||
Size: 12345,
|
||||
}
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
ctx = context.Background()
|
||||
eventsChan = make(chan interface{})
|
||||
gwClient = &cs3mocks.GatewayAPIClient{}
|
||||
indexClient = &mocks.IndexClient{}
|
||||
|
||||
p = provider.New(gwClient, indexClient, "", eventsChan, logger)
|
||||
|
||||
gwClient.On("Authenticate", mock.Anything, mock.Anything).Return(&gateway.AuthenticateResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Token: "authtoken",
|
||||
}, nil)
|
||||
gwClient.On("Stat", mock.Anything, mock.Anything).Return(&sprovider.StatResponse{
|
||||
Status: status.NewOK(context.Background()),
|
||||
Info: ri,
|
||||
}, nil)
|
||||
indexClient.On("DocCount").Return(uint64(1), nil)
|
||||
})
|
||||
|
||||
Describe("New", func() {
|
||||
It("returns a new instance", func() {
|
||||
p = provider.New(gwClient, indexClient, "", eventsChan, logger)
|
||||
Expect(p).ToNot(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("events", func() {
|
||||
It("triggers an index update when a file has been uploaded", func() {
|
||||
called := false
|
||||
indexClient.On("Add", mock.Anything, mock.MatchedBy(func(riToIndex *sprovider.ResourceInfo) bool {
|
||||
return riToIndex.Id.OpaqueId == ri.Id.OpaqueId
|
||||
})).Return(nil).Run(func(args mock.Arguments) {
|
||||
called = true
|
||||
})
|
||||
eventsChan <- events.FileUploaded{
|
||||
Ref: ref,
|
||||
Executant: user.Id,
|
||||
}
|
||||
|
||||
Eventually(func() bool {
|
||||
return called
|
||||
}, "2s").Should(BeTrue())
|
||||
})
|
||||
|
||||
It("removes an entry from the index when the file has been deleted", func() {
|
||||
called := false
|
||||
gwClient.On("Stat", mock.Anything, mock.Anything).Return(&sprovider.StatResponse{
|
||||
Status: status.NewNotFound(context.Background(), ""),
|
||||
}, nil)
|
||||
indexClient.On("Delete", mock.MatchedBy(func(id *sprovider.ResourceId) bool {
|
||||
return id.OpaqueId == ri.Id.OpaqueId
|
||||
})).Return(nil).Run(func(args mock.Arguments) {
|
||||
called = true
|
||||
})
|
||||
eventsChan <- events.ItemTrashed{
|
||||
Ref: ref,
|
||||
ID: ri.Id,
|
||||
Executant: user.Id,
|
||||
}
|
||||
|
||||
Eventually(func() bool {
|
||||
return called
|
||||
}, "2s").Should(BeTrue())
|
||||
})
|
||||
|
||||
It("indexes items when they are being restored", func() {
|
||||
called := false
|
||||
indexClient.On("Restore", mock.MatchedBy(func(id *sprovider.ResourceId) bool {
|
||||
return id.OpaqueId == ri.Id.OpaqueId
|
||||
})).Return(nil).Run(func(args mock.Arguments) {
|
||||
called = true
|
||||
})
|
||||
eventsChan <- events.ItemRestored{
|
||||
Ref: ref,
|
||||
Executant: user.Id,
|
||||
}
|
||||
|
||||
Eventually(func() bool {
|
||||
return called
|
||||
}, "2s").Should(BeTrue())
|
||||
})
|
||||
|
||||
It("indexes items when a version has been restored", func() {
|
||||
called := false
|
||||
indexClient.On("Add", mock.Anything, mock.MatchedBy(func(riToIndex *sprovider.ResourceInfo) bool {
|
||||
return riToIndex.Id.OpaqueId == ri.Id.OpaqueId
|
||||
})).Return(nil).Run(func(args mock.Arguments) {
|
||||
called = true
|
||||
})
|
||||
eventsChan <- events.FileVersionRestored{
|
||||
Ref: ref,
|
||||
Executant: user.Id,
|
||||
}
|
||||
|
||||
Eventually(func() bool {
|
||||
return called
|
||||
}, "2s").Should(BeTrue())
|
||||
})
|
||||
|
||||
It("indexes items when they are being moved", func() {
|
||||
called := false
|
||||
gwClient.On("GetPath", mock.Anything, mock.Anything).Return(&sprovider.GetPathResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Path: "./new/path.pdf",
|
||||
}, nil)
|
||||
indexClient.On("Move", mock.MatchedBy(func(riToIndex *sprovider.ResourceInfo) bool {
|
||||
return riToIndex.Id.OpaqueId == ri.Id.OpaqueId
|
||||
}), "./new/path.pdf").Return(nil).Run(func(args mock.Arguments) {
|
||||
called = true
|
||||
})
|
||||
ref.Path = "./new/path.pdf"
|
||||
eventsChan <- events.ItemMoved{
|
||||
Ref: ref,
|
||||
Executant: user.Id,
|
||||
}
|
||||
|
||||
Eventually(func() bool {
|
||||
return called
|
||||
}, "2s").Should(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
|
||||
"github.com/cs3org/reva/v2/pkg/errtypes"
|
||||
sdk "github.com/cs3org/reva/v2/pkg/sdk/common"
|
||||
"github.com/cs3org/reva/v2/pkg/storage/utils/walker"
|
||||
"github.com/cs3org/reva/v2/pkg/storagespace"
|
||||
"github.com/cs3org/reva/v2/pkg/utils"
|
||||
@@ -57,6 +58,7 @@ func (p *Provider) Search(ctx context.Context, req *searchsvc.SearchRequest) (*s
|
||||
if req.Query == "" {
|
||||
return nil, errtypes.PreconditionFailed("empty query provided")
|
||||
}
|
||||
p.logger.Debug().Str("query", req.Query).Msg("performing a search")
|
||||
|
||||
listSpacesRes, err := p.gwClient.ListStorageSpaces(ctx, &provider.ListStorageSpacesRequest{
|
||||
Filters: []*provider.ListStorageSpacesRequest_Filter{
|
||||
@@ -67,27 +69,61 @@ func (p *Provider) Search(ctx context.Context, req *searchsvc.SearchRequest) (*s
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
p.logger.Error().Err(err).Msg("failed to list the user's storage spaces")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mountpointMap := map[string]string{}
|
||||
for _, space := range listSpacesRes.StorageSpaces {
|
||||
if space.SpaceType != "mountpoint" {
|
||||
continue
|
||||
}
|
||||
opaqueMap := sdk.DecodeOpaqueMap(space.Opaque)
|
||||
grantSpaceId := storagespace.FormatResourceID(provider.ResourceId{
|
||||
StorageId: opaqueMap["grantStorageID"],
|
||||
OpaqueId: opaqueMap["grantOpaqueID"],
|
||||
})
|
||||
mountpointMap[grantSpaceId] = space.Id.OpaqueId
|
||||
}
|
||||
|
||||
matches := []*searchmsg.Match{}
|
||||
for _, space := range listSpacesRes.StorageSpaces {
|
||||
pathPrefix := ""
|
||||
if space.SpaceType == "grant" {
|
||||
var mountpointRootId *searchmsg.ResourceID
|
||||
mountpointPrefix := ""
|
||||
switch space.SpaceType {
|
||||
case "mountpoint":
|
||||
continue // mountpoint spaces are only "links" to the shared spaces. we have to search the shared "grant" space instead
|
||||
case "grant":
|
||||
mountpointId, ok := mountpointMap[space.Id.OpaqueId]
|
||||
if !ok {
|
||||
p.logger.Warn().Interface("space", space).Msg("could not find mountpoint space for grant space")
|
||||
continue
|
||||
}
|
||||
gpRes, err := p.gwClient.GetPath(ctx, &provider.GetPathRequest{
|
||||
ResourceId: space.Root,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
p.logger.Error().Err(err).Str("space", space.Id.OpaqueId).Msg("failed to get path for grant space root")
|
||||
continue
|
||||
}
|
||||
if gpRes.Status.Code != rpcv1beta1.Code_CODE_OK {
|
||||
return nil, errtypes.NewErrtypeFromStatus(gpRes.Status)
|
||||
p.logger.Error().Interface("status", gpRes.Status).Str("space", space.Id.OpaqueId).Msg("failed to get path for grant space root")
|
||||
continue
|
||||
}
|
||||
pathPrefix = utils.MakeRelativePath(gpRes.Path)
|
||||
mountpointPrefix = utils.MakeRelativePath(gpRes.Path)
|
||||
sid, oid, err := storagespace.SplitID(mountpointId)
|
||||
if err != nil {
|
||||
p.logger.Error().Err(err).Str("space", space.Id.OpaqueId).Str("mountpointId", mountpointId).Msg("invalid mountpoint space id")
|
||||
continue
|
||||
}
|
||||
mountpointRootId = &searchmsg.ResourceID{
|
||||
StorageId: sid,
|
||||
OpaqueId: oid,
|
||||
}
|
||||
p.logger.Debug().Interface("grantSpace", space).Interface("mountpointRootId", mountpointRootId).Msg("searching a grant")
|
||||
}
|
||||
|
||||
_, rootStorageID := storagespace.SplitStorageID(space.Root.StorageId)
|
||||
|
||||
res, err := p.indexClient.Search(ctx, &searchsvc.SearchIndexRequest{
|
||||
Query: req.Query,
|
||||
Ref: &searchmsg.Reference{
|
||||
@@ -95,16 +131,21 @@ func (p *Provider) Search(ctx context.Context, req *searchsvc.SearchRequest) (*s
|
||||
StorageId: space.Root.StorageId,
|
||||
OpaqueId: rootStorageID,
|
||||
},
|
||||
Path: pathPrefix,
|
||||
Path: mountpointPrefix,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
p.logger.Error().Err(err).Str("space", space.Id.OpaqueId).Msg("failed to search the index")
|
||||
return nil, err
|
||||
}
|
||||
p.logger.Debug().Str("space", space.Id.OpaqueId).Int("hits", len(res.Matches)).Msg("space search done")
|
||||
|
||||
for _, match := range res.Matches {
|
||||
if pathPrefix != "" {
|
||||
match.Entity.Ref.Path = utils.MakeRelativePath(strings.TrimPrefix(match.Entity.Ref.Path, pathPrefix))
|
||||
if mountpointPrefix != "" {
|
||||
match.Entity.Ref.Path = utils.MakeRelativePath(strings.TrimPrefix(match.Entity.Ref.Path, mountpointPrefix))
|
||||
}
|
||||
if mountpointRootId != nil {
|
||||
match.Entity.Ref.ResourceId = mountpointRootId
|
||||
}
|
||||
matches = append(matches, match)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
sprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/cs3org/reva/v2/pkg/events"
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/status"
|
||||
cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks"
|
||||
"github.com/owncloud/ocis/v2/extensions/search/pkg/search/mocks"
|
||||
@@ -55,13 +54,6 @@ var _ = Describe("Searchprovider", func() {
|
||||
Name: "personalspace",
|
||||
}
|
||||
|
||||
ref = &sprovider.Reference{
|
||||
ResourceId: &sprovider.ResourceId{
|
||||
StorageId: "storageid",
|
||||
OpaqueId: "rootopaqueid",
|
||||
},
|
||||
Path: "./foo.pdf",
|
||||
}
|
||||
ri = &sprovider.ResourceInfo{
|
||||
Id: &sprovider.ResourceId{
|
||||
StorageId: "storageid",
|
||||
@@ -98,98 +90,6 @@ var _ = Describe("Searchprovider", func() {
|
||||
})
|
||||
})
|
||||
|
||||
Describe("events", func() {
|
||||
It("trigger an index update when a file has been uploaded", func() {
|
||||
called := false
|
||||
indexClient.On("Add", mock.Anything, mock.MatchedBy(func(riToIndex *sprovider.ResourceInfo) bool {
|
||||
return riToIndex.Id.OpaqueId == ri.Id.OpaqueId
|
||||
})).Return(nil).Run(func(args mock.Arguments) {
|
||||
called = true
|
||||
})
|
||||
eventsChan <- events.FileUploaded{
|
||||
Ref: ref,
|
||||
Executant: user.Id,
|
||||
}
|
||||
|
||||
Eventually(func() bool {
|
||||
return called
|
||||
}, "2s").Should(BeTrue())
|
||||
})
|
||||
|
||||
It("removes an entry from the index when the file has been deleted", func() {
|
||||
called := false
|
||||
|
||||
gwClient.On("Stat", mock.Anything, mock.Anything).Return(&sprovider.StatResponse{
|
||||
Status: status.NewNotFound(context.Background(), ""),
|
||||
}, nil)
|
||||
indexClient.On("Delete", mock.MatchedBy(func(id *sprovider.ResourceId) bool {
|
||||
return id.OpaqueId == ri.Id.OpaqueId
|
||||
})).Return(nil).Run(func(args mock.Arguments) {
|
||||
called = true
|
||||
})
|
||||
eventsChan <- events.ItemTrashed{
|
||||
Ref: ref,
|
||||
ID: ri.Id,
|
||||
Executant: user.Id,
|
||||
}
|
||||
|
||||
Eventually(func() bool {
|
||||
return called
|
||||
}, "2s").Should(BeTrue())
|
||||
})
|
||||
|
||||
It("indexes items when they are being restored", func() {
|
||||
called := false
|
||||
indexClient.On("Restore", mock.MatchedBy(func(id *sprovider.ResourceId) bool {
|
||||
return id.OpaqueId == ri.Id.OpaqueId
|
||||
})).Return(nil).Run(func(args mock.Arguments) {
|
||||
called = true
|
||||
})
|
||||
eventsChan <- events.ItemRestored{
|
||||
Ref: ref,
|
||||
Executant: user.Id,
|
||||
}
|
||||
|
||||
Eventually(func() bool {
|
||||
return called
|
||||
}, "2s").Should(BeTrue())
|
||||
})
|
||||
|
||||
It("indexes items when a version has been restored", func() {
|
||||
called := false
|
||||
indexClient.On("Add", mock.Anything, mock.MatchedBy(func(riToIndex *sprovider.ResourceInfo) bool {
|
||||
return riToIndex.Id.OpaqueId == ri.Id.OpaqueId
|
||||
})).Return(nil).Run(func(args mock.Arguments) {
|
||||
called = true
|
||||
})
|
||||
eventsChan <- events.FileVersionRestored{
|
||||
Ref: ref,
|
||||
Executant: user.Id,
|
||||
}
|
||||
|
||||
Eventually(func() bool {
|
||||
return called
|
||||
}, "2s").Should(BeTrue())
|
||||
})
|
||||
|
||||
It("indexes items when they are being moved", func() {
|
||||
called := false
|
||||
indexClient.On("Move", mock.MatchedBy(func(riToIndex *sprovider.ResourceInfo) bool {
|
||||
return riToIndex.Id.OpaqueId == ri.Id.OpaqueId
|
||||
})).Return(nil).Run(func(args mock.Arguments) {
|
||||
called = true
|
||||
})
|
||||
eventsChan <- events.ItemMoved{
|
||||
Ref: ref,
|
||||
Executant: user.Id,
|
||||
}
|
||||
|
||||
Eventually(func() bool {
|
||||
return called
|
||||
}, "2s").Should(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("IndexSpace", func() {
|
||||
It("walks the space and indexes all files", func() {
|
||||
gwClient.On("GetUserByClaim", mock.Anything, mock.Anything).Return(&userv1beta1.GetUserByClaimResponse{
|
||||
@@ -267,7 +167,8 @@ var _ = Describe("Searchprovider", func() {
|
||||
|
||||
Context("with received shares", func() {
|
||||
var (
|
||||
grantSpace *sprovider.StorageSpace
|
||||
grantSpace *sprovider.StorageSpace
|
||||
mountpointSpace *sprovider.StorageSpace
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
@@ -275,19 +176,32 @@ var _ = Describe("Searchprovider", func() {
|
||||
SpaceType: "grant",
|
||||
Owner: otherUser,
|
||||
Id: &sprovider.StorageSpaceId{OpaqueId: "otherspaceroot!otherspacegrant"},
|
||||
Root: &sprovider.ResourceId{StorageId: "otherspaceroot", OpaqueId: "otherspaceroot"},
|
||||
Root: &sprovider.ResourceId{StorageId: "otherspaceroot", OpaqueId: "otherspacegrant"},
|
||||
Name: "grantspace",
|
||||
}
|
||||
mountpointSpace = &sprovider.StorageSpace{
|
||||
SpaceType: "mountpoint",
|
||||
Owner: otherUser,
|
||||
Id: &sprovider.StorageSpaceId{OpaqueId: "otherspaceroot!otherspacemountpoint"},
|
||||
Root: &sprovider.ResourceId{StorageId: "otherspaceroot", OpaqueId: "otherspacemountpoint"},
|
||||
Name: "mountpointspace",
|
||||
Opaque: &typesv1beta1.Opaque{
|
||||
Map: map[string]*typesv1beta1.OpaqueEntry{
|
||||
"grantStorageID": {Decoder: "plain", Value: []byte("otherspaceroot")},
|
||||
"grantOpaqueID": {Decoder: "plain", Value: []byte("otherspacegrant")},
|
||||
},
|
||||
},
|
||||
}
|
||||
gwClient.On("GetPath", mock.Anything, mock.Anything).Return(&sprovider.GetPathResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
Path: "/grant/path",
|
||||
}, nil)
|
||||
})
|
||||
|
||||
It("searches the received spaces (grants)", func() {
|
||||
It("searches the received spaces", func() {
|
||||
gwClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&sprovider.ListStorageSpacesResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
StorageSpaces: []*sprovider.StorageSpace{grantSpace},
|
||||
StorageSpaces: []*sprovider.StorageSpace{grantSpace, mountpointSpace},
|
||||
}, nil)
|
||||
indexClient.On("Search", mock.Anything, mock.Anything).Return(&searchsvc.SearchIndexResponse{
|
||||
Matches: []*searchmsg.Match{
|
||||
@@ -319,7 +233,7 @@ var _ = Describe("Searchprovider", func() {
|
||||
match := res.Matches[0]
|
||||
Expect(match.Entity.Id.OpaqueId).To(Equal("grant-shared-id"))
|
||||
Expect(match.Entity.Name).To(Equal("Shared.pdf"))
|
||||
Expect(match.Entity.Ref.ResourceId.OpaqueId).To(Equal(grantSpace.Root.OpaqueId))
|
||||
Expect(match.Entity.Ref.ResourceId.OpaqueId).To(Equal(mountpointSpace.Root.OpaqueId))
|
||||
Expect(match.Entity.Ref.Path).To(Equal("./to/Shared.pdf"))
|
||||
|
||||
indexClient.AssertCalled(GinkgoT(), "Search", mock.Anything, mock.MatchedBy(func(req *searchsvc.SearchIndexRequest) bool {
|
||||
@@ -330,7 +244,7 @@ var _ = Describe("Searchprovider", func() {
|
||||
It("finds matches in both the personal space AND the grant", func() {
|
||||
gwClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&sprovider.ListStorageSpacesResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
StorageSpaces: []*sprovider.StorageSpace{personalSpace, grantSpace},
|
||||
StorageSpaces: []*sprovider.StorageSpace{personalSpace, grantSpace, mountpointSpace},
|
||||
}, nil)
|
||||
indexClient.On("Search", mock.Anything, mock.MatchedBy(func(req *searchsvc.SearchIndexRequest) bool {
|
||||
return req.Ref.ResourceId.StorageId == grantSpace.Root.StorageId
|
||||
|
||||
@@ -38,7 +38,7 @@ type ProviderClient interface {
|
||||
type IndexClient interface {
|
||||
Search(ctx context.Context, req *searchsvc.SearchIndexRequest) (*searchsvc.SearchIndexResponse, error)
|
||||
Add(ref *providerv1beta1.Reference, ri *providerv1beta1.ResourceInfo) error
|
||||
Move(ri *providerv1beta1.ResourceInfo) error
|
||||
Move(ri *providerv1beta1.ResourceInfo, path string) error
|
||||
Delete(ri *providerv1beta1.ResourceId) error
|
||||
Restore(ri *providerv1beta1.ResourceId) error
|
||||
Purge(ri *providerv1beta1.ResourceId) error
|
||||
|
||||
@@ -46,7 +46,11 @@ func NewHandler(opts ...Option) (searchsvc.SearchProviderHandler, error) {
|
||||
indexDir := filepath.Join(cfg.Datapath, "index.bleve")
|
||||
bleveIndex, err := bleve.Open(indexDir)
|
||||
if err != nil {
|
||||
bleveIndex, err = bleve.New(indexDir, index.BuildMapping())
|
||||
mapping, err := index.BuildMapping()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bleveIndex, err = bleve.New(indexDir, mapping)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user