From 34426fecd5b26e50d7beca218bf7d3009ce9b7a0 Mon Sep 17 00:00:00 2001 From: David Christofas Date: Fri, 2 Jul 2021 15:13:18 +0200 Subject: [PATCH] make recycle methods require a key and a path explicitly --- .../unreleased/support-recycle-subpaths.md | 6 +++ .../grpc/services/gateway/storageprovider.go | 1 - .../storageprovider/storageprovider.go | 18 ++++---- .../http/services/owncloud/ocdav/trashbin.go | 22 +++++----- pkg/storage/fs/owncloud/owncloud.go | 22 +++++----- pkg/storage/fs/owncloudsql/owncloudsql.go | 20 ++++----- pkg/storage/fs/s3/s3.go | 6 +-- pkg/storage/storage.go | 6 +-- pkg/storage/utils/decomposedfs/recycle.go | 42 ++++++------------- pkg/storage/utils/eosfs/eosfs.go | 8 ++-- pkg/storage/utils/localfs/localfs.go | 18 ++++---- 11 files changed, 79 insertions(+), 90 deletions(-) create mode 100644 changelog/unreleased/support-recycle-subpaths.md diff --git a/changelog/unreleased/support-recycle-subpaths.md b/changelog/unreleased/support-recycle-subpaths.md new file mode 100644 index 0000000000..bc413d352f --- /dev/null +++ b/changelog/unreleased/support-recycle-subpaths.md @@ -0,0 +1,6 @@ +Enhancement: Support trashbin sub paths in the recycle API + +The recycle API could only act on the root items of the trashbin. Meaning if you delete a deep tree, you couldn't restore just one file from that tree but you had to restore the whole tree. Now listing, restoring and purging work also for sub paths in the trashbin. + +https://github.com/cs3org/reva/pull/1827 + diff --git a/internal/grpc/services/gateway/storageprovider.go b/internal/grpc/services/gateway/storageprovider.go index 1ce15afe34..33e5d7ff27 100644 --- a/internal/grpc/services/gateway/storageprovider.go +++ b/internal/grpc/services/gateway/storageprovider.go @@ -1954,7 +1954,6 @@ func (s *svc) RestoreRecycleItem(ctx context.Context, req *provider.RestoreRecyc } func (s *svc) PurgeRecycle(ctx context.Context, req *gateway.PurgeRecycleRequest) (*provider.PurgeRecycleResponse, error) { - // lookup storage by treating the key as a path. It has been prefixed with the storage path in ListRecycle c, err := s.find(ctx, req.Ref) if err != nil { return &provider.PurgeRecycleResponse{ diff --git a/internal/grpc/services/storageprovider/storageprovider.go b/internal/grpc/services/storageprovider/storageprovider.go index 176285fe86..cdf813b73b 100644 --- a/internal/grpc/services/storageprovider/storageprovider.go +++ b/internal/grpc/services/storageprovider/storageprovider.go @@ -36,6 +36,7 @@ import ( "github.com/cs3org/reva/pkg/mime" "github.com/cs3org/reva/pkg/rgrpc" "github.com/cs3org/reva/pkg/rgrpc/status" + "github.com/cs3org/reva/pkg/rhttp/router" "github.com/cs3org/reva/pkg/storage" "github.com/cs3org/reva/pkg/storage/fs/registry" "github.com/mitchellh/mapstructure" @@ -793,7 +794,12 @@ func (s *service) ListRecycleStream(req *provider.ListRecycleStreamRequest, ss p ctx := ss.Context() log := appctx.GetLogger(ctx) - items, err := s.storage.ListRecycle(ctx, nil) + ref, err := s.unwrap(ctx, req.Ref) + if err != nil { + return err + } + + items, err := s.storage.ListRecycle(ctx, ref.ResourceId.OpaqueId, ref.Path) if err != nil { var st *rpc.Status switch err.(type) { @@ -833,7 +839,8 @@ func (s *service) ListRecycle(ctx context.Context, req *provider.ListRecycleRequ if err != nil { return nil, err } - items, err := s.storage.ListRecycle(ctx, ref) + key, itemPath := router.ShiftPath(ref.Path) + items, err := s.storage.ListRecycle(ctx, key, itemPath) // TODO(labkode): CRITICAL: fill recycle info with storage provider. if err != nil { var st *rpc.Status @@ -863,10 +870,7 @@ func (s *service) RestoreRecycleItem(ctx context.Context, req *provider.RestoreR if err != nil { return nil, err } - ref.ResourceId = &provider.ResourceId{ - OpaqueId: req.Key, - } - if err := s.storage.RestoreRecycleItem(ctx, ref, req.RestoreRef); err != nil { + if err := s.storage.RestoreRecycleItem(ctx, req.Key, ref.Path, req.RestoreRef); err != nil { var st *rpc.Status switch err.(type) { case errtypes.IsNotFound: @@ -890,7 +894,7 @@ func (s *service) RestoreRecycleItem(ctx context.Context, req *provider.RestoreR func (s *service) PurgeRecycle(ctx context.Context, req *provider.PurgeRecycleRequest) (*provider.PurgeRecycleResponse, error) { // if a key was sent as opaque id purge only that item if req.GetRef().GetResourceId() != nil && req.GetRef().GetResourceId().OpaqueId != "" { - if err := s.storage.PurgeRecycleItem(ctx, req.GetRef()); err != nil { + if err := s.storage.PurgeRecycleItem(ctx, req.GetRef().GetResourceId().OpaqueId, req.GetRef().Path); err != nil { var st *rpc.Status switch err.(type) { case errtypes.IsNotFound: diff --git a/internal/http/services/owncloud/ocdav/trashbin.go b/internal/http/services/owncloud/ocdav/trashbin.go index 3a32ccc6b0..a06a37d9c0 100644 --- a/internal/http/services/owncloud/ocdav/trashbin.go +++ b/internal/http/services/owncloud/ocdav/trashbin.go @@ -24,7 +24,6 @@ import ( "fmt" "net/http" "path" - "path/filepath" "strconv" "strings" "time" @@ -108,7 +107,7 @@ func (h *TrashbinHandler) Handler(s *svc) http.Handler { //} if r.Method == "PROPFIND" { - h.listTrashbin(w, r, s, u, path.Join(key, r.URL.Path)) + h.listTrashbin(w, r, s, u, key, r.URL.Path) return } if key != "" && r.Method == "MOVE" { @@ -142,7 +141,7 @@ func (h *TrashbinHandler) Handler(s *svc) http.Handler { }) } -func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s *svc, u *userpb.User, key string) { +func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s *svc, u *userpb.User, key, itemPath string) { ctx := r.Context() ctx, span := trace.StartSpan(ctx, "listTrashbin") defer span.End() @@ -190,8 +189,7 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s } // ask gateway for recycle items - // TODO(labkode): add Reference to ListRecycleRequest - getRecycleRes, err := gc.ListRecycle(ctx, &gateway.ListRecycleRequest{Ref: &provider.Reference{Path: filepath.Join(getHomeRes.Path, key)}}) + getRecycleRes, err := gc.ListRecycle(ctx, &gateway.ListRecycleRequest{Ref: &provider.Reference{Path: path.Join(getHomeRes.Path, key, itemPath)}}) if err != nil { sublog.Error().Err(err).Msg("error calling ListRecycle") @@ -331,7 +329,7 @@ func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, u *use Prop: []*propertyXML{}, }) // yes this is redundant, can be derived from oc:trashbin-original-location which contains the full path, clients should not fetch it - response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:trashbin-original-filename", filepath.Base(item.Ref.Path))) + response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:trashbin-original-filename", path.Base(item.Ref.Path))) response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:trashbin-original-location", strings.TrimPrefix(item.Ref.Path, "/"))) response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:trashbin-delete-timestamp", strconv.FormatUint(item.DeletionTime.Seconds, 10))) response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:trashbin-delete-datetime", dTime)) @@ -368,7 +366,7 @@ func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, u *use } case "trashbin-original-filename": // yes this is redundant, can be derived from oc:trashbin-original-location which contains the full path, clients should not fetch it - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:trashbin-original-filename", filepath.Base(item.Ref.Path))) + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:trashbin-original-filename", path.Base(item.Ref.Path))) case "trashbin-original-location": // TODO (jfd) double check and clarify the cs3 spec what the Key is about and if Path is only the folder that contains the file or if it includes the filename propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:trashbin-original-location", strings.TrimPrefix(item.Ref.Path, "/"))) @@ -415,7 +413,7 @@ func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, u *use } // restore has a destination and a key -func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc, u *userpb.User, dst, key, resourcePath string) { +func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc, u *userpb.User, dst, key, itemPath string) { ctx := r.Context() ctx, span := trace.StartSpan(ctx, "restore") defer span.End() @@ -453,7 +451,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc } dstRef := &provider.Reference{ - Path: filepath.Join(getHomeRes.Path, dst), + Path: path.Join(getHomeRes.Path, dst), } dstStatReq := &provider.StatRequest{ @@ -516,7 +514,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc // use the key which is prefixed with the StoragePath to lookup the correct storage ... // TODO currently limited to the home storage Ref: &provider.Reference{ - Path: path.Join(getHomeRes.Path, resourcePath), + Path: path.Join(getHomeRes.Path, itemPath), }, Key: key, RestoreRef: &provider.Reference{Path: dst}, @@ -555,7 +553,7 @@ 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, u *userpb.User, key, path string) { +func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, u *userpb.User, key, itemPath string) { ctx := r.Context() ctx, span := trace.StartSpan(ctx, "erase") defer span.End() @@ -599,7 +597,7 @@ func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, StorageId: sRes.Info.Id.StorageId, OpaqueId: key, }, - Path: utils.MakeRelativePath(path), + Path: utils.MakeRelativePath(itemPath), }, } diff --git a/pkg/storage/fs/owncloud/owncloud.go b/pkg/storage/fs/owncloud/owncloud.go index 05e24f9719..43da2744e2 100644 --- a/pkg/storage/fs/owncloud/owncloud.go +++ b/pkg/storage/fs/owncloud/owncloud.go @@ -2060,12 +2060,12 @@ func (fs *ocfs) RestoreRevision(ctx context.Context, ref *provider.Reference, re return fs.propagate(ctx, ip) } -func (fs *ocfs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference) error { +func (fs *ocfs) PurgeRecycleItem(ctx context.Context, key, path string) error { rp, err := fs.getRecyclePath(ctx) if err != nil { return errors.Wrap(err, "ocfs: error resolving recycle path") } - ip := filepath.Join(rp, filepath.Clean(ref.ResourceId.OpaqueId)) + ip := filepath.Join(rp, filepath.Clean(key)) // TODO check permission? // check permissions @@ -2086,7 +2086,7 @@ func (fs *ocfs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference) e if err != nil { return errors.Wrap(err, "ocfs: error deleting recycle item") } - err = os.RemoveAll(filepath.Join(filepath.Dir(rp), "versions", filepath.Clean(ref.ResourceId.OpaqueId))) + err = os.RemoveAll(filepath.Join(filepath.Dir(rp), "versions", filepath.Clean(key))) if err != nil { return errors.Wrap(err, "ocfs: error deleting recycle item versions") } @@ -2153,7 +2153,7 @@ func (fs *ocfs) convertToRecycleItem(ctx context.Context, rp string, md os.FileI } } -func (fs *ocfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*provider.RecycleItem, error) { +func (fs *ocfs) ListRecycle(ctx context.Context, key, path string) ([]*provider.RecycleItem, error) { // TODO check permission? on what? user must be the owner? rp, err := fs.getRecyclePath(ctx) if err != nil { @@ -2161,7 +2161,7 @@ func (fs *ocfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*pr } // list files folder - mds, err := ioutil.ReadDir(filepath.Join(rp, ref.Path)) + mds, err := ioutil.ReadDir(filepath.Join(rp, key)) if err != nil { log := appctx.GetLogger(ctx) log.Debug().Err(err).Str("path", rp).Msg("trash not readable") @@ -2180,18 +2180,18 @@ func (fs *ocfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*pr return items, nil } -func (fs *ocfs) RestoreRecycleItem(ctx context.Context, trashRef *provider.Reference, restoreRef *provider.Reference) error { +func (fs *ocfs) RestoreRecycleItem(ctx context.Context, key, path string, restoreRef *provider.Reference) error { // TODO check permission? on what? user must be the owner? log := appctx.GetLogger(ctx) rp, err := fs.getRecyclePath(ctx) if err != nil { return errors.Wrap(err, "ocfs: error resolving recycle path") } - src := filepath.Join(rp, filepath.Clean(trashRef.ResourceId.OpaqueId)) + src := filepath.Join(rp, filepath.Clean(key)) suffix := filepath.Ext(src) if len(suffix) == 0 || !strings.HasPrefix(suffix, ".d") { - log.Error().Str("key", trashRef.ResourceId.OpaqueId).Str("path", src).Msg("invalid trash item suffix") + log.Error().Str("key", key).Str("path", src).Msg("invalid trash item suffix") return nil } @@ -2201,20 +2201,20 @@ func (fs *ocfs) RestoreRecycleItem(ctx context.Context, trashRef *provider.Refer if restoreRef.Path == "" { v, err := xattr.Get(src, trashOriginPrefix) if err != nil { - log.Error().Err(err).Str("key", trashRef.ResourceId.OpaqueId).Str("path", src).Msg("could not read origin") + log.Error().Err(err).Str("key", key).Str("path", src).Msg("could not read origin") } restoreRef.Path = filepath.Join("/", filepath.Clean(string(v)), strings.TrimSuffix(filepath.Base(src), suffix)) } tgt := fs.toInternalPath(ctx, restoreRef.Path) // move back to original location if err := os.Rename(src, tgt); err != nil { - log.Error().Err(err).Str("key", trashRef.ResourceId.OpaqueId).Str("restorePath", restoreRef.Path).Str("src", src).Str("tgt", tgt).Msg("could not restore item") + log.Error().Err(err).Str("key", key).Str("restorePath", restoreRef.Path).Str("src", src).Str("tgt", tgt).Msg("could not restore item") return errors.Wrap(err, "ocfs: could not restore item") } // unset trash origin location in metadata if err := xattr.Remove(tgt, trashOriginPrefix); err != nil { // just a warning, will be overwritten next time it is deleted - log.Warn().Err(err).Str("key", trashRef.ResourceId.OpaqueId).Str("tgt", tgt).Msg("could not unset origin") + log.Warn().Err(err).Str("key", key).Str("tgt", tgt).Msg("could not unset origin") } // TODO(jfd) restore versions diff --git a/pkg/storage/fs/owncloudsql/owncloudsql.go b/pkg/storage/fs/owncloudsql/owncloudsql.go index 4d9d478b65..6a004f46e2 100644 --- a/pkg/storage/fs/owncloudsql/owncloudsql.go +++ b/pkg/storage/fs/owncloudsql/owncloudsql.go @@ -1864,12 +1864,12 @@ func (fs *ocfs) RestoreRevision(ctx context.Context, ref *provider.Reference, re return fs.propagate(ctx, ip) } -func (fs *ocfs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference) error { +func (fs *ocfs) PurgeRecycleItem(ctx context.Context, key, path string) error { rp, err := fs.getRecyclePath(ctx) if err != nil { return errors.Wrap(err, "owncloudsql: error resolving recycle path") } - ip := filepath.Join(rp, filepath.Clean(ref.ResourceId.OpaqueId)) + ip := filepath.Join(rp, filepath.Clean(key)) // TODO check permission? // check permissions @@ -1890,12 +1890,12 @@ func (fs *ocfs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference) e if err != nil { return errors.Wrap(err, "owncloudsql: error deleting recycle item") } - err = os.RemoveAll(filepath.Join(filepath.Dir(rp), "versions", filepath.Clean(ref.ResourceId.OpaqueId))) + err = os.RemoveAll(filepath.Join(filepath.Dir(rp), "versions", filepath.Clean(key))) if err != nil { return errors.Wrap(err, "owncloudsql: error deleting recycle item versions") } - base, ttime, err := splitTrashKey(ref.ResourceId.OpaqueId) + base, ttime, err := splitTrashKey(key) if err != nil { return err } @@ -1972,7 +1972,7 @@ func (fs *ocfs) convertToRecycleItem(ctx context.Context, md os.FileInfo) *provi } } -func (fs *ocfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*provider.RecycleItem, error) { +func (fs *ocfs) ListRecycle(ctx context.Context, key, path string) ([]*provider.RecycleItem, error) { // TODO check permission? on what? user must be the owner? rp, err := fs.getRecyclePath(ctx) if err != nil { @@ -1999,32 +1999,32 @@ func (fs *ocfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*pr return items, nil } -func (fs *ocfs) RestoreRecycleItem(ctx context.Context, trashRef *provider.Reference, restoreRef *provider.Reference) error { +func (fs *ocfs) RestoreRecycleItem(ctx context.Context, key, path string, restoreRef *provider.Reference) error { // TODO check permission? on what? user must be the owner? log := appctx.GetLogger(ctx) rp, err := fs.getRecyclePath(ctx) if err != nil { return errors.Wrap(err, "owncloudsql: error resolving recycle path") } - src := filepath.Join(rp, filepath.Clean(trashRef.ResourceId.OpaqueId)) + src := filepath.Join(rp, filepath.Clean(key)) suffix := filepath.Ext(src) if len(suffix) == 0 || !strings.HasPrefix(suffix, ".d") { - log.Error().Str("key", trashRef.ResourceId.OpaqueId).Str("path", src).Msg("invalid trash item suffix") + log.Error().Str("key", key).Str("path", src).Msg("invalid trash item suffix") return nil } if restoreRef.Path == "" { v, err := xattr.Get(src, trashOriginPrefix) if err != nil { - log.Error().Err(err).Str("key", trashRef.ResourceId.OpaqueId).Str("path", src).Msg("could not read origin") + log.Error().Err(err).Str("key", key).Str("path", src).Msg("could not read origin") } restoreRef.Path = filepath.Join("/", filepath.Clean(string(v)), strings.TrimSuffix(filepath.Base(src), suffix)) } tgt := fs.toInternalPath(ctx, restoreRef.Path) // move back to original location if err := os.Rename(src, tgt); err != nil { - log.Error().Err(err).Str("key", trashRef.ResourceId.OpaqueId).Str("restorePath", restoreRef.Path).Str("src", src).Str("tgt", tgt).Msg("could not restore item") + log.Error().Err(err).Str("key", key).Str("restorePath", restoreRef.Path).Str("src", src).Str("tgt", tgt).Msg("could not restore item") return errors.Wrap(err, "owncloudsql: could not restore item") } diff --git a/pkg/storage/fs/s3/s3.go b/pkg/storage/fs/s3/s3.go index ebff312e93..f0c8fc73c7 100644 --- a/pkg/storage/fs/s3/s3.go +++ b/pkg/storage/fs/s3/s3.go @@ -646,7 +646,7 @@ func (fs *s3FS) RestoreRevision(ctx context.Context, ref *provider.Reference, re return errtypes.NotSupported("restore revision") } -func (fs *s3FS) PurgeRecycleItem(ctx context.Context, ref *provider.Reference) error { +func (fs *s3FS) PurgeRecycleItem(ctx context.Context, key, path string) error { return errtypes.NotSupported("purge recycle item") } @@ -654,11 +654,11 @@ func (fs *s3FS) EmptyRecycle(ctx context.Context) error { return errtypes.NotSupported("empty recycle") } -func (fs *s3FS) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*provider.RecycleItem, error) { +func (fs *s3FS) ListRecycle(ctx context.Context, key, path string) ([]*provider.RecycleItem, error) { return nil, errtypes.NotSupported("list recycle") } -func (fs *s3FS) RestoreRecycleItem(ctx context.Context, trashRef *provider.Reference, restoreRef *provider.Reference) error { +func (fs *s3FS) RestoreRecycleItem(ctx context.Context, key, path string, restoreRef *provider.Reference) error { return errtypes.NotSupported("restore recycle") } diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index 5d198f2418..1392bbfcff 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -42,9 +42,9 @@ type FS interface { ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) DownloadRevision(ctx context.Context, ref *provider.Reference, key string) (io.ReadCloser, error) RestoreRevision(ctx context.Context, ref *provider.Reference, key string) error - ListRecycle(ctx context.Context, ref *provider.Reference) ([]*provider.RecycleItem, error) - RestoreRecycleItem(ctx context.Context, trashRef *provider.Reference, restoreRef *provider.Reference) error - PurgeRecycleItem(ctx context.Context, ref *provider.Reference) error + ListRecycle(ctx context.Context, key, path string) ([]*provider.RecycleItem, error) + RestoreRecycleItem(ctx context.Context, key, path string, restoreRef *provider.Reference) error + PurgeRecycleItem(ctx context.Context, key, path string) error EmptyRecycle(ctx context.Context) error GetPathByID(ctx context.Context, id *provider.ResourceId) (string, error) AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error diff --git a/pkg/storage/utils/decomposedfs/recycle.go b/pkg/storage/utils/decomposedfs/recycle.go index 51b107f751..d3dcebe24a 100644 --- a/pkg/storage/utils/decomposedfs/recycle.go +++ b/pkg/storage/utils/decomposedfs/recycle.go @@ -46,7 +46,7 @@ import ( // contain a directory with symlinks to trash files for every userid/"root" // ListRecycle returns the list of available recycle items -func (fs *Decomposedfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*provider.RecycleItem, error) { +func (fs *Decomposedfs) ListRecycle(ctx context.Context, key, path string) ([]*provider.RecycleItem, error) { log := appctx.GetLogger(ctx) items := make([]*provider.RecycleItem, 0) @@ -65,12 +65,12 @@ func (fs *Decomposedfs) ListRecycle(ctx context.Context, ref *provider.Reference } } - if ref.Path == "/" { + if key == "" && path == "/" { return fs.listTrashRoot(ctx) } trashRoot := fs.getRecycleRoot(ctx) - f, err := os.Open(filepath.Join(trashRoot, ref.Path)) + f, err := os.Open(filepath.Join(trashRoot, key, path)) if err != nil { if os.IsNotExist(err) { return items, nil @@ -79,8 +79,7 @@ func (fs *Decomposedfs) ListRecycle(ctx context.Context, ref *provider.Reference } defer f.Close() - root, tail := shiftPath(ref.Path) - parentNode, err := os.Readlink(filepath.Join(trashRoot, root)) + parentNode, err := os.Readlink(filepath.Join(trashRoot, key)) if err != nil { log.Error().Err(err).Str("trashRoot", trashRoot).Msg("error reading trash link, skipping") return nil, err @@ -90,7 +89,7 @@ func (fs *Decomposedfs) ListRecycle(ctx context.Context, ref *provider.Reference return nil, err } else if !md.IsDir() { // this is the case when we want to directly list a file in the trashbin - item, err := fs.createTrashItem(ctx, parentNode, filepath.Dir(tail), filepath.Join(trashRoot, ref.Path)) + item, err := fs.createTrashItem(ctx, parentNode, filepath.Dir(path), filepath.Join(trashRoot, key, path)) if err != nil { return items, err } @@ -103,7 +102,7 @@ func (fs *Decomposedfs) ListRecycle(ctx context.Context, ref *provider.Reference return nil, err } for i := range names { - if item, err := fs.createTrashItem(ctx, parentNode, tail, filepath.Join(trashRoot, ref.Path, names[i])); err == nil { + if item, err := fs.createTrashItem(ctx, parentNode, path, filepath.Join(trashRoot, key, path, names[i])); err == nil { items = append(items, item) } } @@ -259,11 +258,11 @@ func (fs *Decomposedfs) listTrashRoot(ctx context.Context) ([]*provider.RecycleI } // RestoreRecycleItem restores the specified item -func (fs *Decomposedfs) RestoreRecycleItem(ctx context.Context, trashRef *provider.Reference, restoreRef *provider.Reference) error { +func (fs *Decomposedfs) RestoreRecycleItem(ctx context.Context, key, path string, restoreRef *provider.Reference) error { if restoreRef == nil { restoreRef = &provider.Reference{} } - rn, restoreFunc, err := fs.tp.RestoreRecycleItemFunc(ctx, trashRef.ResourceId.OpaqueId, trashRef.Path, restoreRef.Path) + rn, restoreFunc, err := fs.tp.RestoreRecycleItemFunc(ctx, key, path, restoreRef.Path) if err != nil { return err } @@ -276,7 +275,7 @@ func (fs *Decomposedfs) RestoreRecycleItem(ctx context.Context, trashRef *provid case err != nil: return errtypes.InternalError(err.Error()) case !ok: - return errtypes.PermissionDenied(trashRef.ResourceId.OpaqueId) + return errtypes.PermissionDenied(key) } // Run the restore func @@ -284,8 +283,8 @@ func (fs *Decomposedfs) RestoreRecycleItem(ctx context.Context, trashRef *provid } // PurgeRecycleItem purges the specified item -func (fs *Decomposedfs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference) error { - rn, purgeFunc, err := fs.tp.PurgeRecycleItemFunc(ctx, ref.ResourceId.OpaqueId, ref.Path) +func (fs *Decomposedfs) PurgeRecycleItem(ctx context.Context, key, path string) error { + rn, purgeFunc, err := fs.tp.PurgeRecycleItemFunc(ctx, key, path) if err != nil { return err } @@ -298,7 +297,7 @@ func (fs *Decomposedfs) PurgeRecycleItem(ctx context.Context, ref *provider.Refe case err != nil: return errtypes.InternalError(err.Error()) case !ok: - return errtypes.PermissionDenied(ref.ResourceId.OpaqueId) + return errtypes.PermissionDenied(key) } // Run the purge func @@ -333,20 +332,3 @@ func (fs *Decomposedfs) getRecycleRoot(ctx context.Context) string { } return filepath.Join(fs.o.Root, "trash", "root") } - -// shiftPath splits off the first component of p, which will be cleaned of -// relative components before processing. head will never contain a slash and -// tail will always be a rooted path without trailing slash. -// see https://blog.merovius.de/2017/06/18/how-not-to-use-an-http-router.html -// and https://gist.github.com/weatherglass/62bd8a704d4dfdc608fe5c5cb5a6980c#gistcomment-2161690 for the zero alloc code below -func shiftPath(p string) (head, tail string) { - if p == "" { - return "", "/" - } - p = strings.TrimPrefix(path.Clean(p), "/") - i := strings.Index(p, "/") - if i < 0 { - return p, "/" - } - return p[:i], p[i:] -} diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index 288ba63bbd..81b927b8c6 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -1362,7 +1362,7 @@ func (fs *eosfs) RestoreRevision(ctx context.Context, ref *provider.Reference, r return fs.c.RollbackToVersion(ctx, uid, gid, fn, revisionKey) } -func (fs *eosfs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference) error { +func (fs *eosfs) PurgeRecycleItem(ctx context.Context, key, itemPath string) error { return errtypes.NotSupported("eosfs: operation not supported") } @@ -1380,7 +1380,7 @@ func (fs *eosfs) EmptyRecycle(ctx context.Context) error { return fs.c.PurgeDeletedEntries(ctx, uid, gid) } -func (fs *eosfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*provider.RecycleItem, error) { +func (fs *eosfs) ListRecycle(ctx context.Context, key, itemPath string) ([]*provider.RecycleItem, error) { u, err := getUser(ctx) if err != nil { return nil, errors.Wrap(err, "eosfs: no user in ctx") @@ -1411,7 +1411,7 @@ func (fs *eosfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*p return recycleEntries, nil } -func (fs *eosfs) RestoreRecycleItem(ctx context.Context, trashRef *provider.Reference, restoreRef *provider.Reference) error { +func (fs *eosfs) RestoreRecycleItem(ctx context.Context, key, itemPath string, restoreRef *provider.Reference) error { u, err := getUser(ctx) if err != nil { return errors.Wrap(err, "eosfs: no user in ctx") @@ -1422,7 +1422,7 @@ func (fs *eosfs) RestoreRecycleItem(ctx context.Context, trashRef *provider.Refe return err } - return fs.c.RestoreDeletedEntry(ctx, uid, gid, trashRef.ResourceId.OpaqueId) + return fs.c.RestoreDeletedEntry(ctx, uid, gid, key) } func (fs *eosfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { diff --git a/pkg/storage/utils/localfs/localfs.go b/pkg/storage/utils/localfs/localfs.go index fcec170d42..4b07a91f20 100644 --- a/pkg/storage/utils/localfs/localfs.go +++ b/pkg/storage/utils/localfs/localfs.go @@ -1131,8 +1131,8 @@ func (fs *localfs) RestoreRevision(ctx context.Context, ref *provider.Reference, return fs.propagate(ctx, np) } -func (fs *localfs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference) error { - rp := fs.wrapRecycleBin(ctx, ref.ResourceId.OpaqueId) +func (fs *localfs) PurgeRecycleItem(ctx context.Context, key, itemPath string) error { + rp := fs.wrapRecycleBin(ctx, key) if err := os.Remove(rp); err != nil { return errors.Wrap(err, "localfs: error deleting recycle item") @@ -1181,7 +1181,7 @@ func (fs *localfs) convertToRecycleItem(ctx context.Context, rp string, md os.Fi } } -func (fs *localfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([]*provider.RecycleItem, error) { +func (fs *localfs) ListRecycle(ctx context.Context, key, path string) ([]*provider.RecycleItem, error) { rp := fs.wrapRecycleBin(ctx, "/") @@ -1199,14 +1199,14 @@ func (fs *localfs) ListRecycle(ctx context.Context, ref *provider.Reference) ([] return items, nil } -func (fs *localfs) RestoreRecycleItem(ctx context.Context, trashRef *provider.Reference, restoreRef *provider.Reference) error { +func (fs *localfs) RestoreRecycleItem(ctx context.Context, key, itemPath string, restoreRef *provider.Reference) error { - suffix := path.Ext(trashRef.ResourceId.OpaqueId) + suffix := path.Ext(key) if len(suffix) == 0 || !strings.HasPrefix(suffix, ".d") { return errors.New("localfs: invalid trash item suffix") } - filePath, err := fs.getRecycledEntry(ctx, trashRef.ResourceId.OpaqueId) + filePath, err := fs.getRecycledEntry(ctx, key) if err != nil { return errors.Wrap(err, "localfs: invalid key") } @@ -1225,10 +1225,10 @@ func (fs *localfs) RestoreRecycleItem(ctx context.Context, trashRef *provider.Re return errors.New("localfs: can't restore - file already exists at original path") } - rp := fs.wrapRecycleBin(ctx, trashRef.ResourceId.OpaqueId) + rp := fs.wrapRecycleBin(ctx, key) if _, err = os.Stat(rp); err != nil { if os.IsNotExist(err) { - return errtypes.NotFound(trashRef.ResourceId.OpaqueId) + return errtypes.NotFound(key) } return errors.Wrap(err, "localfs: error stating "+rp) } @@ -1237,7 +1237,7 @@ func (fs *localfs) RestoreRecycleItem(ctx context.Context, trashRef *provider.Re return errors.Wrap(err, "ocfs: could not restore item") } - err = fs.removeFromRecycledDB(ctx, trashRef.ResourceId.OpaqueId) + err = fs.removeFromRecycledDB(ctx, key) if err != nil { return errors.Wrap(err, "localfs: error adding entry to DB") }