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/go.mod b/go.mod index b02feb08c9..09ededa618 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/cheggaaa/pb v1.0.29 github.com/coreos/go-oidc v2.2.1+incompatible github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e - github.com/cs3org/go-cs3apis v0.0.0-20210614143420-5ee2eb1e7887 + github.com/cs3org/go-cs3apis v0.0.0-20210702091910-85a56bfd027f github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/eventials/go-tus v0.0.0-20200718001131-45c7ec8f5d59 github.com/gdexlab/go-render v1.0.1 diff --git a/go.sum b/go.sum index f9984dd5f0..f63843c651 100644 --- a/go.sum +++ b/go.sum @@ -104,8 +104,8 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e h1:tqSPWQeueWTKnJVMJffz4pz0o1WuQxJ28+5x5JgaHD8= github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e/go.mod h1:XJEZ3/EQuI3BXTp/6DUzFr850vlxq11I6satRtz0YQ4= -github.com/cs3org/go-cs3apis v0.0.0-20210614143420-5ee2eb1e7887 h1:X5Se3M/kbh9w6LZQvyLS7djAGKcWGzmaY6IOa7Talpk= -github.com/cs3org/go-cs3apis v0.0.0-20210614143420-5ee2eb1e7887/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= +github.com/cs3org/go-cs3apis v0.0.0-20210702091910-85a56bfd027f h1:l09QSEPO8DI3V2hBnc6KhTsrNg/DTyBYjCTwSb/HR6Q= +github.com/cs3org/go-cs3apis v0.0.0-20210702091910-85a56bfd027f/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/internal/grpc/services/gateway/storageprovider.go b/internal/grpc/services/gateway/storageprovider.go index 80fa004610..33e5d7ff27 100644 --- a/internal/grpc/services/gateway/storageprovider.go +++ b/internal/grpc/services/gateway/storageprovider.go @@ -1928,6 +1928,7 @@ func (s *svc) ListRecycle(ctx context.Context, req *gateway.ListRecycleRequest) Opaque: req.Opaque, FromTs: req.FromTs, ToTs: req.ToTs, + Ref: req.Ref, }) if err != nil { return nil, errors.Wrap(err, "gateway: error calling ListRecycleRequest") @@ -1953,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 9c8529971e..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) + 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) { @@ -829,7 +835,12 @@ func (s *service) ListRecycleStream(req *provider.ListRecycleStreamRequest, ss p } func (s *service) ListRecycle(ctx context.Context, req *provider.ListRecycleRequest) (*provider.ListRecycleResponse, error) { - items, err := s.storage.ListRecycle(ctx) + ref, err := s.unwrap(ctx, req.Ref) + if err != nil { + return nil, err + } + 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 @@ -855,7 +866,11 @@ 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, req.RestoreRef); err != nil { + ref, err := s.unwrap(ctx, req.Ref) + if err != nil { + return nil, err + } + if err := s.storage.RestoreRecycleItem(ctx, req.Key, ref.Path, req.RestoreRef); err != nil { var st *rpc.Status switch err.(type) { case errtypes.IsNotFound: @@ -879,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().GetResourceId().OpaqueId); 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 d19c086631..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" @@ -107,8 +106,8 @@ func (h *TrashbinHandler) Handler(s *svc) http.Handler { // return //} - if key == "" && r.Method == "PROPFIND" { - h.listTrashbin(w, r, s, u) + if r.Method == "PROPFIND" { + h.listTrashbin(w, r, s, u, key, r.URL.Path) return } if key != "" && r.Method == "MOVE" { @@ -129,12 +128,12 @@ func (h *TrashbinHandler) Handler(s *svc) http.Handler { log.Debug().Str("key", key).Str("dst", dst).Msg("restore") - h.restore(w, r, s, u, dst, key) + h.restore(w, r, s, u, dst, key, r.URL.Path) return } if r.Method == "DELETE" { - h.delete(w, r, s, u, key) + h.delete(w, r, s, u, key, r.URL.Path) return } @@ -142,13 +141,25 @@ func (h *TrashbinHandler) Handler(s *svc) http.Handler { }) } -func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s *svc, u *userpb.User) { +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() + depth := r.Header.Get("Depth") + if depth == "" { + depth = "1" + } + sublog := appctx.GetLogger(ctx).With().Logger() + // see https://tools.ietf.org/html/rfc4918#section-9.1 + if depth != "0" && depth != "1" && depth != "infinity" { + sublog.Debug().Str("depth", depth).Msgf("invalid Depth header value") + w.WriteHeader(http.StatusBadRequest) + return + } + pf, status, err := readPropfind(r.Body) if err != nil { sublog.Debug().Err(err).Msg("error reading propfind request") @@ -178,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: getHomeRes.Path}}) + 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") @@ -192,7 +202,47 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s return } - propRes, err := h.formatTrashPropfind(ctx, s, u, &pf, getRecycleRes.RecycleItems) + items := getRecycleRes.RecycleItems + + if depth == "infinity" { + var stack []string + // check sub-containers in reverse order and add them to the stack + // the reversed order here will produce a more logical sorting of results + for i := len(items) - 1; i >= 0; i-- { + // for i := range res.Infos { + if items[i].Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { + stack = append(stack, items[i].Key) + } + } + + for len(stack) > 0 { + key := stack[len(stack)-1] + getRecycleRes, err := gc.ListRecycle(ctx, &gateway.ListRecycleRequest{Ref: &provider.Reference{Path: path.Join(getHomeRes.Path, key)}}) + if err != nil { + sublog.Error().Err(err).Msg("error calling ListRecycle") + w.WriteHeader(http.StatusInternalServerError) + return + } + + if getRecycleRes.Status.Code != rpc.Code_CODE_OK { + HandleErrorStatus(&sublog, w, getRecycleRes.Status) + return + } + items = append(items, getRecycleRes.RecycleItems...) + + stack = stack[:len(stack)-1] + // check sub-containers in reverse order and add them to the stack + // the reversed order here will produce a more logical sorting of results + for i := len(getRecycleRes.RecycleItems) - 1; i >= 0; i-- { + // for i := range res.Infos { + if getRecycleRes.RecycleItems[i].Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { + stack = append(stack, getRecycleRes.RecycleItems[i].Key) + } + } + } + } + + propRes, err := h.formatTrashPropfind(ctx, s, u, &pf, items) if err != nil { sublog.Error().Err(err).Msg("error formatting propfind") w.WriteHeader(http.StatusInternalServerError) @@ -279,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)) @@ -316,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, "/"))) @@ -363,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 string, key 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() @@ -401,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{ @@ -464,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: getHomeRes.Path, + Path: path.Join(getHomeRes.Path, itemPath), }, Key: key, RestoreRef: &provider.Reference{Path: dst}, @@ -503,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 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() @@ -547,6 +597,7 @@ func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, StorageId: sRes.Info.Id.StorageId, OpaqueId: key, }, + Path: utils.MakeRelativePath(itemPath), }, } diff --git a/pkg/storage/fs/owncloud/owncloud.go b/pkg/storage/fs/owncloud/owncloud.go index 27c202aa5b..43da2744e2 100644 --- a/pkg/storage/fs/owncloud/owncloud.go +++ b/pkg/storage/fs/owncloud/owncloud.go @@ -2060,7 +2060,7 @@ func (fs *ocfs) RestoreRevision(ctx context.Context, ref *provider.Reference, re return fs.propagate(ctx, ip) } -func (fs *ocfs) PurgeRecycleItem(ctx context.Context, key string) 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") @@ -2153,7 +2153,7 @@ func (fs *ocfs) convertToRecycleItem(ctx context.Context, rp string, md os.FileI } } -func (fs *ocfs) ListRecycle(ctx context.Context) ([]*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) ([]*provider.RecycleItem, error } // list files folder - mds, err := ioutil.ReadDir(rp) + 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,7 +2180,7 @@ func (fs *ocfs) ListRecycle(ctx context.Context) ([]*provider.RecycleItem, error return items, nil } -func (fs *ocfs) RestoreRecycleItem(ctx context.Context, key string, 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) diff --git a/pkg/storage/fs/owncloudsql/owncloudsql.go b/pkg/storage/fs/owncloudsql/owncloudsql.go index 589338da11..6a004f46e2 100644 --- a/pkg/storage/fs/owncloudsql/owncloudsql.go +++ b/pkg/storage/fs/owncloudsql/owncloudsql.go @@ -1864,7 +1864,7 @@ func (fs *ocfs) RestoreRevision(ctx context.Context, ref *provider.Reference, re return fs.propagate(ctx, ip) } -func (fs *ocfs) PurgeRecycleItem(ctx context.Context, key string) 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") @@ -1972,7 +1972,7 @@ func (fs *ocfs) convertToRecycleItem(ctx context.Context, md os.FileInfo) *provi } } -func (fs *ocfs) ListRecycle(ctx context.Context) ([]*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,7 +1999,7 @@ func (fs *ocfs) ListRecycle(ctx context.Context) ([]*provider.RecycleItem, error return items, nil } -func (fs *ocfs) RestoreRecycleItem(ctx context.Context, key string, 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) diff --git a/pkg/storage/fs/s3/s3.go b/pkg/storage/fs/s3/s3.go index 38c3b6b4f1..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, key string) 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) ([]*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, key string, 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 627a649a00..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) ([]*provider.RecycleItem, error) - RestoreRecycleItem(ctx context.Context, key string, restoreRef *provider.Reference) error - PurgeRecycleItem(ctx context.Context, key string) 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/decomposedfs.go b/pkg/storage/utils/decomposedfs/decomposedfs.go index eb31b6eb97..ccce2b4fce 100644 --- a/pkg/storage/utils/decomposedfs/decomposedfs.go +++ b/pkg/storage/utils/decomposedfs/decomposedfs.go @@ -68,8 +68,8 @@ 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, restorePath string) (*node.Node, func() error, error) // FIXME REFERENCE use ref instead of path - PurgeRecycleItemFunc(ctx context.Context, key string) (*node.Node, func() error, error) + RestoreRecycleItemFunc(ctx context.Context, key, trashPath, restorePath string) (*node.Node, func() error, error) // FIXME REFERENCE use ref instead of path + PurgeRecycleItemFunc(ctx context.Context, key, purgePath string) (*node.Node, func() error, error) WriteBlob(key string, reader io.Reader) error ReadBlob(key string) (io.ReadCloser, error) diff --git a/pkg/storage/utils/decomposedfs/recycle.go b/pkg/storage/utils/decomposedfs/recycle.go index 28f3bfedcc..d3dcebe24a 100644 --- a/pkg/storage/utils/decomposedfs/recycle.go +++ b/pkg/storage/utils/decomposedfs/recycle.go @@ -21,6 +21,7 @@ package decomposedfs import ( "context" "os" + "path" "path/filepath" "strings" "time" @@ -45,12 +46,10 @@ 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) (items []*provider.RecycleItem, err error) { +func (fs *Decomposedfs) ListRecycle(ctx context.Context, key, path string) ([]*provider.RecycleItem, error) { log := appctx.GetLogger(ctx) - trashRoot := fs.getRecycleRoot(ctx) - - items = make([]*provider.RecycleItem, 0) + items := make([]*provider.RecycleItem, 0) // TODO how do we check if the storage allows listing the recycle for the current user? check owner of the root of the storage? // use permissions ReadUserPermissions? @@ -66,6 +65,120 @@ func (fs *Decomposedfs) ListRecycle(ctx context.Context) (items []*provider.Recy } } + if key == "" && path == "/" { + return fs.listTrashRoot(ctx) + } + + trashRoot := fs.getRecycleRoot(ctx) + f, err := os.Open(filepath.Join(trashRoot, key, path)) + if err != nil { + if os.IsNotExist(err) { + return items, nil + } + return nil, errors.Wrapf(err, "tree: error listing %s", trashRoot) + } + defer f.Close() + + 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 + } + + if md, err := f.Stat(); err != nil { + 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(path), filepath.Join(trashRoot, key, path)) + if err != nil { + return items, err + } + items = append(items, item) + return items, err + } + + names, err := f.Readdirnames(0) + if err != nil { + return nil, err + } + for i := range names { + if item, err := fs.createTrashItem(ctx, parentNode, path, filepath.Join(trashRoot, key, path, names[i])); err == nil { + items = append(items, item) + } + } + return items, nil +} + +func (fs *Decomposedfs) createTrashItem(ctx context.Context, parentNode, intermediatePath, itemPath string) (*provider.RecycleItem, error) { + log := appctx.GetLogger(ctx) + trashRoot := fs.getRecycleRoot(ctx) + trashnode, err := os.Readlink(itemPath) + if err != nil { + log.Error().Err(err).Str("trashRoot", trashRoot).Msg("error reading trash link, skipping") + return nil, err + } + parts := strings.SplitN(filepath.Base(parentNode), ".T.", 2) + if len(parts) != 2 { + log.Error().Str("trashRoot", trashRoot).Str("trashnode", trashnode).Interface("parts", parts).Msg("malformed trash link, skipping") + return nil, errors.New("malformed trash link") + } + + nodePath := fs.lu.InternalPath(filepath.Base(trashnode)) + md, err := os.Stat(nodePath) + if err != nil { + log.Error().Err(err).Str("trashRoot", trashRoot).Str("trashnode", trashnode).Msg("could not stat trash item, skipping") + return nil, err + } + + item := &provider.RecycleItem{ + Type: getResourceType(md.IsDir()), + Size: uint64(md.Size()), + Key: path.Join(parts[0], intermediatePath, filepath.Base(itemPath)), + } + if deletionTime, err := time.Parse(time.RFC3339Nano, parts[1]); err == nil { + item.DeletionTime = &types.Timestamp{ + Seconds: uint64(deletionTime.Unix()), + // TODO nanos + } + } else { + log.Error().Err(err).Str("trashRoot", trashRoot).Str("link", trashnode).Interface("parts", parts).Msg("could parse time format, ignoring") + } + + // lookup origin path in extended attributes + parentPath := fs.lu.InternalPath(filepath.Base(parentNode)) + if attrBytes, err := xattr.Get(parentPath, xattrs.TrashOriginAttr); err == nil { + item.Ref = &provider.Reference{Path: filepath.Join(string(attrBytes), intermediatePath, filepath.Base(itemPath))} + } else { + log.Error().Err(err).Str("trashRoot", trashRoot).Str("link", trashnode).Msg("could not read origin path, skipping") + return nil, err + } + // TODO filter results by permission ... on the original parent? or the trashed node? + // if it were on the original parent it would be possible to see files that were trashed before the current user got access + // so -> check the trash node itself + // hmm listing trash currently lists the current users trash or the 'root' trash. from ocs only the home storage is queried for trash items. + // for now we can only really check if the current user is the owner + if attrBytes, err := xattr.Get(nodePath, xattrs.OwnerIDAttr); err == nil { + if fs.o.EnableHome { + u := user.ContextMustGetUser(ctx) + if u.Id.OpaqueId != string(attrBytes) { + log.Warn().Str("trashRoot", trashRoot).Str("link", trashnode).Msg("trash item not owned by current user, skipping") + // continue + return nil, errors.New("trash item not owned by current user") + } + } + } else { + log.Error().Err(err).Str("trashRoot", trashRoot).Str("link", trashnode).Msg("could not read owner, skipping") + return nil, err + } + + return item, nil +} + +func (fs *Decomposedfs) listTrashRoot(ctx context.Context) ([]*provider.RecycleItem, error) { + log := appctx.GetLogger(ctx) + items := make([]*provider.RecycleItem, 0) + + trashRoot := fs.getRecycleRoot(ctx) f, err := os.Open(trashRoot) if err != nil { if os.IsNotExist(err) { @@ -79,12 +192,11 @@ func (fs *Decomposedfs) ListRecycle(ctx context.Context) (items []*provider.Recy if err != nil { return nil, err } + for i := range names { - var trashnode string - trashnode, err = os.Readlink(filepath.Join(trashRoot, names[i])) + trashnode, err := os.Readlink(filepath.Join(trashRoot, names[i])) if err != nil { log.Error().Err(err).Str("trashRoot", trashRoot).Str("name", names[i]).Msg("error reading trash link, skipping") - err = nil continue } parts := strings.SplitN(filepath.Base(trashnode), ".T.", 2) @@ -96,7 +208,7 @@ func (fs *Decomposedfs) ListRecycle(ctx context.Context) (items []*provider.Recy nodePath := fs.lu.InternalPath(filepath.Base(trashnode)) md, err := os.Stat(nodePath) if err != nil { - log.Error().Err(err).Str("trashRoot", trashRoot).Str("name", names[i]).Str("trashnode", trashnode).Interface("parts", parts).Msg("could not stat trash item, skipping") + log.Error().Err(err).Str("trashRoot", trashRoot).Str("name", names[i]).Str("trashnode", trashnode). /*.Interface("parts", parts)*/ Msg("could not stat trash item, skipping") continue } @@ -142,15 +254,15 @@ func (fs *Decomposedfs) ListRecycle(ctx context.Context) (items []*provider.Recy items = append(items, item) } - return + return items, nil } // RestoreRecycleItem restores the specified item -func (fs *Decomposedfs) RestoreRecycleItem(ctx context.Context, key string, 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, key, restoreRef.Path) + rn, restoreFunc, err := fs.tp.RestoreRecycleItemFunc(ctx, key, path, restoreRef.Path) if err != nil { return err } @@ -171,8 +283,8 @@ func (fs *Decomposedfs) RestoreRecycleItem(ctx context.Context, key string, rest } // PurgeRecycleItem purges the specified item -func (fs *Decomposedfs) PurgeRecycleItem(ctx context.Context, key string) error { - rn, purgeFunc, err := fs.tp.PurgeRecycleItemFunc(ctx, key) +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 } diff --git a/pkg/storage/utils/decomposedfs/tree/tree.go b/pkg/storage/utils/decomposedfs/tree/tree.go index af74301078..1724c6175d 100644 --- a/pkg/storage/utils/decomposedfs/tree/tree.go +++ b/pkg/storage/utils/decomposedfs/tree/tree.go @@ -410,8 +410,8 @@ 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, restorePath string) (*node.Node, func() error, error) { - rn, trashItem, deletedNodePath, origin, err := t.readRecycleItem(ctx, key) +func (t *Tree) RestoreRecycleItemFunc(ctx context.Context, key, trashPath, restorePath string) (*node.Node, func() error, error) { + rn, trashItem, deletedNodePath, origin, err := t.readRecycleItem(ctx, key, trashPath) if err != nil { return nil, nil, err } @@ -451,6 +451,12 @@ func (t *Tree) RestoreRecycleItemFunc(ctx context.Context, key, restorePath stri return errors.Wrap(err, "Decomposedfs: could not set name attribute") } + if trashPath != "" { + if err := xattr.Set(nodePath, xattrs.ParentidAttr, []byte(n.ParentID)); err != nil { + return errors.Wrap(err, "Decomposedfs: could not set name attribute") + } + } + // delete item link in trash if err = os.Remove(trashItem); err != nil { log.Error().Err(err).Str("trashItem", trashItem).Msg("error deleting trashitem") @@ -461,8 +467,8 @@ func (t *Tree) RestoreRecycleItemFunc(ctx context.Context, key, restorePath stri } // PurgeRecycleItemFunc returns a node and a function to purge it from the trash -func (t *Tree) PurgeRecycleItemFunc(ctx context.Context, key string) (*node.Node, func() error, error) { - rn, trashItem, deletedNodePath, _, err := t.readRecycleItem(ctx, key) +func (t *Tree) PurgeRecycleItemFunc(ctx context.Context, key string, path string) (*node.Node, func() error, error) { + rn, trashItem, deletedNodePath, _, err := t.readRecycleItem(ctx, key, path) if err != nil { return nil, nil, err } @@ -696,13 +702,13 @@ func (t *Tree) createNode(n *node.Node, owner *userpb.UserId) (err error) { } // TODO refactor the returned params into Node properties? would make all the path transformations go away... -func (t *Tree) readRecycleItem(ctx context.Context, key string) (n *node.Node, trashItem string, deletedNodePath string, origin string, err error) { +func (t *Tree) readRecycleItem(ctx context.Context, key, path string) (n *node.Node, trashItem string, deletedNodePath string, origin string, err error) { if key == "" { return nil, "", "", "", errtypes.InternalError("key is empty") } u := user.ContextMustGetUser(ctx) - trashItem = filepath.Join(t.lookup.InternalRoot(), "trash", u.Id.OpaqueId, key) + trashItem = filepath.Join(t.lookup.InternalRoot(), "trash", u.Id.OpaqueId, key, path) var link string link, err = os.Readlink(trashItem) @@ -710,10 +716,15 @@ func (t *Tree) readRecycleItem(ctx context.Context, key string) (n *node.Node, t appctx.GetLogger(ctx).Error().Err(err).Str("trashItem", trashItem).Msg("error reading trash link") return } - parts := strings.SplitN(filepath.Base(link), ".T.", 2) - if len(parts) != 2 { - appctx.GetLogger(ctx).Error().Err(err).Str("trashItem", trashItem).Interface("parts", parts).Msg("malformed trash link") - return + + nodeID := filepath.Base(link) + if path == "" || path == "/" { + parts := strings.SplitN(filepath.Base(link), ".T.", 2) + if len(parts) != 2 { + appctx.GetLogger(ctx).Error().Err(err).Str("trashItem", trashItem).Interface("parts", parts).Msg("malformed trash link") + return + } + nodeID = parts[0] } var attrBytes []byte @@ -733,7 +744,7 @@ func (t *Tree) readRecycleItem(ctx context.Context, key string) (n *node.Node, t return } - n = node.New(parts[0], "", "", 0, "", owner, t.lookup) + n = node.New(nodeID, "", "", 0, "", owner, t.lookup) // lookup blobID in extended attributes if attrBytes, err = xattr.Get(deletedNodePath, xattrs.BlobIDAttr); err == nil { n.BlobID = string(attrBytes) @@ -757,9 +768,20 @@ func (t *Tree) readRecycleItem(ctx context.Context, key string) (n *node.Node, t // get origin node origin = "/" + deletedNodeRootPath := deletedNodePath + if path != "" && path != "/" { + trashItemRoot := filepath.Join(t.lookup.InternalRoot(), "trash", u.Id.OpaqueId, key) + var rootLink string + rootLink, err = os.Readlink(trashItemRoot) + if err != nil { + appctx.GetLogger(ctx).Error().Err(err).Str("trashItem", trashItem).Msg("error reading trash link") + return + } + deletedNodeRootPath = t.lookup.InternalPath(filepath.Base(rootLink)) + } // lookup origin path in extended attributes - if attrBytes, err = xattr.Get(deletedNodePath, xattrs.TrashOriginAttr); err == nil { - origin = string(attrBytes) + if attrBytes, err = xattr.Get(deletedNodeRootPath, xattrs.TrashOriginAttr); err == nil { + origin = filepath.Join(string(attrBytes), path) } else { log.Error().Err(err).Str("trashItem", trashItem).Str("link", link).Str("deletedNodePath", deletedNodePath).Msg("could not read origin path, restoring to /") } diff --git a/pkg/storage/utils/decomposedfs/tree/tree_test.go b/pkg/storage/utils/decomposedfs/tree/tree_test.go index ad83131c3a..08c9362a7c 100644 --- a/pkg/storage/utils/decomposedfs/tree/tree_test.go +++ b/pkg/storage/utils/decomposedfs/tree/tree_test.go @@ -115,7 +115,7 @@ var _ = Describe("Tree", func() { _, err := os.Stat(trashPath) Expect(err).ToNot(HaveOccurred()) - _, purgeFunc, err := t.PurgeRecycleItemFunc(env.Ctx, n.ID) + _, purgeFunc, err := t.PurgeRecycleItemFunc(env.Ctx, n.ID, "") Expect(err).ToNot(HaveOccurred()) Expect(purgeFunc()).To(Succeed()) }) @@ -139,7 +139,7 @@ var _ = Describe("Tree", func() { }) It("restores the file to its original location if the targetPath is empty", func() { - _, restoreFunc, err := t.RestoreRecycleItemFunc(env.Ctx, n.ID, "") + _, restoreFunc, err := t.RestoreRecycleItemFunc(env.Ctx, n.ID, "", "") Expect(err).ToNot(HaveOccurred()) Expect(restoreFunc()).To(Succeed()) @@ -150,7 +150,7 @@ var _ = Describe("Tree", func() { }) It("restores files to different locations", func() { - _, restoreFunc, err := t.RestoreRecycleItemFunc(env.Ctx, n.ID, "dir1/newLocation") + _, restoreFunc, err := t.RestoreRecycleItemFunc(env.Ctx, n.ID, "", "dir1/newLocation") Expect(err).ToNot(HaveOccurred()) Expect(restoreFunc()).To(Succeed()) @@ -165,7 +165,7 @@ var _ = Describe("Tree", func() { }) It("removes the file from the trash", func() { - _, restoreFunc, err := t.RestoreRecycleItemFunc(env.Ctx, n.ID, "") + _, restoreFunc, err := t.RestoreRecycleItemFunc(env.Ctx, n.ID, "", "") Expect(err).ToNot(HaveOccurred()) Expect(restoreFunc()).To(Succeed()) @@ -203,7 +203,7 @@ var _ = Describe("Tree", func() { _, err := os.Stat(trashPath) Expect(err).ToNot(HaveOccurred()) - _, purgeFunc, err := t.PurgeRecycleItemFunc(env.Ctx, n.ID) + _, purgeFunc, err := t.PurgeRecycleItemFunc(env.Ctx, n.ID, "") Expect(err).ToNot(HaveOccurred()) Expect(purgeFunc()).To(Succeed()) }) diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index 52d724a64f..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, key string) 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) ([]*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) ([]*provider.RecycleItem, erro return recycleEntries, nil } -func (fs *eosfs) RestoreRecycleItem(ctx context.Context, key string, 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") diff --git a/pkg/storage/utils/localfs/localfs.go b/pkg/storage/utils/localfs/localfs.go index 5416f6fa39..4b07a91f20 100644 --- a/pkg/storage/utils/localfs/localfs.go +++ b/pkg/storage/utils/localfs/localfs.go @@ -1131,7 +1131,7 @@ func (fs *localfs) RestoreRevision(ctx context.Context, ref *provider.Reference, return fs.propagate(ctx, np) } -func (fs *localfs) PurgeRecycleItem(ctx context.Context, key string) error { +func (fs *localfs) PurgeRecycleItem(ctx context.Context, key, itemPath string) error { rp := fs.wrapRecycleBin(ctx, key) if err := os.Remove(rp); err != nil { @@ -1181,7 +1181,7 @@ func (fs *localfs) convertToRecycleItem(ctx context.Context, rp string, md os.Fi } } -func (fs *localfs) ListRecycle(ctx context.Context) ([]*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) ([]*provider.RecycleItem, er return items, nil } -func (fs *localfs) RestoreRecycleItem(ctx context.Context, restoreKey string, restoreRef *provider.Reference) error { +func (fs *localfs) RestoreRecycleItem(ctx context.Context, key, itemPath string, restoreRef *provider.Reference) error { - suffix := path.Ext(restoreKey) + 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, restoreKey) + 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, restoreKey string, re return errors.New("localfs: can't restore - file already exists at original path") } - rp := fs.wrapRecycleBin(ctx, restoreKey) + rp := fs.wrapRecycleBin(ctx, key) if _, err = os.Stat(rp); err != nil { if os.IsNotExist(err) { - return errtypes.NotFound(restoreKey) + return errtypes.NotFound(key) } return errors.Wrap(err, "localfs: error stating "+rp) } @@ -1237,7 +1237,7 @@ func (fs *localfs) RestoreRecycleItem(ctx context.Context, restoreKey string, re return errors.Wrap(err, "ocfs: could not restore item") } - err = fs.removeFromRecycledDB(ctx, restoreKey) + err = fs.removeFromRecycledDB(ctx, key) if err != nil { return errors.Wrap(err, "localfs: error adding entry to DB") } diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index 45d4aa26e1..744be876c7 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -3,10 +3,6 @@ ### File Basic file management like up and download, move, copy, properties, quota, trash, versions and chunking. -#### [PROPFIND on trashbin with Depth: infinity only shows the first level](https://github.com/owncloud/ocis/issues/1116) -- [apiTrashbin/trashbinDelete.feature:106](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L106) -- [apiTrashbin/trashbinDelete.feature:122](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L122) - #### [invalid webdav responses for unauthorized requests.](https://github.com/owncloud/product/issues/273) - [apiTrashbin/trashbinFilesFolders.feature:200](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L200) - [apiTrashbin/trashbinFilesFolders.feature:201](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L201) @@ -16,27 +12,12 @@ Basic file management like up and download, move, copy, properties, quota, trash - [apiTrashbin/trashbinFilesFolders.feature:231](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L231) #### [PROPFIND on trashbin with Depth: infinity only shows the first level](https://github.com/owncloud/ocis/issues/1116) -- [apiTrashbin/trashbinFilesFolders.feature:278](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L278) -- [apiTrashbin/trashbinFilesFolders.feature:279](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L279) -- [apiTrashbinRestore/trashbinRestore.feature:459](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L459) -- [apiTrashbinRestore/trashbinRestore.feature:460](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L460) - [apiTrashbinRestore/trashbinRestore.feature:478](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L478) - [apiTrashbinRestore/trashbinRestore.feature:479](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L479) -- [apiTrashbinRestore/trashbinRestore.feature:502](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L502) -- [apiTrashbinRestore/trashbinRestore.feature:503](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L503) -- [apiTrashbinRestore/trashbinRestore.feature:521](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L521) -- [apiTrashbinRestore/trashbinRestore.feature:522](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L522) -#### [trash-bin restore move does not send back Etag and other headers](https://github.com/owncloud/ocis/issues/1121) -- [apiTrashbinRestore/trashbinRestore.feature:130](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L130) -- [apiTrashbinRestore/trashbinRestore.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L131) - -#### [PROPFIND on trashbin with Depth: infinity only shows the first level](https://github.com/owncloud/ocis/issues/1116) #### [cannot restore to a different file-name](https://github.com/owncloud/ocis/issues/1122) - [apiTrashbinRestore/trashbinRestore.feature:309](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L309) - [apiTrashbinRestore/trashbinRestore.feature:310](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L310) -- [apiTrashbinRestore/trashbinRestore.feature:329](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L329) -- [apiTrashbinRestore/trashbinRestore.feature:330](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L330) #### [Implement Versions Feature for ocis storage](https://github.com/owncloud/product/issues/210) - [apiWebdavEtagPropagation2/restoreVersion.feature:10](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/restoreVersion.feature#L10) diff --git a/tests/acceptance/expected-failures-on-S3NG-storage.md b/tests/acceptance/expected-failures-on-S3NG-storage.md index 093d35c0e5..8db46e4b05 100644 --- a/tests/acceptance/expected-failures-on-S3NG-storage.md +++ b/tests/acceptance/expected-failures-on-S3NG-storage.md @@ -3,10 +3,6 @@ ### File Basic file management like up and download, move, copy, properties, quota, trash, versions and chunking. -#### [PROPFIND on trashbin with Depth: infinity only shows the first level](https://github.com/owncloud/ocis/issues/1116) -- [apiTrashbin/trashbinDelete.feature:106](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L106) -- [apiTrashbin/trashbinDelete.feature:122](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinDelete.feature#L122) - #### [invalid webdav responses for unauthorized requests.](https://github.com/owncloud/product/issues/273) - [apiTrashbin/trashbinFilesFolders.feature:200](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L200) - [apiTrashbin/trashbinFilesFolders.feature:201](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L201) @@ -16,27 +12,12 @@ Basic file management like up and download, move, copy, properties, quota, trash - [apiTrashbin/trashbinFilesFolders.feature:231](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L231) #### [PROPFIND on trashbin with Depth: infinity only shows the first level](https://github.com/owncloud/ocis/issues/1116) -- [apiTrashbin/trashbinFilesFolders.feature:278](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L278) -- [apiTrashbin/trashbinFilesFolders.feature:279](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L279) -- [apiTrashbinRestore/trashbinRestore.feature:459](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L459) -- [apiTrashbinRestore/trashbinRestore.feature:460](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L460) - [apiTrashbinRestore/trashbinRestore.feature:478](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L478) - [apiTrashbinRestore/trashbinRestore.feature:479](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L479) -- [apiTrashbinRestore/trashbinRestore.feature:502](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L502) -- [apiTrashbinRestore/trashbinRestore.feature:503](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L503) -- [apiTrashbinRestore/trashbinRestore.feature:521](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L521) -- [apiTrashbinRestore/trashbinRestore.feature:522](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbinRestore/trashbinRestore.feature#L522) -#### [trash-bin restore move does not send back Etag and other headers](https://github.com/owncloud/ocis/issues/1121) -- [apiTrashbinRestore/trashbinRestore.feature:130](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinRestore.feature#L130) -- [apiTrashbinRestore/trashbinRestore.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinRestore.feature#L131) - -#### [PROPFIND on trashbin with Depth: infinity only shows the first level](https://github.com/owncloud/ocis/issues/1116) #### [cannot restore to a different file-name](https://github.com/owncloud/ocis/issues/1122) - [apiTrashbinRestore/trashbinRestore.feature:309](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinRestore.feature#L309) - [apiTrashbinRestore/trashbinRestore.feature:310](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinRestore.feature#L310) -- [apiTrashbinRestore/trashbinRestore.feature:329](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinRestore.feature#L329) -- [apiTrashbinRestore/trashbinRestore.feature:330](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinRestore.feature#L330) #### [Version count is 1 more than on oC10](https://github.com/owncloud/ocis/issues/1633) - [apiVersions/fileVersionsSharingToShares.feature:178](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L178) diff --git a/tests/integration/grpc/grpc_suite_test.go b/tests/integration/grpc/grpc_suite_test.go index a45338a641..ea8b14d781 100644 --- a/tests/integration/grpc/grpc_suite_test.go +++ b/tests/integration/grpc/grpc_suite_test.go @@ -41,9 +41,9 @@ const timeoutMs = 30000 var mutex = sync.Mutex{} var port = 19000 -func TestGprc(t *testing.T) { +func TestGrpc(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "Gprc Suite") + RunSpecs(t, "Grpc Suite") } type cleanupFunc func(bool) error diff --git a/tests/integration/grpc/storageprovider_test.go b/tests/integration/grpc/storageprovider_test.go index d32a446004..642283d980 100644 --- a/tests/integration/grpc/storageprovider_test.go +++ b/tests/integration/grpc/storageprovider_test.go @@ -322,7 +322,7 @@ var _ = Describe("storage providers", func() { Expect(res.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) By("listing the recycle items") - listRes, err := serviceClient.ListRecycle(ctx, &storagep.ListRecycleRequest{}) + listRes, err := serviceClient.ListRecycle(ctx, &storagep.ListRecycleRequest{Ref: homeRef}) Expect(err).ToNot(HaveOccurred()) Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) @@ -337,7 +337,7 @@ var _ = Describe("storage providers", func() { restoreRes, err := serviceClient.RestoreRecycleItem(ctx, &storagep.RestoreRecycleItemRequest{ - Ref: subdirRef, + Ref: homeRef, Key: item.Key, }, ) @@ -357,7 +357,7 @@ var _ = Describe("storage providers", func() { Expect(res.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) By("listing the recycle items") - listRes, err := serviceClient.ListRecycle(ctx, &storagep.ListRecycleRequest{}) + listRes, err := serviceClient.ListRecycle(ctx, &storagep.ListRecycleRequest{Ref: homeRef}) Expect(err).ToNot(HaveOccurred()) Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) @@ -372,7 +372,7 @@ var _ = Describe("storage providers", func() { restoreRes, err := serviceClient.RestoreRecycleItem(ctx, &storagep.RestoreRecycleItemRequest{ - Ref: subdirRef, + Ref: homeRef, Key: item.Key, RestoreRef: &storagep.Reference{Path: "/subdirRestored"}, }, @@ -392,7 +392,7 @@ var _ = Describe("storage providers", func() { Expect(res.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) By("listing recycle items") - listRes, err := serviceClient.ListRecycle(ctx, &storagep.ListRecycleRequest{}) + listRes, err := serviceClient.ListRecycle(ctx, &storagep.ListRecycleRequest{Ref: homeRef}) Expect(err).ToNot(HaveOccurred()) Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) Expect(len(listRes.RecycleItems)).To(Equal(1)) @@ -402,7 +402,7 @@ var _ = Describe("storage providers", func() { Expect(err).ToNot(HaveOccurred()) Expect(purgeRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) - listRes, err = serviceClient.ListRecycle(ctx, &storagep.ListRecycleRequest{}) + listRes, err = serviceClient.ListRecycle(ctx, &storagep.ListRecycleRequest{Ref: homeRef}) Expect(err).ToNot(HaveOccurred()) Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) Expect(len(listRes.RecycleItems)).To(Equal(0))