From 1e0b5cf02a5829b8fe37b468bdfac7cc2935b5b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Wed, 21 Aug 2024 16:03:56 +0200 Subject: [PATCH] allow listing directory trash items by key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../unreleased/trash-listing-bykey-fixes.md | 9 +++ .../storageprovider/storageprovider.go | 31 ++++++--- internal/http/services/owncloud/ocdav/dav.go | 2 +- .../http/services/owncloud/ocdav/spaces.go | 25 +++++--- .../http/services/owncloud/ocdav/trashbin.go | 42 ++++++------ pkg/storage/utils/decomposedfs/recycle.go | 51 ++++++++------- .../utils/decomposedfs/recycle_test.go | 64 +++++++++---------- 7 files changed, 132 insertions(+), 92 deletions(-) create mode 100644 changelog/unreleased/trash-listing-bykey-fixes.md diff --git a/changelog/unreleased/trash-listing-bykey-fixes.md b/changelog/unreleased/trash-listing-bykey-fixes.md new file mode 100644 index 0000000000..dd649013af --- /dev/null +++ b/changelog/unreleased/trash-listing-bykey-fixes.md @@ -0,0 +1,9 @@ +Bugfix: Allow listing directory trash items by key + +The storageprovider now passes on the key without inventing a `/` as the relative path when it was not present at the end of the key. This allows differentiating requests that want to get the trash item of a folder itself (where the relative path is empty) or listing the children of a folder in the trash (where the relative path at least starts with a `/`). + +We also fixed the `/dav/spaces` endpoint to not invent a `/` at the end of URLs to allow clients to actually make these different requests. + +As a byproduct we now return the size of trashed items. + +https://github.com/cs3org/reva/pull/4818 diff --git a/internal/grpc/services/storageprovider/storageprovider.go b/internal/grpc/services/storageprovider/storageprovider.go index 763148e8e7..3637980c0a 100644 --- a/internal/grpc/services/storageprovider/storageprovider.go +++ b/internal/grpc/services/storageprovider/storageprovider.go @@ -27,6 +27,7 @@ import ( "path" "sort" "strconv" + "strings" "time" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" @@ -880,8 +881,11 @@ func (s *Service) ListRecycleStream(req *provider.ListRecycleStreamRequest, ss p ctx := ss.Context() log := appctx.GetLogger(ctx) - key, itemPath := router.ShiftPath(req.Key) - items, err := s.Storage.ListRecycle(ctx, req.Ref, key, itemPath) + // if no slash is present in the key, do not pass a relative path to the storage + // when a path is passed to the storage, it will list the contents of the directory + key, relativePath := splitKeyAndPath(req.GetKey()) + items, err := s.Storage.ListRecycle(ctx, req.Ref, key, relativePath) + if err != nil { var st *rpc.Status switch err.(type) { @@ -924,8 +928,10 @@ func (s *Service) ListRecycleStream(req *provider.ListRecycleStreamRequest, ss p } func (s *Service) ListRecycle(ctx context.Context, req *provider.ListRecycleRequest) (*provider.ListRecycleResponse, error) { - key, itemPath := router.ShiftPath(req.Key) - items, err := s.Storage.ListRecycle(ctx, req.Ref, key, itemPath) + // if no slash is present in the key, do not pass a relative path to the storage + // when a path is passed to the storage, it will list the contents of the directory + key, relativePath := splitKeyAndPath(req.GetKey()) + items, err := s.Storage.ListRecycle(ctx, req.Ref, key, relativePath) if err != nil { var st *rpc.Status switch err.(type) { @@ -962,8 +968,8 @@ func (s *Service) RestoreRecycleItem(ctx context.Context, req *provider.RestoreR ctx = ctxpkg.ContextSetLockID(ctx, req.LockId) // TODO(labkode): CRITICAL: fill recycle info with storage provider. - key, itemPath := router.ShiftPath(req.Key) - err := s.Storage.RestoreRecycleItem(ctx, req.Ref, key, itemPath, req.RestoreRef) + key, relativePath := splitKeyAndPath(req.GetKey()) + err := s.Storage.RestoreRecycleItem(ctx, req.Ref, key, relativePath, req.RestoreRef) res := &provider.RestoreRecycleItemResponse{ Status: status.NewStatusFromErrType(ctx, "restore recycle item", err), @@ -980,9 +986,9 @@ func (s *Service) PurgeRecycle(ctx context.Context, req *provider.PurgeRecycleRe } // if a key was sent as opaque id purge only that item - key, itemPath := router.ShiftPath(req.Key) + key, relativePath := splitKeyAndPath(req.GetKey()) if key != "" { - if err := s.Storage.PurgeRecycleItem(ctx, req.Ref, key, itemPath); err != nil { + if err := s.Storage.PurgeRecycleItem(ctx, req.Ref, key, relativePath); err != nil { st := status.NewStatusFromErrType(ctx, "error purging recycle item", err) appctx.GetLogger(ctx). Error(). @@ -1313,3 +1319,12 @@ func canLockPublicShare(ctx context.Context) bool { psr := utils.ReadPlainFromOpaque(u.Opaque, "public-share-role") return psr == "" || psr == conversions.RoleEditor } + +// splitKeyAndPath splits a key into a root and a relative path +func splitKeyAndPath(key string) (string, string) { + root, relativePath := router.ShiftPath(key) + if relativePath == "/" && !strings.HasSuffix(key, "/") { + relativePath = "" + } + return root, relativePath +} diff --git a/internal/http/services/owncloud/ocdav/dav.go b/internal/http/services/owncloud/ocdav/dav.go index ba82e15439..7aff215a34 100644 --- a/internal/http/services/owncloud/ocdav/dav.go +++ b/internal/http/services/owncloud/ocdav/dav.go @@ -291,7 +291,7 @@ func (h *DavHandler) Handler(s *svc) http.Handler { http.Redirect(w, r, rUrl, http.StatusTemporaryRedirect) return } - log.Debug().Str("token", token).Interface("status", res.Status).Msg("resource id not found") + log.Debug().Str("token", token).Interface("status", psRes.Status).Msg("resource id not found") w.WriteHeader(http.StatusNotFound) return } diff --git a/internal/http/services/owncloud/ocdav/spaces.go b/internal/http/services/owncloud/ocdav/spaces.go index 37797d60ad..2439a98251 100644 --- a/internal/http/services/owncloud/ocdav/spaces.go +++ b/internal/http/services/owncloud/ocdav/spaces.go @@ -21,6 +21,7 @@ package ocdav import ( "net/http" "path" + "strings" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/config" @@ -132,8 +133,7 @@ func (h *SpacesHandler) handleSpacesTrashbin(w http.ResponseWriter, r *http.Requ ctx := r.Context() log := appctx.GetLogger(ctx) - var spaceID string - spaceID, r.URL.Path = router.ShiftPath(r.URL.Path) + spaceID, key := splitSpaceAndKey(r.URL.Path) if spaceID == "" { // listing is disabled, no auth will change that w.WriteHeader(http.StatusMethodNotAllowed) @@ -146,12 +146,9 @@ func (h *SpacesHandler) handleSpacesTrashbin(w http.ResponseWriter, r *http.Requ return } - var key string - key, r.URL.Path = router.ShiftPath(r.URL.Path) - switch r.Method { case MethodPropfind: - trashbinHandler.listTrashbin(w, r, s, &ref, path.Join(_trashbinPath, spaceID), key, r.URL.Path) + trashbinHandler.listTrashbin(w, r, s, &ref, path.Join(_trashbinPath, spaceID), key) case MethodMove: if key == "" { http.Error(w, "501 Not implemented", http.StatusNotImplemented) @@ -167,15 +164,25 @@ func (h *SpacesHandler) handleSpacesTrashbin(w http.ResponseWriter, r *http.Requ w.WriteHeader(http.StatusBadRequest) return } - log.Debug().Str("key", key).Str("path", r.URL.Path).Str("dst", dst).Msg("spaces restore") + log.Debug().Str("key", key).Str("dst", dst).Msg("spaces restore") dstRef := proto.Clone(&ref).(*provider.Reference) dstRef.Path = utils.MakeRelativePath(dst) - trashbinHandler.restore(w, r, s, &ref, dstRef, key, r.URL.Path) + trashbinHandler.restore(w, r, s, &ref, dstRef, key) case http.MethodDelete: - trashbinHandler.delete(w, r, s, &ref, key, r.URL.Path) + trashbinHandler.delete(w, r, s, &ref, key) default: http.Error(w, "501 Not implemented", http.StatusNotImplemented) } } + +func splitSpaceAndKey(p string) (space, key string) { + p = strings.TrimPrefix(p, "/") + parts := strings.SplitN(p, "/", 2) + space = parts[0] + if len(parts) > 1 { + key = parts[1] + } + return +} diff --git a/internal/http/services/owncloud/ocdav/trashbin.go b/internal/http/services/owncloud/ocdav/trashbin.go index ef742c54df..751a19bece 100644 --- a/internal/http/services/owncloud/ocdav/trashbin.go +++ b/internal/http/services/owncloud/ocdav/trashbin.go @@ -43,7 +43,6 @@ import ( "github.com/cs3org/reva/v2/pkg/appctx" ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" rstatus "github.com/cs3org/reva/v2/pkg/rgrpc/status" - "github.com/cs3org/reva/v2/pkg/rhttp/router" "github.com/cs3org/reva/v2/pkg/utils" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" ) @@ -74,7 +73,7 @@ func (h *TrashbinHandler) Handler(s *svc) http.Handler { } var username string - username, r.URL.Path = router.ShiftPath(r.URL.Path) + username, r.URL.Path = splitSpaceAndKey(r.URL.Path) if username == "" { // listing is disabled, no auth will change that w.WriteHeader(http.StatusMethodNotAllowed) @@ -131,13 +130,12 @@ func (h *TrashbinHandler) Handler(s *svc) http.Handler { } ref := spacelookup.MakeRelativeReference(space, ".", false) - // key will be a base64 encoded cs3 path, it uniquely identifies a trash item & storage - var key string - key, r.URL.Path = router.ShiftPath(r.URL.Path) + // key will be a base64 encoded cs3 path, it uniquely identifies a trash item with an opaque id and an optional path + key := r.URL.Path switch r.Method { case MethodPropfind: - h.listTrashbin(w, r, s, ref, user.Username, key, r.URL.Path) + h.listTrashbin(w, r, s, ref, user.Username, key) case MethodMove: if key == "" { http.Error(w, "501 Not implemented", http.StatusNotImplemented) @@ -172,16 +170,16 @@ func (h *TrashbinHandler) Handler(s *svc) http.Handler { dstRef := spacelookup.MakeRelativeReference(space, p, false) log.Debug().Str("key", key).Str("dst", dst).Msg("restore") - h.restore(w, r, s, ref, dstRef, key, r.URL.Path) + h.restore(w, r, s, ref, dstRef, key) case http.MethodDelete: - h.delete(w, r, s, ref, key, r.URL.Path) + h.delete(w, r, s, ref, key) default: http.Error(w, "501 Not implemented", http.StatusNotImplemented) } }) } -func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s *svc, ref *provider.Reference, refBase, key, itemPath string) { +func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s *svc, ref *provider.Reference, refBase, key string) { ctx, span := appctx.GetTracerProvider(r.Context()).Tracer(tracerName).Start(r.Context(), "list_trashbin") defer span.End() @@ -214,7 +212,7 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s } if depth == net.DepthZero { - rootHref := path.Join(refBase, key, itemPath) + rootHref := path.Join(refBase, key) propRes, err := h.formatTrashPropfind(ctx, s, ref.ResourceId.SpaceId, refBase, rootHref, nil, nil) if err != nil { sublog.Error().Err(err).Msg("error formatting propfind") @@ -232,6 +230,13 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s return } + if depth == net.DepthOne && !strings.HasSuffix(key, "/") { + // if the depth is one and the key is empty we are listing the root trashbin + // we need to list all trash items in the root trashbin + // and then list all sub-containers + key += "/" + } + pf, status, err := propfind.ReadPropfind(r.Body) if err != nil { sublog.Debug().Err(err).Msg("error reading propfind request") @@ -246,7 +251,7 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s return } // ask gateway for recycle items - getRecycleRes, err := client.ListRecycle(ctx, &provider.ListRecycleRequest{Ref: ref, Key: path.Join(key, itemPath)}) + getRecycleRes, err := client.ListRecycle(ctx, &provider.ListRecycleRequest{Ref: ref, Key: key}) if err != nil { sublog.Error().Err(err).Msg("error calling ListRecycle") w.WriteHeader(http.StatusInternalServerError) @@ -304,7 +309,7 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s } } - rootHref := path.Join(refBase, key, itemPath) + rootHref := path.Join(refBase, key) propRes, err := h.formatTrashPropfind(ctx, s, ref.ResourceId.SpaceId, refBase, rootHref, &pf, items) if err != nil { sublog.Error().Err(err).Msg("error formatting propfind") @@ -480,7 +485,7 @@ func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, spaceI return &response, nil } -func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc, ref, dst *provider.Reference, key, itemPath string) { +func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc, ref, dst *provider.Reference, key string) { ctx, span := appctx.GetTracerProvider(r.Context()).Tracer(tracerName).Start(r.Context(), "restore") defer span.End() @@ -566,7 +571,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc req := &provider.RestoreRecycleItemRequest{ Ref: ref, - Key: path.Join(key, itemPath), + Key: key, RestoreRef: dst, } @@ -608,16 +613,15 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc } // delete has only a key -func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, ref *provider.Reference, key, itemPath string) { +func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, ref *provider.Reference, key string) { ctx, span := appctx.GetTracerProvider(r.Context()).Tracer(tracerName).Start(r.Context(), "erase") defer span.End() - sublog := appctx.GetLogger(ctx).With().Interface("reference", ref).Str("key", key).Str("item_path", itemPath).Logger() + sublog := appctx.GetLogger(ctx).With().Interface("reference", ref).Str("key", key).Logger() - trashPath := path.Join(key, itemPath) req := &provider.PurgeRecycleRequest{ Ref: ref, - Key: trashPath, + Key: key, } client, err := s.gatewaySelector.Next() @@ -638,7 +642,7 @@ func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, case rpc.Code_CODE_NOT_FOUND: sublog.Debug().Interface("status", res.Status).Msg("resource not found") w.WriteHeader(http.StatusConflict) - m := fmt.Sprintf("path %s not found", trashPath) + m := fmt.Sprintf("key %s not found", key) b, err := errors.Marshal(http.StatusConflict, m, "", "") errors.HandleWebdavError(&sublog, w, b, err) case rpc.Code_CODE_PERMISSION_DENIED: diff --git a/pkg/storage/utils/decomposedfs/recycle.go b/pkg/storage/utils/decomposedfs/recycle.go index aa0ead067b..fdc99b806b 100644 --- a/pkg/storage/utils/decomposedfs/recycle.go +++ b/pkg/storage/utils/decomposedfs/recycle.go @@ -23,7 +23,6 @@ import ( iofs "io/fs" "os" "path/filepath" - "strconv" "strings" "time" @@ -55,6 +54,9 @@ func (fs *Decomposedfs) ListRecycle(ctx context.Context, ref *provider.Reference if ref == nil || ref.ResourceId == nil || ref.ResourceId.OpaqueId == "" { return nil, errtypes.BadRequest("spaceid required") } + if key == "" && relativePath != "" { + return nil, errtypes.BadRequest("key is required when navigating with a path") + } spaceID := ref.ResourceId.OpaqueId sublog := appctx.GetLogger(ctx).With().Str("spaceid", spaceID).Str("key", key).Str("relative_path", relativePath).Logger() @@ -75,7 +77,7 @@ func (fs *Decomposedfs) ListRecycle(ctx context.Context, ref *provider.Reference return nil, errtypes.NotFound(key) } - if key == "" && relativePath == "/" { + if key == "" && relativePath == "" { return fs.listTrashRoot(ctx, spaceID) } @@ -113,16 +115,25 @@ func (fs *Decomposedfs) ListRecycle(ctx context.Context, ref *provider.Reference sublog.Error().Err(err).Msg("could not parse time format, ignoring") } - nodeType := fs.lu.TypeFromPath(ctx, originalPath) - if nodeType != provider.ResourceType_RESOURCE_TYPE_CONTAINER { + var size int64 + if relativePath == "" { // this is the case when we want to directly list a file in the trashbin - blobsize, err := strconv.ParseInt(string(attrs[prefixes.BlobsizeAttr]), 10, 64) - if err != nil { - return items, err + nodeType := fs.lu.TypeFromPath(ctx, originalPath) + switch nodeType { + case provider.ResourceType_RESOURCE_TYPE_FILE: + size, err = fs.lu.ReadBlobSizeAttr(ctx, originalPath) + if err != nil { + return items, err + } + case provider.ResourceType_RESOURCE_TYPE_CONTAINER: + size, err = fs.lu.MetadataBackend().GetInt64(ctx, originalPath, prefixes.TreesizeAttr) + if err != nil { + return items, err + } } item := &provider.RecycleItem{ - Type: nodeType, - Size: uint64(blobsize), + Type: fs.lu.TypeFromPath(ctx, originalPath), + Size: uint64(size), Key: filepath.Join(key, relativePath), DeletionTime: deletionTime, Ref: &provider.Reference{ @@ -134,9 +145,6 @@ func (fs *Decomposedfs) ListRecycle(ctx context.Context, ref *provider.Reference } // we have to read the names and stat the path to follow the symlinks - if err != nil { - return nil, err - } childrenPath := filepath.Join(originalPath, relativePath) childrenDir, err := os.Open(childrenPath) if err != nil { @@ -154,9 +162,10 @@ func (fs *Decomposedfs) ListRecycle(ctx context.Context, ref *provider.Reference continue } - size := int64(0) + // reset size + size = 0 - nodeType = fs.lu.TypeFromPath(ctx, resolvedChildPath) + nodeType := fs.lu.TypeFromPath(ctx, resolvedChildPath) switch nodeType { case provider.ResourceType_RESOURCE_TYPE_FILE: size, err = fs.lu.ReadBlobSizeAttr(ctx, resolvedChildPath) @@ -165,12 +174,7 @@ func (fs *Decomposedfs) ListRecycle(ctx context.Context, ref *provider.Reference continue } case provider.ResourceType_RESOURCE_TYPE_CONTAINER: - attr, err := fs.lu.MetadataBackend().Get(ctx, resolvedChildPath, prefixes.TreesizeAttr) - if err != nil { - sublog.Error().Err(err).Str("name", name).Msg("invalid tree size, skipping") - continue - } - size, err = strconv.ParseInt(string(attr), 10, 64) + size, err = fs.lu.MetadataBackend().GetInt64(ctx, resolvedChildPath, prefixes.TreesizeAttr) if err != nil { sublog.Error().Err(err).Str("name", name).Msg("invalid tree size, skipping") continue @@ -217,7 +221,7 @@ func readTrashLink(path string) (string, string, string, error) { func (fs *Decomposedfs) listTrashRoot(ctx context.Context, spaceID string) ([]*provider.RecycleItem, error) { log := appctx.GetLogger(ctx) trashRoot := fs.getRecycleRoot(spaceID) - + items := []*provider.RecycleItem{} subTrees, err := filepath.Glob(trashRoot + "/*") if err != nil { return nil, err @@ -256,6 +260,7 @@ func (fs *Decomposedfs) listTrashRoot(ctx context.Context, spaceID string) ([]*p } for _, itemPath := range matches { + // TODO can we encode this in the path instead of reading the link? nodePath, nodeID, timeSuffix, err := readTrashLink(itemPath) if err != nil { log.Error().Err(err).Str("trashRoot", trashRoot).Str("item", itemPath).Msg("error reading trash link, skipping") @@ -300,6 +305,7 @@ func (fs *Decomposedfs) listTrashRoot(ctx context.Context, spaceID string) ([]*p } else { log.Error().Str("trashRoot", trashRoot).Str("item", itemPath).Str("spaceid", spaceID).Str("nodeid", nodeID).Str("dtime", timeSuffix).Msg("could not read origin path") } + select { case results <- item: case <-ctx.Done(): @@ -318,7 +324,6 @@ func (fs *Decomposedfs) listTrashRoot(ctx context.Context, spaceID string) ([]*p }() // Collect results - items := []*provider.RecycleItem{} for ri := range results { items = append(items, ri) } @@ -414,7 +419,7 @@ func (fs *Decomposedfs) EmptyRecycle(ctx context.Context, ref *provider.Referenc return errtypes.BadRequest("spaceid must be set") } - items, err := fs.ListRecycle(ctx, ref, "", "/") + items, err := fs.ListRecycle(ctx, ref, "", "") if err != nil { return err } diff --git a/pkg/storage/utils/decomposedfs/recycle_test.go b/pkg/storage/utils/decomposedfs/recycle_test.go index 5cfe9ec5e7..b883d39955 100644 --- a/pkg/storage/utils/decomposedfs/recycle_test.go +++ b/pkg/storage/utils/decomposedfs/recycle_test.go @@ -73,7 +73,7 @@ var _ = Describe("Recycle", func() { }) It("they are stored in the same trashbin", func() { - items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") + items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") Expect(err).ToNot(HaveOccurred()) Expect(len(items)).To(Equal(2)) }) @@ -88,17 +88,17 @@ var _ = Describe("Recycle", func() { // mock call to blobstore env.Blobstore.On("Delete", mock.Anything).Return(nil).Times(2) - items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") + items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") Expect(err).ToNot(HaveOccurred()) Expect(len(items)).To(Equal(2)) - err = env.Fs.PurgeRecycleItem(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "/") + err = env.Fs.PurgeRecycleItem(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "") Expect(err).ToNot(HaveOccurred()) - err = env.Fs.PurgeRecycleItem(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, items[1].Key, "/") + err = env.Fs.PurgeRecycleItem(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, items[1].Key, "") Expect(err).ToNot(HaveOccurred()) - items, err = env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") + items, err = env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") Expect(err).ToNot(HaveOccurred()) Expect(len(items)).To(Equal(0)) }) @@ -106,14 +106,14 @@ var _ = Describe("Recycle", func() { It("they can be restored", func() { env.Blobstore.On("Delete", mock.Anything).Return(nil).Times(2) - items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") + items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") Expect(err).ToNot(HaveOccurred()) Expect(len(items)).To(Equal(2)) - err = env.Fs.RestoreRecycleItem(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "/", nil) + err = env.Fs.RestoreRecycleItem(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "", nil) Expect(err).ToNot(HaveOccurred()) - items, err = env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") + items, err = env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") Expect(err).ToNot(HaveOccurred()) Expect(len(items)).To(Equal(1)) }) @@ -167,10 +167,10 @@ var _ = Describe("Recycle", func() { }) It("they are stored in the same trashbin (for both users)", func() { - itemsA, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") + itemsA, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") Expect(err).ToNot(HaveOccurred()) Expect(len(itemsA)).To(Equal(2)) - itemsB, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") + itemsB, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") Expect(err).ToNot(HaveOccurred()) Expect(len(itemsB)).To(Equal(2)) Expect(itemsA).To(ConsistOf(itemsB)) @@ -179,7 +179,7 @@ var _ = Describe("Recycle", func() { It("they can be permanently deleted by the other user", func() { env.Blobstore.On("Delete", mock.Anything).Return(nil).Times(2) - items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") + items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") Expect(err).ToNot(HaveOccurred()) Expect(len(items)).To(Equal(2)) @@ -194,13 +194,13 @@ var _ = Describe("Recycle", func() { ctx2 = env.Ctx } - err = env.Fs.PurgeRecycleItem(ctx1, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "/") + err = env.Fs.PurgeRecycleItem(ctx1, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "") Expect(err).ToNot(HaveOccurred()) - err = env.Fs.PurgeRecycleItem(ctx2, &provider.Reference{ResourceId: env.SpaceRootRes}, items[1].Key, "/") + err = env.Fs.PurgeRecycleItem(ctx2, &provider.Reference{ResourceId: env.SpaceRootRes}, items[1].Key, "") Expect(err).ToNot(HaveOccurred()) - items, err = env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") + items, err = env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") Expect(err).ToNot(HaveOccurred()) Expect(len(items)).To(Equal(0)) }) @@ -208,7 +208,7 @@ var _ = Describe("Recycle", func() { It("they can be restored by the other user", func() { env.Blobstore.On("Delete", mock.Anything).Return(nil).Times(2) - items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") + items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") Expect(err).ToNot(HaveOccurred()) Expect(len(items)).To(Equal(2)) @@ -223,13 +223,13 @@ var _ = Describe("Recycle", func() { ctx2 = env.Ctx } - err = env.Fs.RestoreRecycleItem(ctx1, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "/", nil) + err = env.Fs.RestoreRecycleItem(ctx1, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "", nil) Expect(err).ToNot(HaveOccurred()) - err = env.Fs.RestoreRecycleItem(ctx2, &provider.Reference{ResourceId: env.SpaceRootRes}, items[1].Key, "/", nil) + err = env.Fs.RestoreRecycleItem(ctx2, &provider.Reference{ResourceId: env.SpaceRootRes}, items[1].Key, "", nil) Expect(err).ToNot(HaveOccurred()) - items, err = env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") + items, err = env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") Expect(err).ToNot(HaveOccurred()) Expect(len(items)).To(Equal(0)) }) @@ -269,12 +269,12 @@ var _ = Describe("Recycle", func() { }) It("they are stored in different trashbins", func() { - items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") + items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") Expect(err).ToNot(HaveOccurred()) Expect(len(items)).To(Equal(1)) recycled1 := items[0] - items, err = env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: projectID}, "", "/") + items, err = env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: projectID}, "", "") Expect(err).ToNot(HaveOccurred()) Expect(len(items)).To(Equal(1)) recycled2 := items[0] @@ -283,7 +283,7 @@ var _ = Describe("Recycle", func() { }) It("they can excess the spaces quota if restored", func() { - items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: projectID}, "", "/") + items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: projectID}, "", "") Expect(err).ToNot(HaveOccurred()) Expect(len(items)).To(Equal(1)) @@ -291,7 +291,7 @@ var _ = Describe("Recycle", func() { _, err = env.CreateTestFile("largefile", "largefile-blobid", projectID.OpaqueId, projectID.SpaceId, 2000) Expect(err).ToNot(HaveOccurred()) - err = env.Fs.RestoreRecycleItem(env.Ctx, &provider.Reference{ResourceId: projectID}, items[0].Key, "/", nil) + err = env.Fs.RestoreRecycleItem(env.Ctx, &provider.Reference{ResourceId: projectID}, items[0].Key, "", nil) Expect(err).ToNot(HaveOccurred()) max, used, remaining, err := env.Fs.GetQuota(env.Ctx, &provider.Reference{ResourceId: projectID}) @@ -340,7 +340,7 @@ var _ = Describe("Recycle", func() { }) Expect(err).ToNot(HaveOccurred()) - items, err := env.Fs.ListRecycle(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") + items, err := env.Fs.ListRecycle(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") Expect(err).ToNot(HaveOccurred()) Expect(len(items)).To(Equal(1)) }) @@ -353,7 +353,7 @@ var _ = Describe("Recycle", func() { Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("permission denied")) - items, err := env.Fs.ListRecycle(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") + items, err := env.Fs.ListRecycle(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") Expect(err).ToNot(HaveOccurred()) Expect(len(items)).To(Equal(0)) }) @@ -365,11 +365,11 @@ var _ = Describe("Recycle", func() { }) Expect(err).ToNot(HaveOccurred()) - items, err := env.Fs.ListRecycle(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") + items, err := env.Fs.ListRecycle(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") Expect(err).ToNot(HaveOccurred()) Expect(len(items)).To(Equal(1)) - err = env.Fs.PurgeRecycleItem(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "/") + err = env.Fs.PurgeRecycleItem(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("permission denied")) }) @@ -381,11 +381,11 @@ var _ = Describe("Recycle", func() { }) Expect(err).ToNot(HaveOccurred()) - items, err := env.Fs.ListRecycle(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") + items, err := env.Fs.ListRecycle(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") Expect(err).ToNot(HaveOccurred()) Expect(len(items)).To(Equal(1)) - err = env.Fs.RestoreRecycleItem(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "/", nil) + err = env.Fs.RestoreRecycleItem(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "", nil) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("permission denied")) }) @@ -432,19 +432,19 @@ var _ = Describe("Recycle", func() { }) Expect(err).ToNot(HaveOccurred()) - _, err = env.Fs.ListRecycle(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") + _, err = env.Fs.ListRecycle(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("not found")) - items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "/") + items, err := env.Fs.ListRecycle(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, "", "") Expect(err).ToNot(HaveOccurred()) Expect(len(items)).To(Equal(1)) - err = env.Fs.PurgeRecycleItem(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "/") + err = env.Fs.PurgeRecycleItem(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("not found")) - err = env.Fs.RestoreRecycleItem(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "/", nil) + err = env.Fs.RestoreRecycleItem(ctx, &provider.Reference{ResourceId: env.SpaceRootRes}, items[0].Key, "", nil) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("not found")) })