From 5923e1956fd0bd3c1f77c0ed0be8f4c3c793f9b1 Mon Sep 17 00:00:00 2001 From: David Christofas Date: Fri, 17 Sep 2021 02:53:20 -0400 Subject: [PATCH] add filter-files to the dav REPORT API (#2071) --- changelog/unreleased/list-favorites.md | 6 + .../http/services/owncloud/ocdav/ocdav.go | 38 +++-- .../http/services/owncloud/ocdav/propfind.go | 4 +- .../http/services/owncloud/ocdav/proppatch.go | 28 ++++ .../services/owncloud/ocdav/publicfile.go | 2 +- .../http/services/owncloud/ocdav/report.go | 62 +++++++- .../services/owncloud/ocdav/report_test.go | 63 ++++++++ .../http/services/owncloud/ocdav/versions.go | 2 +- pkg/storage/favorite/favorite.go | 78 ++++++++++ pkg/storage/favorite/favorite_test.go | 143 ++++++++++++++++++ pkg/storage/memory.go | 58 +++++++ .../expected-failures-on-OCIS-storage.md | 6 - .../expected-failures-on-S3NG-storage.md | 6 - .../oc-integration-tests/drone/frontend.toml | 7 + .../oc-integration-tests/local/frontend.toml | 7 + 15 files changed, 484 insertions(+), 26 deletions(-) create mode 100644 changelog/unreleased/list-favorites.md create mode 100644 internal/http/services/owncloud/ocdav/report_test.go create mode 100644 pkg/storage/favorite/favorite.go create mode 100644 pkg/storage/favorite/favorite_test.go create mode 100644 pkg/storage/memory.go diff --git a/changelog/unreleased/list-favorites.md b/changelog/unreleased/list-favorites.md new file mode 100644 index 00000000000..e89733cf1aa --- /dev/null +++ b/changelog/unreleased/list-favorites.md @@ -0,0 +1,6 @@ +Enhancement: Implement listing favorites via the dav report API + +Added filter-files to the dav REPORT API. This enables the listing of +favorites. + +https://github.com/cs3org/reva/pull/2071 diff --git a/internal/http/services/owncloud/ocdav/ocdav.go b/internal/http/services/owncloud/ocdav/ocdav.go index ff98568842c..b1151e2f62c 100644 --- a/internal/http/services/owncloud/ocdav/ocdav.go +++ b/internal/http/services/owncloud/ocdav/ocdav.go @@ -35,11 +35,15 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/pkg/appctx" ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/pkg/rhttp" "github.com/cs3org/reva/pkg/rhttp/global" "github.com/cs3org/reva/pkg/rhttp/router" "github.com/cs3org/reva/pkg/sharedconf" + "github.com/cs3org/reva/pkg/storage" + "github.com/cs3org/reva/pkg/storage/favorite" + "github.com/cs3org/reva/pkg/storage/fs/registry" "github.com/cs3org/reva/pkg/storage/utils/templates" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" @@ -97,11 +101,13 @@ type Config struct { // Example: if WebdavNamespace is /users/{{substr 0 1 .Username}}/{{.Username}} // and received path is /docs the internal path will be: // /users///docs - WebdavNamespace string `mapstructure:"webdav_namespace"` - GatewaySvc string `mapstructure:"gatewaysvc"` - Timeout int64 `mapstructure:"timeout"` - Insecure bool `mapstructure:"insecure"` - PublicURL string `mapstructure:"public_url"` + WebdavNamespace string `mapstructure:"webdav_namespace"` + GatewaySvc string `mapstructure:"gatewaysvc"` + Timeout int64 `mapstructure:"timeout"` + Insecure bool `mapstructure:"insecure"` + PublicURL string `mapstructure:"public_url"` + Driver string `mapstructure:"driver"` + Drivers map[string]map[string]interface{} `mapstructure:"drivers" docs:"url:pkg/storage/fs/localhome/localhome.go"` } func (c *Config) init() { @@ -110,10 +116,18 @@ func (c *Config) init() { } type svc struct { - c *Config - webDavHandler *WebDavHandler - davHandler *DavHandler - client *http.Client + c *Config + webDavHandler *WebDavHandler + davHandler *DavHandler + favoritesManager favorite.Manager + client *http.Client +} + +func getFS(c *Config) (storage.FS, error) { + if f, ok := registry.NewFuncs[c.Driver]; ok { + return f(c.Drivers[c.Driver]) + } + return nil, errtypes.NotFound("driver not found: " + c.Driver) } // New returns a new ocdav @@ -125,6 +139,11 @@ func New(m map[string]interface{}, log *zerolog.Logger) (global.Service, error) conf.init() + fs, err := getFS(conf) + if err != nil { + return nil, err + } + s := &svc{ c: conf, webDavHandler: new(WebDavHandler), @@ -133,6 +152,7 @@ func New(m map[string]interface{}, log *zerolog.Logger) (global.Service, error) rhttp.Timeout(time.Duration(conf.Timeout*int64(time.Second))), rhttp.Insecure(conf.Insecure), ), + favoritesManager: favorite.NewInMemoryManager(fs), } // initialize handlers and set default configs if err := s.webDavHandler.init(conf.WebdavNamespace, true); err != nil { diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind.go index 0e9c99be888..e95007abc90 100644 --- a/internal/http/services/owncloud/ocdav/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind.go @@ -134,7 +134,7 @@ func (s *svc) handleSpacesPropfind(w http.ResponseWriter, r *http.Request, space } func (s *svc) propfindResponse(ctx context.Context, w http.ResponseWriter, r *http.Request, namespace string, pf propfindXML, parentInfo *provider.ResourceInfo, resourceInfos []*provider.ResourceInfo, log zerolog.Logger) { - propRes, err := s.formatPropfind(ctx, &pf, resourceInfos, namespace) + propRes, err := s.multistatusResponse(ctx, &pf, resourceInfos, namespace) if err != nil { log.Error().Err(err).Msg("error formatting propfind") w.WriteHeader(http.StatusInternalServerError) @@ -342,7 +342,7 @@ func readPropfind(r io.Reader) (pf propfindXML, status int, err error) { return pf, 0, nil } -func (s *svc) formatPropfind(ctx context.Context, pf *propfindXML, mds []*provider.ResourceInfo, ns string) (string, error) { +func (s *svc) multistatusResponse(ctx context.Context, pf *propfindXML, mds []*provider.ResourceInfo, ns string) (string, error) { responses := make([]*responseXML, 0, len(mds)) for i := range mds { res, err := s.mdToPropResponse(ctx, pf, mds[i], ns) diff --git a/internal/http/services/owncloud/ocdav/proppatch.go b/internal/http/services/owncloud/ocdav/proppatch.go index 2fb3a0f6a39..d7d5ce4a429 100644 --- a/internal/http/services/owncloud/ocdav/proppatch.go +++ b/internal/http/services/owncloud/ocdav/proppatch.go @@ -30,6 +30,7 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/pkg/appctx" + ctxpkg "github.com/cs3org/reva/pkg/ctx" rtrace "github.com/cs3org/reva/pkg/trace" "github.com/pkg/errors" "github.com/rs/zerolog" @@ -232,6 +233,19 @@ func (s *svc) handleProppatch(ctx context.Context, w http.ResponseWriter, r *htt HandleErrorStatus(&log, w, res.Status) return nil, nil, false } + if key == "http://owncloud.org/ns/favorite" { + statRes, err := c.Stat(ctx, &provider.StatRequest{Ref: ref}) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return nil, nil, false + } + currentUser := ctxpkg.ContextMustGetUser(ctx) + err = s.favoritesManager.UnsetFavorite(ctx, currentUser.Id, statRes.Info.Id) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return nil, nil, false + } + } removedProps = append(removedProps, propNameXML) } else { sreq.ArbitraryMetadata.Metadata[key] = value @@ -259,6 +273,20 @@ func (s *svc) handleProppatch(ctx context.Context, w http.ResponseWriter, r *htt acceptedProps = append(acceptedProps, propNameXML) delete(sreq.ArbitraryMetadata.Metadata, key) + + if key == "http://owncloud.org/ns/favorite" { + statRes, err := c.Stat(ctx, &provider.StatRequest{Ref: ref}) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return nil, nil, false + } + currentUser := ctxpkg.ContextMustGetUser(ctx) + err = s.favoritesManager.SetFavorite(ctx, currentUser.Id, statRes.Info.Id) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return nil, nil, false + } + } } } // FIXME: in case of error, need to set all properties back to the original state, diff --git a/internal/http/services/owncloud/ocdav/publicfile.go b/internal/http/services/owncloud/ocdav/publicfile.go index 797f1e4d530..f2315e90cfd 100644 --- a/internal/http/services/owncloud/ocdav/publicfile.go +++ b/internal/http/services/owncloud/ocdav/publicfile.go @@ -186,7 +186,7 @@ func (s *svc) handlePropfindOnToken(w http.ResponseWriter, r *http.Request, ns s infos := s.getPublicFileInfos(onContainer, depth == "0", tokenStatInfo) - propRes, err := s.formatPropfind(ctx, &pf, infos, ns) + propRes, err := s.multistatusResponse(ctx, &pf, infos, ns) if err != nil { sublog.Error().Err(err).Msg("error formatting propfind") w.WriteHeader(http.StatusInternalServerError) diff --git a/internal/http/services/owncloud/ocdav/report.go b/internal/http/services/owncloud/ocdav/report.go index 5807d5094df..a0f0b4a86e8 100644 --- a/internal/http/services/owncloud/ocdav/report.go +++ b/internal/http/services/owncloud/ocdav/report.go @@ -24,6 +24,12 @@ import ( "net/http" "github.com/cs3org/reva/pkg/appctx" + ctxpkg "github.com/cs3org/reva/pkg/ctx" +) + +const ( + elementNameSearchFiles = "search-files" + elementNameFilterFiles = "filter-files" ) func (s *svc) handleReport(w http.ResponseWriter, r *http.Request, ns string) { @@ -42,6 +48,11 @@ func (s *svc) handleReport(w http.ResponseWriter, r *http.Request, ns string) { return } + if rep.FilterFiles != nil { + s.doFilterFiles(w, r, rep.FilterFiles, ns) + return + } + // TODO(jfd): implement report w.WriteHeader(http.StatusNotImplemented) @@ -59,9 +70,39 @@ func (s *svc) doSearchFiles(w http.ResponseWriter, r *http.Request, sf *reportSe w.WriteHeader(http.StatusNotImplemented) } +func (s *svc) doFilterFiles(w http.ResponseWriter, r *http.Request, ff *reportFilterFiles, namespace string) { + ctx := r.Context() + log := appctx.GetLogger(ctx) + + if ff.Rules.Favorite { + // List the users favorite resources. + currentUser := ctxpkg.ContextMustGetUser(ctx) + favorites, err := s.favoritesManager.ListFavorites(ctx, currentUser.Id) + if err != nil { + log.Error().Err(err).Msg("error getting favorites") + w.WriteHeader(http.StatusInternalServerError) + return + } + + responsesXML, err := s.multistatusResponse(ctx, &propfindXML{Prop: ff.Prop}, favorites, namespace) + if err != nil { + log.Error().Err(err).Msg("error formatting propfind") + w.WriteHeader(http.StatusInternalServerError) + return + } + w.Header().Set(HeaderDav, "1, 3, extended-mkcol") + w.Header().Set(HeaderContentType, "application/xml; charset=utf-8") + w.WriteHeader(http.StatusMultiStatus) + if _, err := w.Write([]byte(responsesXML)); err != nil { + log.Err(err).Msg("error writing response") + } + } +} + type report struct { SearchFiles *reportSearchFiles // FilterFiles TODO add this for tag based search + FilterFiles *reportFilterFiles `xml:"filter-files"` } type reportSearchFiles struct { XMLName xml.Name `xml:"search-files"` @@ -75,6 +116,18 @@ type reportSearchFilesSearch struct { Offset int `xml:"offset"` } +type reportFilterFiles struct { + XMLName xml.Name `xml:"filter-files"` + Lang string `xml:"xml:lang,attr,omitempty"` + Prop propfindProps `xml:"DAV: prop"` + Rules reportFilterFilesRules `xml:"filter-rules"` +} + +type reportFilterFilesRules struct { + Favorite bool `xml:"favorite"` + SystemTag int `xml:"systemtag"` +} + func readReport(r io.Reader) (rep *report, status int, err error) { decoder := xml.NewDecoder(r) rep = &report{} @@ -89,13 +142,20 @@ func readReport(r io.Reader) (rep *report, status int, err error) { } if v, ok := t.(xml.StartElement); ok { - if v.Name.Local == "search-files" { + if v.Name.Local == elementNameSearchFiles { var repSF reportSearchFiles err = decoder.DecodeElement(&repSF, &v) if err != nil { return nil, http.StatusBadRequest, err } rep.SearchFiles = &repSF + } else if v.Name.Local == elementNameFilterFiles { + var repFF reportFilterFiles + err = decoder.DecodeElement(&repFF, &v) + if err != nil { + return nil, http.StatusBadRequest, err + } + rep.FilterFiles = &repFF } } } diff --git a/internal/http/services/owncloud/ocdav/report_test.go b/internal/http/services/owncloud/ocdav/report_test.go new file mode 100644 index 00000000000..42b5d64a90e --- /dev/null +++ b/internal/http/services/owncloud/ocdav/report_test.go @@ -0,0 +1,63 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package ocdav + +import ( + "strings" + "testing" +) + +func TestUnmarshallReportFilterFiles(t *testing.T) { + ffXML := ` + + + + + + + + + + + + + + + + + + 1 + +` + + reader := strings.NewReader(ffXML) + + report, status, err := readReport(reader) + if status != 0 || err != nil { + t.Error("Failed to unmarshal filter-files xml") + } + + if report.FilterFiles == nil { + t.Error("Failed to unmarshal filter-files xml. FilterFiles is nil") + } + + if report.FilterFiles.Rules.Favorite == false { + t.Error("Failed to correctly unmarshal filter-rules. Favorite is expected to be true.") + } +} diff --git a/internal/http/services/owncloud/ocdav/versions.go b/internal/http/services/owncloud/ocdav/versions.go index b9af216c47f..459e995620c 100644 --- a/internal/http/services/owncloud/ocdav/versions.go +++ b/internal/http/services/owncloud/ocdav/versions.go @@ -164,7 +164,7 @@ func (h *VersionsHandler) doListVersions(w http.ResponseWriter, r *http.Request, infos = append(infos, vi) } - propRes, err := s.formatPropfind(ctx, &pf, infos, "") + propRes, err := s.multistatusResponse(ctx, &pf, infos, "") if err != nil { sublog.Error().Err(err).Msg("error formatting propfind") w.WriteHeader(http.StatusInternalServerError) diff --git a/pkg/storage/favorite/favorite.go b/pkg/storage/favorite/favorite.go new file mode 100644 index 00000000000..8c505429a50 --- /dev/null +++ b/pkg/storage/favorite/favorite.go @@ -0,0 +1,78 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package favorite + +import ( + "context" + + user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/pkg/storage" +) + +// Manager defines an interface for a favorites manager. +type Manager interface { + // ListFavorites returns all resources that were favorited by a user. + ListFavorites(ctx context.Context, userID *user.UserId) ([]*provider.ResourceInfo, error) + // SetFavorite marks a resource as favorited by a user. + SetFavorite(ctx context.Context, userID *user.UserId, resourceID *provider.ResourceId) error + // UnsetFavorite unmarks a resource as favorited by a user. + UnsetFavorite(ctx context.Context, userID *user.UserId, resourceID *provider.ResourceId) error +} + +// NewInMemoryManager returns an instance of a favorites manager using an in-memory storage. +func NewInMemoryManager(fs storage.FS) Manager { + return InMemoryManager{fs: fs, favorites: make(map[string]map[string]struct{})} +} + +// InMemoryManager implements the Manager interface to manage favorites using an in-memory storage. +type InMemoryManager struct { + fs storage.FS + favorites map[string]map[string]struct{} +} + +// ListFavorites returns all resources that were favorited by a user. +func (m InMemoryManager) ListFavorites(ctx context.Context, userID *user.UserId) ([]*provider.ResourceInfo, error) { + favorites := make([]*provider.ResourceInfo, 0, len(m.favorites[userID.OpaqueId])) + for id := range m.favorites[userID.OpaqueId] { + info, err := m.fs.GetMD(ctx, &provider.Reference{ResourceId: &provider.ResourceId{OpaqueId: id}}, []string{}) + if err != nil { + continue + } + favorites = append(favorites, info) + } + return favorites, nil +} + +// SetFavorite marks a resource as favorited by a user. +func (m InMemoryManager) SetFavorite(_ context.Context, userID *user.UserId, resourceID *provider.ResourceId) error { + if m.favorites[userID.OpaqueId] == nil { + m.favorites[userID.OpaqueId] = make(map[string]struct{}) + } + m.favorites[userID.OpaqueId][resourceID.OpaqueId] = struct{}{} + + return nil +} + +// UnsetFavorite unmarks a resource as favorited by a user. +func (m InMemoryManager) UnsetFavorite(_ context.Context, userID *user.UserId, resourceID *provider.ResourceId) error { + delete(m.favorites[userID.OpaqueId], resourceID.OpaqueId) + + return nil +} diff --git a/pkg/storage/favorite/favorite_test.go b/pkg/storage/favorite/favorite_test.go new file mode 100644 index 00000000000..59076206da3 --- /dev/null +++ b/pkg/storage/favorite/favorite_test.go @@ -0,0 +1,143 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package favorite + +import ( + "context" + "testing" + + user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/storage" +) + +type environment struct { + userOne *user.User + userOneCtx context.Context + + userTwo *user.User + userTwoCtx context.Context + + userThree *user.User + userThreeCtx context.Context + + resourceInfoOne *provider.ResourceInfo + resourceInfoTwo *provider.ResourceInfo + + fs storage.FS +} + +func createEnvironment() environment { + userOne := &user.User{Id: &user.UserId{OpaqueId: "userOne"}} + userTwo := &user.User{Id: &user.UserId{OpaqueId: "userTwo"}} + userThree := &user.User{Id: &user.UserId{OpaqueId: "userThree"}} + + resourceInfoOne := &provider.ResourceInfo{Id: &provider.ResourceId{OpaqueId: "resourceInfoOne"}} + resourceInfoTwo := &provider.ResourceInfo{Id: &provider.ResourceId{OpaqueId: "resourceInfoTwo"}} + + return environment{ + userOne: userOne, + userOneCtx: ctxpkg.ContextSetUser(context.Background(), userOne), + userTwo: userTwo, + userTwoCtx: ctxpkg.ContextSetUser(context.Background(), userTwo), + userThree: userThree, + userThreeCtx: ctxpkg.ContextSetUser(context.Background(), userThree), + + resourceInfoOne: resourceInfoOne, + resourceInfoTwo: resourceInfoTwo, + + fs: storage.InMemoryFS{Resources: map[string]map[string]*provider.ResourceInfo{ + userOne.Id.OpaqueId: { + resourceInfoOne.Id.OpaqueId: resourceInfoOne, + }, + userTwo.Id.OpaqueId: { + resourceInfoOne.Id.OpaqueId: resourceInfoOne, + resourceInfoTwo.Id.OpaqueId: resourceInfoTwo, + }, + }}, + } +} + +func TestListFavorite(t *testing.T) { + env := createEnvironment() + sut := NewInMemoryManager(env.fs) + + favorites, _ := sut.ListFavorites(env.userOneCtx, env.userOne.Id) + if len(favorites) != 0 { + t.Error("ListFavorites should not return anything when a user hasn't set a favorite") + } + + _ = sut.SetFavorite(env.userOneCtx, env.userOne.Id, env.resourceInfoOne.Id) + _ = sut.SetFavorite(env.userTwoCtx, env.userTwo.Id, env.resourceInfoOne.Id) + _ = sut.SetFavorite(env.userTwoCtx, env.userTwo.Id, env.resourceInfoTwo.Id) + + favorites, _ = sut.ListFavorites(env.userOneCtx, env.userOne.Id) + if len(favorites) != 1 { + t.Errorf("Expected %d favorites got %d", 1, len(favorites)) + } + + favorites, _ = sut.ListFavorites(env.userTwoCtx, env.userTwo.Id) + if len(favorites) != 2 { + t.Errorf("Expected %d favorites got %d", 2, len(favorites)) + } + + favorites, _ = sut.ListFavorites(env.userThreeCtx, env.userThree.Id) + if len(favorites) != 0 { + t.Errorf("Expected %d favorites got %d", 0, len(favorites)) + } +} + +func TestSetFavorite(t *testing.T) { + env := createEnvironment() + + sut := NewInMemoryManager(env.fs) + + favorites, _ := sut.ListFavorites(env.userOneCtx, env.userOne.Id) + lenBefore := len(favorites) + + _ = sut.SetFavorite(env.userOneCtx, env.userOne.Id, env.resourceInfoOne.Id) + + favorites, _ = sut.ListFavorites(env.userOneCtx, env.userOne.Id) + lenAfter := len(favorites) + + if lenAfter-lenBefore != 1 { + t.Errorf("Setting a favorite should add 1 favorite but actually added %d", lenAfter-lenBefore) + } +} + +func TestUnsetFavorite(t *testing.T) { + env := createEnvironment() + + sut := NewInMemoryManager(env.fs) + + _ = sut.SetFavorite(env.userOneCtx, env.userOne.Id, env.resourceInfoOne.Id) + favorites, _ := sut.ListFavorites(env.userOneCtx, env.userOne.Id) + lenBefore := len(favorites) + + _ = sut.UnsetFavorite(env.userOneCtx, env.userOne.Id, env.resourceInfoOne.Id) + + favorites, _ = sut.ListFavorites(env.userOneCtx, env.userOne.Id) + lenAfter := len(favorites) + + if lenAfter-lenBefore != -1 { + t.Errorf("Setting a favorite should remove 1 favorite but actually removed %d", lenAfter-lenBefore) + } +} diff --git a/pkg/storage/memory.go b/pkg/storage/memory.go new file mode 100644 index 00000000000..3bc43cac9f8 --- /dev/null +++ b/pkg/storage/memory.go @@ -0,0 +1,58 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package storage + +import ( + "context" + + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/errtypes" +) + +// InMemoryFS is supposed to be a storage.FS implementation which can be used in unit tests which depend on a storage. +// Not all methods are implemented but can be added when required. +// The data is only stored in memory. +type InMemoryFS struct { + FS + // Resources acts as the in memory storage. + // The expected layout is as follows: + // + // Resources: map[string]map[string]*provider.ResourceInfo{ + // userOne.Id.OpaqueId: { + // resourceInfoOne.Id.OpaqueId: resourceInfoOne, + // }, + // userTwo.Id.OpaqueId: { + // resourceInfoOne.Id.OpaqueId: resourceInfoOne, + // resourceInfoTwo.Id.OpaqueId: resourceInfoTwo, + // }, + // } + Resources map[string]map[string]*provider.ResourceInfo +} + +// GetMD looks up the ResourceInfo by a Reference +func (fs InMemoryFS) GetMD(ctx context.Context, ref *provider.Reference, _ []string) (*provider.ResourceInfo, error) { + user := ctxpkg.ContextMustGetUser(ctx) + if infos, ok := fs.Resources[user.Id.OpaqueId]; ok { + if info, ok := infos[ref.ResourceId.OpaqueId]; ok { + return info, nil + } + } + return nil, errtypes.NotFound(ref.ResourceId.OpaqueId) +} diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index 6c1def9086b..0fa0c71caf7 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -1007,18 +1007,12 @@ Scenario Outline: search for entry with emoji by pattern - [apiWebdavOperations/search.feature:255](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L255) Scenario: search for entries across various folders by tags using REPORT method And other missing implementation of favorites -- [apiFavorites/favorites.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L91) -- [apiFavorites/favorites.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L92) -- [apiFavorites/favorites.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L112) -- [apiFavorites/favorites.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L113) - [apiFavorites/favorites.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L128) - [apiFavorites/favorites.feature:129](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L129) - [apiFavorites/favorites.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L148) - [apiFavorites/favorites.feature:149](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L149) - [apiFavorites/favorites.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L176) - [apiFavorites/favorites.feature:177](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L177) -- [apiFavorites/favorites.feature:217](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L217) -- [apiFavorites/favorites.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L218) - [apiFavorites/favoritesSharingToShares.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L21) - [apiFavorites/favoritesSharingToShares.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L22) - [apiFavorites/favoritesSharingToShares.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L35) diff --git a/tests/acceptance/expected-failures-on-S3NG-storage.md b/tests/acceptance/expected-failures-on-S3NG-storage.md index b84f331dc50..b0989ce3e83 100644 --- a/tests/acceptance/expected-failures-on-S3NG-storage.md +++ b/tests/acceptance/expected-failures-on-S3NG-storage.md @@ -1006,18 +1006,12 @@ Scenario Outline: search for entry with emoji by pattern - [apiWebdavOperations/search.feature:255](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L255) Scenario: search for entries across various folders by tags using REPORT method And other missing implementation of favorites -- [apiFavorites/favorites.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L91) -- [apiFavorites/favorites.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L92) -- [apiFavorites/favorites.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L112) -- [apiFavorites/favorites.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L113) - [apiFavorites/favorites.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L128) - [apiFavorites/favorites.feature:129](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L129) - [apiFavorites/favorites.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L148) - [apiFavorites/favorites.feature:149](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L149) - [apiFavorites/favorites.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L176) - [apiFavorites/favorites.feature:177](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L177) -- [apiFavorites/favorites.feature:217](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L217) -- [apiFavorites/favorites.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L218) - [apiFavorites/favoritesSharingToShares.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L21) - [apiFavorites/favoritesSharingToShares.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L22) - [apiFavorites/favoritesSharingToShares.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L35) diff --git a/tests/oc-integration-tests/drone/frontend.toml b/tests/oc-integration-tests/drone/frontend.toml index bea95138ec3..5daa77fa9dd 100644 --- a/tests/oc-integration-tests/drone/frontend.toml +++ b/tests/oc-integration-tests/drone/frontend.toml @@ -59,6 +59,13 @@ files_namespace = "/oc" # - the oc js sdk is hardcoded to the remote.php/webdav so it will see the new tree # - TODO android? no sync ... but will see different tree webdav_namespace = "/home" +driver = "ocis" + +[http.services.ocdav.drivers.ocis] +root = "/drone/src/tmp/reva/data" +enable_home = true +treetime_accounting = true +treesize_accounting = true [http.services.ocs] diff --git a/tests/oc-integration-tests/local/frontend.toml b/tests/oc-integration-tests/local/frontend.toml index 5c98ac3c224..bd433b235c1 100644 --- a/tests/oc-integration-tests/local/frontend.toml +++ b/tests/oc-integration-tests/local/frontend.toml @@ -59,6 +59,13 @@ files_namespace = "/users" # - the oc js sdk is hardcoded to the remote.php/webdav so it will see the new tree # - TODO android? no sync ... but will see different tree webdav_namespace = "/home" +driver = "ocis" + +[http.services.ocdav.drivers.ocis] +root = "/drone/src/tmp/reva/data" +enable_home = true +treetime_accounting = true +treesize_accounting = true [http.services.ocs]