Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix recycle to different location #1541

Merged
merged 6 commits into from
Apr 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions changelog/unreleased/fix-restore-to-different-location.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Bugfix: Allow for restoring recycle items to different locations

The CS3 APIs specify a way to restore a recycle item to a different location
than the original by setting the `restore_path` field in the
`RestoreRecycleItemRequest`. This field had not been considered until now.

https://github.com/cs3org/reva/pull/1541
https://cs3org.github.io/cs3apis/
Original file line number Diff line number Diff line change
Expand Up @@ -814,7 +814,7 @@ func (s *service) ListRecycle(ctx context.Context, req *provider.ListRecycleRequ

func (s *service) RestoreRecycleItem(ctx context.Context, req *provider.RestoreRecycleItemRequest) (*provider.RestoreRecycleItemResponse, error) {
// TODO(labkode): CRITICAL: fill recycle info with storage provider.
if err := s.storage.RestoreRecycleItem(ctx, req.Key); err != nil {
if err := s.storage.RestoreRecycleItem(ctx, req.Key, req.RestorePath); err != nil {
var st *rpc.Status
switch err.(type) {
case errtypes.IsNotFound:
Expand Down
17 changes: 9 additions & 8 deletions pkg/storage/fs/owncloud/owncloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -2140,7 +2140,7 @@ func (fs *ocfs) ListRecycle(ctx context.Context) ([]*provider.RecycleItem, error
return items, nil
}

func (fs *ocfs) RestoreRecycleItem(ctx context.Context, key string) error {
func (fs *ocfs) RestoreRecycleItem(ctx context.Context, key, restorePath string) error {
// TODO check permission? on what? user must be the owner?
log := appctx.GetLogger(ctx)
rp, err := fs.getRecyclePath(ctx)
Expand All @@ -2155,16 +2155,17 @@ func (fs *ocfs) RestoreRecycleItem(ctx context.Context, key string) error {
return nil
}

origin := "/"
if v, err := xattr.Get(src, trashOriginPrefix); err != nil {
log.Error().Err(err).Str("key", key).Str("path", src).Msg("could not read origin")
} else {
origin = filepath.Clean(string(v))
if restorePath == "" {
v, err := xattr.Get(src, trashOriginPrefix)
if err != nil {
log.Error().Err(err).Str("key", key).Str("path", src).Msg("could not read origin")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we return here? Because if err != nil, restorePath will be the base of src, restoring to which might create conflicts

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also restored to / before, I didn't want to change behaviour like that with this PR.
Having said that I think the origin xattr will only be missing in very bad exceptions anyway. In those cases it might be desirable to have the files restored to / (if possible), no?

}
restorePath = filepath.Join("/", filepath.Clean(string(v)), strings.TrimSuffix(filepath.Base(src), suffix))
}
tgt := fs.toInternalPath(ctx, filepath.Join("/", origin, strings.TrimSuffix(filepath.Base(src), suffix)))
tgt := fs.toInternalPath(ctx, restorePath)
// move back to original location
if err := os.Rename(src, tgt); err != nil {
log.Error().Err(err).Str("key", key).Str("origin", origin).Str("src", src).Str("tgt", tgt).Msg("could not restore item")
log.Error().Err(err).Str("key", key).Str("restorePath", restorePath).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
Expand Down
2 changes: 1 addition & 1 deletion pkg/storage/fs/s3/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,6 @@ func (fs *s3FS) ListRecycle(ctx context.Context) ([]*provider.RecycleItem, error
return nil, errtypes.NotSupported("list recycle")
}

func (fs *s3FS) RestoreRecycleItem(ctx context.Context, restoreKey string) error {
func (fs *s3FS) RestoreRecycleItem(ctx context.Context, restoreKey, restorePath string) error {
return errtypes.NotSupported("restore recycle")
}
2 changes: 1 addition & 1 deletion pkg/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ type FS interface {
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) ([]*provider.RecycleItem, error)
RestoreRecycleItem(ctx context.Context, key string) error
RestoreRecycleItem(ctx context.Context, key, restorePath string) error
PurgeRecycleItem(ctx context.Context, key string) error
EmptyRecycle(ctx context.Context) error
GetPathByID(ctx context.Context, id *provider.ResourceId) (string, error)
Expand Down
2 changes: 1 addition & 1 deletion pkg/storage/utils/decomposedfs/decomposedfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ type Tree interface {
// CreateReference(ctx context.Context, node *node.Node, targetURI *url.URL) error
Move(ctx context.Context, oldNode *node.Node, newNode *node.Node) (err error)
Delete(ctx context.Context, node *node.Node) (err error)
RestoreRecycleItemFunc(ctx context.Context, key string) (*node.Node, func() error, error)
RestoreRecycleItemFunc(ctx context.Context, key, restorePath string) (*node.Node, func() error, error)
PurgeRecycleItemFunc(ctx context.Context, key string) (*node.Node, func() error, error)

WriteBlob(key string, reader io.Reader) error
Expand Down
4 changes: 2 additions & 2 deletions pkg/storage/utils/decomposedfs/recycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,8 @@ func (fs *Decomposedfs) ListRecycle(ctx context.Context) (items []*provider.Recy
}

// RestoreRecycleItem restores the specified item
func (fs *Decomposedfs) RestoreRecycleItem(ctx context.Context, key string) error {
rn, restoreFunc, err := fs.tp.RestoreRecycleItemFunc(ctx, key)
func (fs *Decomposedfs) RestoreRecycleItem(ctx context.Context, key, restorePath string) error {
rn, restoreFunc, err := fs.tp.RestoreRecycleItemFunc(ctx, key, restorePath)
if err != nil {
return err
}
Expand Down
8 changes: 6 additions & 2 deletions pkg/storage/utils/decomposedfs/tree/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,16 +345,20 @@ func (t *Tree) Delete(ctx context.Context, n *node.Node) (err error) {
}

// RestoreRecycleItemFunc returns a node and a function to restore it from the trash
func (t *Tree) RestoreRecycleItemFunc(ctx context.Context, key string) (*node.Node, func() error, error) {
func (t *Tree) RestoreRecycleItemFunc(ctx context.Context, key, restorePath string) (*node.Node, func() error, error) {
rn, trashItem, deletedNodePath, origin, err := t.readRecycleItem(ctx, key)
if err != nil {
return nil, nil, err
}

if restorePath == "" {
restorePath = origin
}

fn := func() error {
// link to origin
var n *node.Node
n, err = t.lookup.NodeFromPath(ctx, origin)
n, err = t.lookup.NodeFromPath(ctx, restorePath)
if err != nil {
return err
}
Expand Down
36 changes: 30 additions & 6 deletions pkg/storage/utils/decomposedfs/tree/tree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,13 @@ var _ = Describe("Tree", func() {

Context("with an existingfile", func() {
var (
n *node.Node
n *node.Node
originalPath = "dir1/file1"
)

JustBeforeEach(func() {
var err error
n, err = env.Lookup.NodeFromPath(env.Ctx, "/dir1/file1")
n, err = env.Lookup.NodeFromPath(env.Ctx, originalPath)
Expect(err).ToNot(HaveOccurred())
})

Expand Down Expand Up @@ -135,18 +136,41 @@ var _ = Describe("Tree", func() {
Expect(err).ToNot(HaveOccurred())
_, err = os.Stat(n.InternalPath())
Expect(err).To(HaveOccurred())
})

_, restoreFunc, err := t.RestoreRecycleItemFunc(env.Ctx, env.Owner.Id.OpaqueId+":"+n.ID)
It("restores the file to its original location if the targetPath is empty", func() {
_, restoreFunc, err := t.RestoreRecycleItemFunc(env.Ctx, env.Owner.Id.OpaqueId+":"+n.ID, "")
Expect(err).ToNot(HaveOccurred())

Expect(restoreFunc()).To(Succeed())

originalNode, err := env.Lookup.NodeFromPath(env.Ctx, originalPath)
Expect(err).ToNot(HaveOccurred())
Expect(originalNode.Exists).To(BeTrue())
})

It("restores the file to its original location", func() {
_, err := os.Stat(n.InternalPath())
It("restores files to different locations", func() {
_, restoreFunc, err := t.RestoreRecycleItemFunc(env.Ctx, env.Owner.Id.OpaqueId+":"+n.ID, "dir1/newLocation")
Expect(err).ToNot(HaveOccurred())

Expect(restoreFunc()).To(Succeed())

newNode, err := env.Lookup.NodeFromPath(env.Ctx, "dir1/newLocation")
Expect(err).ToNot(HaveOccurred())
Expect(newNode.Exists).To(BeTrue())

originalNode, err := env.Lookup.NodeFromPath(env.Ctx, originalPath)
Expect(err).ToNot(HaveOccurred())
Expect(originalNode.Exists).To(BeFalse())
})

It("removes the file from the trash", func() {
_, err := os.Stat(trashPath)
_, restoreFunc, err := t.RestoreRecycleItemFunc(env.Ctx, env.Owner.Id.OpaqueId+":"+n.ID, "")
Expect(err).ToNot(HaveOccurred())

Expect(restoreFunc()).To(Succeed())

_, err = os.Stat(trashPath)
Expect(err).To(HaveOccurred())
})
})
Expand Down
2 changes: 1 addition & 1 deletion pkg/storage/utils/eosfs/eosfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -1284,7 +1284,7 @@ func (fs *eosfs) ListRecycle(ctx context.Context) ([]*provider.RecycleItem, erro
return recycleEntries, nil
}

func (fs *eosfs) RestoreRecycleItem(ctx context.Context, key string) error {
func (fs *eosfs) RestoreRecycleItem(ctx context.Context, key, restorePath string) error {
u, err := getUser(ctx)
if err != nil {
return errors.Wrap(err, "eos: no user in ctx")
Expand Down
21 changes: 12 additions & 9 deletions pkg/storage/utils/localfs/localfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -1179,7 +1179,7 @@ func (fs *localfs) ListRecycle(ctx context.Context) ([]*provider.RecycleItem, er
return items, nil
}

func (fs *localfs) RestoreRecycleItem(ctx context.Context, restoreKey string) error {
func (fs *localfs) RestoreRecycleItem(ctx context.Context, restoreKey, restorePath string) error {

suffix := path.Ext(restoreKey)
if len(suffix) == 0 || !strings.HasPrefix(suffix, ".d") {
Expand All @@ -1191,14 +1191,17 @@ func (fs *localfs) RestoreRecycleItem(ctx context.Context, restoreKey string) er
return errors.Wrap(err, "localfs: invalid key")
}

var originalPath string
if fs.isShareFolder(ctx, filePath) {
originalPath = fs.wrapReferences(ctx, filePath)
} else {
originalPath = fs.wrap(ctx, filePath)
var localRestorePath string
switch {
case restorePath != "":
localRestorePath = fs.wrap(ctx, restorePath)
case fs.isShareFolder(ctx, filePath):
localRestorePath = fs.wrapReferences(ctx, filePath)
default:
localRestorePath = fs.wrap(ctx, filePath)
}

if _, err = os.Stat(originalPath); err == nil {
if _, err = os.Stat(localRestorePath); err == nil {
return errors.New("localfs: can't restore - file already exists at original path")
}

Expand All @@ -1210,7 +1213,7 @@ func (fs *localfs) RestoreRecycleItem(ctx context.Context, restoreKey string) er
return errors.Wrap(err, "localfs: error stating "+rp)
}

if err := os.Rename(rp, originalPath); err != nil {
if err := os.Rename(rp, localRestorePath); err != nil {
return errors.Wrap(err, "ocfs: could not restore item")
}

Expand All @@ -1219,7 +1222,7 @@ func (fs *localfs) RestoreRecycleItem(ctx context.Context, restoreKey string) er
return errors.Wrap(err, "localfs: error adding entry to DB")
}

return fs.propagate(ctx, originalPath)
return fs.propagate(ctx, localRestorePath)
}

func (fs *localfs) propagate(ctx context.Context, leafPath string) error {
Expand Down
7 changes: 0 additions & 7 deletions tests/acceptance/expected-failures-on-OCIS-storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,6 @@
### File
Basic file management like up and download, move, copy, properties, quota, trash, versions and chunking.

#### [Implement Trashbin Feature for ocis storage](https://github.com/owncloud/product/issues/209)

- [apiWebdavEtagPropagation2/restoreFromTrash.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/restoreFromTrash.feature#L48)
- [apiWebdavEtagPropagation2/restoreFromTrash.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/restoreFromTrash.feature#L49)
- [apiWebdavEtagPropagation2/restoreFromTrash.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/restoreFromTrash.feature#L90)
- [apiWebdavEtagPropagation2/restoreFromTrash.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/restoreFromTrash.feature#L91)

#### [QA trashcan cannot delete a deep tree](https://github.com/owncloud/ocis/issues/1077)
- [apiTrashbin/trashbinDelete.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L107)
- [apiTrashbin/trashbinDelete.feature:123](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L123)
Expand Down
6 changes: 0 additions & 6 deletions tests/acceptance/expected-failures-on-OWNCLOUD-storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,6 @@
### File
Basic file management like up and download, move, copy, properties, quota, trash, versions and chunking.

#### [Implement Trashbin Feature for ocis storage](https://github.com/owncloud/product/issues/209)
- [apiWebdavEtagPropagation2/restoreFromTrash.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/restoreFromTrash.feature#L48)
- [apiWebdavEtagPropagation2/restoreFromTrash.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/restoreFromTrash.feature#L49)
- [apiWebdavEtagPropagation2/restoreFromTrash.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/restoreFromTrash.feature#L90)
- [apiWebdavEtagPropagation2/restoreFromTrash.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/restoreFromTrash.feature#L91)

#### [QA trashcan cannot delete a deep tree](https://github.com/owncloud/ocis/issues/1077)
- [apiTrashbin/trashbinDelete.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L107)
- [apiTrashbin/trashbinDelete.feature:123](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L123)
Expand Down
7 changes: 0 additions & 7 deletions tests/acceptance/expected-failures-on-S3NG-storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,6 @@
### File
Basic file management like up and download, move, copy, properties, quota, trash, versions and chunking.

#### [Implement Trashbin Feature for ocis storage](https://github.com/owncloud/product/issues/209)

- [apiWebdavEtagPropagation2/restoreFromTrash.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/restoreFromTrash.feature#L48)
- [apiWebdavEtagPropagation2/restoreFromTrash.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/restoreFromTrash.feature#L49)
- [apiWebdavEtagPropagation2/restoreFromTrash.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/restoreFromTrash.feature#L90)
- [apiWebdavEtagPropagation2/restoreFromTrash.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/restoreFromTrash.feature#L91)

#### [QA trashcan cannot delete a deep tree](https://github.com/owncloud/ocis/issues/1077)
- [apiTrashbin/trashbinDelete.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L107)
- [apiTrashbin/trashbinDelete.feature:123](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L123)
Expand Down
38 changes: 38 additions & 0 deletions tests/integration/grpc/storageprovider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,44 @@ var _ = Describe("storage providers", func() {
Expect(statRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK))
})

It("restores resources to a different location", func() {
restoreRef := &storagep.Reference{
Spec: &storagep.Reference_Path{Path: "/subdirRestored"},
}
By("deleting an item")
res, err := serviceClient.Delete(ctx, &storagep.DeleteRequest{Ref: subdirRef})
Expect(err).ToNot(HaveOccurred())
Expect(res.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK))

By("listing the recycle items")
listRes, err := serviceClient.ListRecycle(ctx, &storagep.ListRecycleRequest{})
Expect(err).ToNot(HaveOccurred())
Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK))

Expect(len(listRes.RecycleItems)).To(Equal(1))
item := listRes.RecycleItems[0]
Expect(item.Path).To(Equal(subdirPath))

By("restoring the item to a different location")
statRes, err := serviceClient.Stat(ctx, &storagep.StatRequest{Ref: restoreRef})
Expect(err).ToNot(HaveOccurred())
Expect(statRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_NOT_FOUND))

restoreRes, err := serviceClient.RestoreRecycleItem(ctx,
&storagep.RestoreRecycleItemRequest{
Ref: subdirRef,
Key: item.Key,
RestorePath: "/subdirRestored",
},
)
Expect(err).ToNot(HaveOccurred())
Expect(restoreRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK))

statRes, err = serviceClient.Stat(ctx, &storagep.StatRequest{Ref: restoreRef})
Expect(err).ToNot(HaveOccurred())
Expect(statRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK))
})

It("purges recycle items resources", func() {
By("deleting an item")
res, err := serviceClient.Delete(ctx, &storagep.DeleteRequest{Ref: subdirRef})
Expand Down