diff --git a/changelog/unreleased/shares-folder-mount.md b/changelog/unreleased/shares-folder-mount.md new file mode 100644 index 0000000000..7afcbe5e74 --- /dev/null +++ b/changelog/unreleased/shares-folder-mount.md @@ -0,0 +1,3 @@ +Enhancement: Move shares folder out from home directory to a separate mount + +https://github.com/cs3org/reva/pull/1584 diff --git a/internal/grpc/services/gateway/ocmshareprovider.go b/internal/grpc/services/gateway/ocmshareprovider.go index 5e8df9efc8..cc023b8f35 100644 --- a/internal/grpc/services/gateway/ocmshareprovider.go +++ b/internal/grpc/services/gateway/ocmshareprovider.go @@ -391,7 +391,7 @@ func (s *svc) UpdateReceivedOCMShare(ctx context.Context, req *ocm.UpdateReceive Status: createRefStatus, }, err case ocm.ShareState_SHARE_STATE_REJECTED: - s.removeReference(ctx, req.GetShare().GetShare().ResourceId) // error is logged inside removeReference + s.removeOCMReference(ctx, req.GetShare().GetShare().GetResourceId()) // error is logged inside removeReference // FIXME we are ignoring an error from removeReference here return res, nil } @@ -425,6 +425,77 @@ func (s *svc) GetReceivedOCMShare(ctx context.Context, req *ocm.GetReceivedOCMSh return res, nil } +func (s *svc) removeOCMReference(ctx context.Context, resourceID *provider.ResourceId) *rpc.Status { + log := appctx.GetLogger(ctx) + + idReference := &provider.Reference{ResourceId: resourceID} + storageProvider, err := s.find(ctx, idReference) + if err != nil { + if _, ok := err.(errtypes.IsNotFound); ok { + return status.NewNotFound(ctx, "storage provider not found") + } + return status.NewInternal(ctx, err, "error finding storage provider") + } + + statRes, err := storageProvider.Stat(ctx, &provider.StatRequest{Ref: idReference}) + if err != nil { + return status.NewInternal(ctx, err, "gateway: error calling Stat for the share resource id: "+resourceID.String()) + } + + // FIXME how can we delete a reference if the original resource was deleted? + if statRes.Status.Code != rpc.Code_CODE_OK { + err := status.NewErrorFromCode(statRes.Status.GetCode(), "gateway") + return status.NewInternal(ctx, err, "could not delete share reference") + } + + homeRes, err := s.GetHome(ctx, &provider.GetHomeRequest{}) + if err != nil { + err := errors.Wrap(err, "gateway: error calling GetHome") + return status.NewInternal(ctx, err, "could not delete share reference") + } + + sharePath := path.Join(homeRes.Path, s.c.ShareFolder, path.Base(statRes.Info.Path)) + log.Debug().Str("share_path", sharePath).Msg("remove reference of share") + + homeProvider, err := s.find(ctx, &provider.Reference{Path: sharePath}) + if err != nil { + if _, ok := err.(errtypes.IsNotFound); ok { + return status.NewNotFound(ctx, "storage provider not found") + } + return status.NewInternal(ctx, err, "error finding storage provider") + } + + deleteReq := &provider.DeleteRequest{ + Opaque: &types.Opaque{ + Map: map[string]*types.OpaqueEntry{ + // This signals the storageprovider that we want to delete the share reference and not the underlying file. + "deleting_shared_resource": {}, + }, + }, + Ref: &provider.Reference{Path: sharePath}, + } + + deleteResp, err := homeProvider.Delete(ctx, deleteReq) + if err != nil { + return status.NewInternal(ctx, err, "could not delete share reference") + } + + switch deleteResp.Status.Code { + case rpc.Code_CODE_OK: + // we can continue deleting the reference + case rpc.Code_CODE_NOT_FOUND: + // This is fine, we wanted to delete it anyway + return status.NewOK(ctx) + default: + err := status.NewErrorFromCode(deleteResp.Status.GetCode(), "gateway") + return status.NewInternal(ctx, err, "could not delete share reference") + } + + log.Debug().Str("share_path", sharePath).Msg("share reference successfully removed") + + return status.NewOK(ctx) +} + func (s *svc) createOCMReference(ctx context.Context, share *ocm.Share) (*rpc.Status, error) { log := appctx.GetLogger(ctx) diff --git a/internal/grpc/services/gateway/sharesstorageprovider.go b/internal/grpc/services/gateway/sharesstorageprovider.go new file mode 100644 index 0000000000..2aa02bd475 --- /dev/null +++ b/internal/grpc/services/gateway/sharesstorageprovider.go @@ -0,0 +1,163 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package gateway + +import ( + "context" + "path" + "strings" + + "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" + + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/pkg/rgrpc/status" + "github.com/cs3org/reva/pkg/storage/utils/etag" +) + +func (s *svc) getSharedFolder(ctx context.Context) string { + return path.Join("/", s.c.ShareFolder) +} + +// check if the path contains the prefix of the shared folder +func (s *svc) inSharedFolder(ctx context.Context, p string) bool { + sharedFolder := s.getSharedFolder(ctx) + return strings.HasPrefix(p, sharedFolder) +} + +// /MyShares/ +func (s *svc) isSharedFolder(ctx context.Context, p string) bool { + return p == s.getSharedFolder(ctx) +} + +// /MyShares/photos/ +func (s *svc) isShareName(ctx context.Context, p string) bool { + sharedFolder := s.getSharedFolder(ctx) + rel := strings.Trim(strings.TrimPrefix(p, sharedFolder), "/") + return strings.HasPrefix(p, sharedFolder) && len(strings.Split(rel, "/")) == 1 +} + +// /MyShares/photos/Ibiza/beach.png +func (s *svc) isShareChild(ctx context.Context, p string) bool { + sharedFolder := s.getSharedFolder(ctx) + rel := strings.Trim(strings.TrimPrefix(p, sharedFolder), "/") + return strings.HasPrefix(p, sharedFolder) && len(strings.Split(rel, "/")) > 1 +} + +// path must contain a share path with share children, if not it will panic. +// should be called after checking isShareChild == true +func (s *svc) splitShare(ctx context.Context, p string) (string, string) { + sharedFolder := s.getSharedFolder(ctx) + p = strings.Trim(strings.TrimPrefix(p, sharedFolder), "/") + parts := strings.SplitN(p, "/", 2) + if len(parts) != 2 { + panic("gateway: path for splitShare does not contain 2 elements:" + p) + } + + shareName := path.Join(sharedFolder, parts[0]) + shareChild := path.Join("/", parts[1]) + return shareName, shareChild +} + +func (s *svc) statSharesFolder(ctx context.Context) (*provider.StatResponse, error) { + statRes, err := s.stat(ctx, &provider.StatRequest{ + Ref: &provider.Reference{ + Path: s.getSharedFolder(ctx), + }, + }) + if err != nil { + return &provider.StatResponse{ + Status: status.NewInternal(ctx, err, "gateway: error stating shares folder"), + }, nil + } + + if statRes.Status.Code != rpc.Code_CODE_OK { + return &provider.StatResponse{ + Status: statRes.Status, + }, nil + } + + lsRes, err := s.listSharesFolder(ctx) + if err != nil { + return &provider.StatResponse{ + Status: status.NewInternal(ctx, err, "gateway: error listing shares folder"), + }, nil + } + if lsRes.Status.Code != rpc.Code_CODE_OK { + return &provider.StatResponse{ + Status: lsRes.Status, + }, nil + } + + etagCacheKey := statRes.Info.Owner.OpaqueId + ":" + statRes.Info.Path + if resEtag, err := s.etagCache.Get(etagCacheKey); err == nil { + statRes.Info.Etag = resEtag.(string) + } else { + statRes.Info.Etag = etag.GenerateEtagFromResources(statRes.Info, lsRes.Infos) + if s.c.EtagCacheTTL > 0 { + _ = s.etagCache.Set(etagCacheKey, statRes.Info.Etag) + } + } + return statRes, nil +} + +func (s *svc) listSharesFolder(ctx context.Context) (*provider.ListContainerResponse, error) { + lcr, err := s.listContainer(ctx, &provider.ListContainerRequest{ + Ref: &provider.Reference{ + Path: s.getSharedFolder(ctx), + }, + }) + if err != nil { + return &provider.ListContainerResponse{ + Status: status.NewInternal(ctx, err, "gateway: error listing shared folder"), + }, nil + } + if lcr.Status.Code != rpc.Code_CODE_OK { + return &provider.ListContainerResponse{ + Status: lcr.Status, + }, nil + } + checkedInfos := make([]*provider.ResourceInfo, 0) + for i := range lcr.Infos { + info, protocol, err := s.checkRef(ctx, lcr.Infos[i]) + if err != nil { + // Create status to log the proper messages + // This might arise when the shared resource has been moved to the recycle bin + // or when the resource was unshared, but the share reference was not removed + status.NewStatusFromErrType(ctx, "error resolving reference "+lcr.Infos[i].Target, err) + continue + } + + if protocol == "webdav" { + info, err = s.webdavRefStat(ctx, lcr.Infos[i].Target) + if err != nil { + // This might arise when the webdav token has expired + continue + } + } + + // It should be possible to delete and move share references, so expose all possible permissions + info.PermissionSet = conversions.NewManagerRole().CS3ResourcePermissions() + info.Path = lcr.Infos[i].GetPath() + checkedInfos = append(checkedInfos, info) + } + lcr.Infos = checkedInfos + + return lcr, nil +} diff --git a/internal/grpc/services/gateway/storageprovider.go b/internal/grpc/services/gateway/storageprovider.go index 1fbad971fc..92678d1bdc 100644 --- a/internal/grpc/services/gateway/storageprovider.go +++ b/internal/grpc/services/gateway/storageprovider.go @@ -35,15 +35,15 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" ctxpkg "github.com/cs3org/reva/pkg/ctx" rtrace "github.com/cs3org/reva/pkg/trace" + "github.com/cs3org/reva/pkg/utils" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/rgrpc/status" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" - "github.com/cs3org/reva/pkg/storage/utils/etag" - "github.com/cs3org/reva/pkg/utils" "github.com/golang-jwt/jwt" "github.com/google/uuid" "github.com/pkg/errors" @@ -782,7 +782,6 @@ func (s *svc) CreateContainer(ctx context.Context, req *provider.CreateContainer return &provider.CreateContainerResponse{ Status: status.NewInvalidArg(ctx, "path points to share folder or share name"), }, nil - } if s.isShareChild(ctx, p) { @@ -868,12 +867,6 @@ func (s *svc) TouchFile(ctx context.Context, req *provider.TouchFileRequest) (*p return res, nil } -// check if the path contains the prefix of the shared folder -func (s *svc) inSharedFolder(ctx context.Context, p string) bool { - sharedFolder := s.getSharedFolder(ctx) - return strings.HasPrefix(p, sharedFolder) -} - func (s *svc) Delete(ctx context.Context, req *provider.DeleteRequest) (*provider.DeleteResponse, error) { log := appctx.GetLogger(ctx) p, st := s.getPath(ctx, req.Ref) @@ -1305,97 +1298,6 @@ func (s *svc) Unlock(ctx context.Context, req *provider.UnlockRequest) (*provide return res, nil } -func (s *svc) statHome(ctx context.Context) (*provider.StatResponse, error) { - statRes, err := s.stat(ctx, &provider.StatRequest{Ref: &provider.Reference{Path: s.getHome(ctx)}}) - if err != nil { - return &provider.StatResponse{ - Status: status.NewInternal(ctx, err, "gateway: error stating home"), - }, nil - } - - if statRes.Status.Code != rpc.Code_CODE_OK { - return &provider.StatResponse{ - Status: statRes.Status, - }, nil - } - - statSharedFolder, err := s.statSharesFolder(ctx) - if err != nil { - return &provider.StatResponse{ - Status: status.NewInternal(ctx, err, "gateway: error stating shares folder"), - }, nil - } - if statSharedFolder.Status.Code != rpc.Code_CODE_OK { - // If shares folder is not found, skip updating the etag - if statSharedFolder.Status.Code == rpc.Code_CODE_NOT_FOUND { - return statRes, nil - } - // otherwise return stat of share folder - return &provider.StatResponse{ - Status: statSharedFolder.Status, - }, nil - } - - if etagIface, err := s.etagCache.Get(statRes.Info.Owner.OpaqueId + ":" + statRes.Info.Path); err == nil { - resMtime := utils.TSToTime(statRes.Info.Mtime) - resEtag := etagIface.(etagWithTS) - // Use the updated etag if the home folder has been modified - if resMtime.Before(resEtag.Timestamp) { - statRes.Info.Etag = resEtag.Etag - } - } else { - statRes.Info.Etag = etag.GenerateEtagFromResources(statRes.Info, []*provider.ResourceInfo{statSharedFolder.Info}) - if s.c.EtagCacheTTL > 0 { - _ = s.etagCache.Set(statRes.Info.Owner.OpaqueId+":"+statRes.Info.Path, etagWithTS{statRes.Info.Etag, time.Now()}) - } - } - - return statRes, nil -} - -func (s *svc) statSharesFolder(ctx context.Context) (*provider.StatResponse, error) { - statRes, err := s.stat(ctx, &provider.StatRequest{Ref: &provider.Reference{Path: s.getSharedFolder(ctx)}}) - if err != nil { - return &provider.StatResponse{ - Status: status.NewInternal(ctx, err, "gateway: error stating shares folder"), - }, nil - } - - if statRes.Status.Code != rpc.Code_CODE_OK { - return &provider.StatResponse{ - Status: statRes.Status, - }, nil - } - - lsRes, err := s.listSharesFolder(ctx) - if err != nil { - return &provider.StatResponse{ - Status: status.NewInternal(ctx, err, "gateway: error listing shares folder"), - }, nil - } - if lsRes.Status.Code != rpc.Code_CODE_OK { - return &provider.StatResponse{ - Status: lsRes.Status, - }, nil - } - - if etagIface, err := s.etagCache.Get(statRes.Info.Owner.OpaqueId + ":" + statRes.Info.Path); err == nil { - resMtime := utils.TSToTime(statRes.Info.Mtime) - resEtag := etagIface.(etagWithTS) - // Use the updated etag if the shares folder has been modified, i.e., a new - // reference has been created. - if resMtime.Before(resEtag.Timestamp) { - statRes.Info.Etag = resEtag.Etag - } - } else { - statRes.Info.Etag = etag.GenerateEtagFromResources(statRes.Info, lsRes.Infos) - if s.c.EtagCacheTTL > 0 { - _ = s.etagCache.Set(statRes.Info.Owner.OpaqueId+":"+statRes.Info.Path, etagWithTS{statRes.Info.Etag, time.Now()}) - } - } - return statRes, nil -} - func (s *svc) stat(ctx context.Context, req *provider.StatRequest) (*provider.StatResponse, error) { providers, err := s.findProviders(ctx, req.Ref) if err != nil { @@ -1439,6 +1341,11 @@ func (s *svc) statAcrossProviders(ctx context.Context, req *provider.StatRequest } for _, p := range providers { + // If it's a share storage provider, don't consider it in virtual views + if strings.HasPrefix(p.ProviderPath, "/Shares") { + continue + } + c, err := s.getStorageProviderClient(ctx, p) if err != nil { log.Err(err).Msg("error connecting to storage provider=" + p.Address) @@ -1497,10 +1404,6 @@ func (s *svc) Stat(ctx context.Context, req *provider.StatRequest) (*provider.St p = res.Info.Path } - if path.Clean(p) == s.getHome(ctx) { - return s.statHome(ctx) - } - if s.isSharedFolder(ctx, p) { return s.statSharesFolder(ctx) } @@ -1555,6 +1458,8 @@ func (s *svc) Stat(ctx context.Context, req *provider.StatRequest) (*provider.St orgPath := res.Info.Path res.Info = ri res.Info.Path = orgPath + // It should be possible to delete and move share references, so expose all possible permissions + res.Info.PermissionSet = conversions.NewManagerRole().CS3ResourcePermissions() return res, nil } @@ -1699,83 +1604,6 @@ func (s *svc) ListContainerStream(_ *provider.ListContainerStreamRequest, _ gate return errtypes.NotSupported("Unimplemented") } -func (s *svc) listHome(ctx context.Context, req *provider.ListContainerRequest) (*provider.ListContainerResponse, error) { - lcr, err := s.listContainer(ctx, &provider.ListContainerRequest{ - Ref: &provider.Reference{Path: s.getHome(ctx)}, - ArbitraryMetadataKeys: req.ArbitraryMetadataKeys, - }) - if err != nil { - return &provider.ListContainerResponse{ - Status: status.NewInternal(ctx, err, "gateway: error listing home"), - }, nil - } - if lcr.Status.Code != rpc.Code_CODE_OK { - return &provider.ListContainerResponse{ - Status: lcr.Status, - }, nil - } - - for i := range lcr.Infos { - if s.isSharedFolder(ctx, lcr.Infos[i].GetPath()) { - statSharedFolder, err := s.statSharesFolder(ctx) - if err != nil { - return &provider.ListContainerResponse{ - Status: status.NewInternal(ctx, err, "gateway: error stating shares folder"), - }, nil - } - if statSharedFolder.Status.Code != rpc.Code_CODE_OK { - return &provider.ListContainerResponse{ - Status: statSharedFolder.Status, - }, nil - } - lcr.Infos[i] = statSharedFolder.Info - break - } - } - - return lcr, nil -} - -func (s *svc) listSharesFolder(ctx context.Context) (*provider.ListContainerResponse, error) { - lcr, err := s.listContainer(ctx, &provider.ListContainerRequest{Ref: &provider.Reference{Path: s.getSharedFolder(ctx)}}) - if err != nil { - return &provider.ListContainerResponse{ - Status: status.NewInternal(ctx, err, "gateway: error listing shared folder"), - }, nil - } - if lcr.Status.Code != rpc.Code_CODE_OK { - return &provider.ListContainerResponse{ - Status: lcr.Status, - }, nil - } - checkedInfos := make([]*provider.ResourceInfo, 0) - for i := range lcr.Infos { - info, protocol, err := s.checkRef(ctx, lcr.Infos[i]) - if err != nil { - // create status to log the proper messages - // this might arise when the shared resource has been moved to the recycle bin - // this might arise when the resource was unshared, but the share reference was not removed - status.NewStatusFromErrType(ctx, "error resolving reference "+lcr.Infos[i].Target, err) - // continue on errors so the user can see a list of the working shares - continue - } - - if protocol == "webdav" { - info, err = s.webdavRefStat(ctx, lcr.Infos[i].Target) - if err != nil { - // Might be the case that the webdav token has expired - continue - } - } - - info.Path = lcr.Infos[i].Path - checkedInfos = append(checkedInfos, info) - } - lcr.Infos = checkedInfos - - return lcr, nil -} - func (s *svc) filterProvidersByUserAgent(ctx context.Context, providers []*registry.ProviderInfo) []*registry.ProviderInfo { cat, ok := ctxpkg.ContextGetUserAgentCategory(ctx) if !ok { @@ -1839,6 +1667,11 @@ func (s *svc) listContainerAcrossProviders(ctx context.Context, req *provider.Li log := appctx.GetLogger(ctx) for _, p := range s.filterProvidersByUserAgent(ctx, providers) { + // If it's a share storage provider, don't consider it in virtual views + if strings.HasPrefix(p.ProviderPath, "/Shares") { + continue + } + c, err := s.getStorageProviderClient(ctx, p) if err != nil { log.Err(err).Msg("error connecting to storage provider=" + p.Address) @@ -1905,10 +1738,6 @@ func (s *svc) ListContainer(ctx context.Context, req *provider.ListContainerRequ }, nil } - if path.Clean(p) == s.getHome(ctx) { - return s.listHome(ctx, req) - } - if s.isSharedFolder(ctx, p) { return s.listSharesFolder(ctx) } @@ -2095,75 +1924,6 @@ func (s *svc) getPath(ctx context.Context, ref *provider.Reference, keys ...stri return "", &rpc.Status{Code: rpc.Code_CODE_INTERNAL} } -// /home/MyShares/ -func (s *svc) isSharedFolder(ctx context.Context, p string) bool { - return s.split(ctx, p, 2) -} - -// /home/MyShares/photos/ -func (s *svc) isShareName(ctx context.Context, p string) bool { - return s.split(ctx, p, 3) -} - -// /home/MyShares/photos/Ibiza/beach.png -func (s *svc) isShareChild(ctx context.Context, p string) bool { - return s.split(ctx, p, 4) -} - -// always validate that the path contains the share folder -// split cannot be called with i<2 -func (s *svc) split(ctx context.Context, p string, i int) bool { - log := appctx.GetLogger(ctx) - if i < 2 { - panic("split called with i < 2") - } - - parts := s.splitPath(ctx, p) - - // validate that we have always at least two elements - if len(parts) < 2 { - return false - } - - // validate the share folder is always the second element, first element is always the hardcoded value of "home" - if parts[1] != s.c.ShareFolder { - log.Debug().Msgf("gateway: split: parts[1]:%+v != shareFolder:%+v", parts[1], s.c.ShareFolder) - return false - } - - log.Debug().Msgf("gateway: split: path:%+v parts:%+v shareFolder:%+v", p, parts, s.c.ShareFolder) - - if len(parts) == i && parts[i-1] != "" { - return true - } - - return false -} - -// path must contain a share path with share children, if not it will panic. -// should be called after checking isShareChild == true -func (s *svc) splitShare(ctx context.Context, p string) (string, string) { - parts := s.splitPath(ctx, p) - if len(parts) != 4 { - panic("gateway: path for splitShare does not contain 4 elements:" + p) - } - - shareName := path.Join("/", parts[0], parts[1], parts[2]) - shareChild := path.Join("/", parts[3]) - return shareName, shareChild -} - -func (s *svc) splitPath(_ context.Context, p string) []string { - p = strings.Trim(p, "/") - return strings.SplitN(p, "/", 4) // ["home", "MyShares", "photos", "Ibiza/beach.png"] -} - -func (s *svc) getSharedFolder(ctx context.Context) string { - home := s.getHome(ctx) - shareFolder := path.Join(home, s.c.ShareFolder) - return shareFolder -} - func (s *svc) CreateSymlink(ctx context.Context, req *provider.CreateSymlinkRequest) (*provider.CreateSymlinkResponse, error) { return &provider.CreateSymlinkResponse{ Status: status.NewUnimplemented(ctx, errtypes.NotSupported("CreateSymlink not implemented"), "CreateSymlink not implemented"), @@ -2342,8 +2102,3 @@ func getUniqueProviders(providers []*registry.ProviderInfo) []*registry.Provider } return res } - -type etagWithTS struct { - Etag string - Timestamp time.Time -} diff --git a/internal/grpc/services/gateway/usershareprovider.go b/internal/grpc/services/gateway/usershareprovider.go index f2c1b515d8..86d3bed499 100644 --- a/internal/grpc/services/gateway/usershareprovider.go +++ b/internal/grpc/services/gateway/usershareprovider.go @@ -34,6 +34,7 @@ import ( "github.com/cs3org/reva/pkg/rgrpc/status" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/pkg/storage/utils/grants" + "github.com/cs3org/reva/pkg/utils" "github.com/pkg/errors" ) @@ -130,8 +131,6 @@ func (s *svc) RemoveShare(ctx context.Context, req *collaboration.RemoveShareReq return nil, errors.Wrap(err, "gateway: error calling RemoveShare") } - s.removeReference(ctx, share.ResourceId) - // if we don't need to commit we return earlier if !s.c.CommitShareToStorageGrant && !s.c.CommitShareToStorageRef { return res, nil @@ -346,12 +345,12 @@ func (s *svc) UpdateReceivedShare(ctx context.Context, req *collaboration.Update case "state": switch req.GetShare().GetState() { case collaboration.ShareState_SHARE_STATE_ACCEPTED: - rpcStatus := s.createReference(ctx, res.GetShare().GetShare().GetResourceId()) + rpcStatus := s.createReference(ctx, res.GetShare().GetShare()) if rpcStatus.Code != rpc.Code_CODE_OK { return &collaboration.UpdateReceivedShareResponse{Status: rpcStatus}, nil } case collaboration.ShareState_SHARE_STATE_REJECTED: - rpcStatus := s.removeReference(ctx, res.GetShare().GetShare().ResourceId) + rpcStatus := s.removeReference(ctx, res.GetShare().GetShare()) if rpcStatus.Code != rpc.Code_CODE_OK && rpcStatus.Code != rpc.Code_CODE_NOT_FOUND { return &collaboration.UpdateReceivedShareResponse{Status: rpcStatus}, nil } @@ -369,10 +368,10 @@ func (s *svc) UpdateReceivedShare(ctx context.Context, req *collaboration.Update return res, nil } -func (s *svc) removeReference(ctx context.Context, resourceID *provider.ResourceId) *rpc.Status { +func (s *svc) removeReference(ctx context.Context, share *collaboration.Share) *rpc.Status { log := appctx.GetLogger(ctx) - idReference := &provider.Reference{ResourceId: resourceID} + idReference := &provider.Reference{ResourceId: share.ResourceId} storageProvider, err := s.find(ctx, idReference) if err != nil { if _, ok := err.(errtypes.IsNotFound); ok { @@ -383,7 +382,7 @@ func (s *svc) removeReference(ctx context.Context, resourceID *provider.Resource statRes, err := storageProvider.Stat(ctx, &provider.StatRequest{Ref: idReference}) if err != nil { - return status.NewInternal(ctx, err, "gateway: error calling Stat for the share resource id: "+resourceID.String()) + return status.NewInternal(ctx, err, "gateway: error calling Stat for the share resource id: "+share.ResourceId.String()) } // FIXME how can we delete a reference if the original resource was deleted? @@ -398,51 +397,68 @@ func (s *svc) removeReference(ctx context.Context, resourceID *provider.Resource return status.NewInternal(ctx, err, "could not delete share reference") } - sharePath := path.Join(homeRes.Path, s.c.ShareFolder, path.Base(statRes.Info.Path)) - log.Debug().Str("share_path", sharePath).Msg("remove reference of share") + // We list the shares folder and delete references corresponding to this share + // TODO: We need to maintain a DB of these + shareFolderPath := path.Join(homeRes.Path, s.c.ShareFolder) - homeProvider, err := s.find(ctx, &provider.Reference{Path: sharePath}) + homeProvider, err := s.find(ctx, &provider.Reference{Path: shareFolderPath}) if err != nil { if _, ok := err.(errtypes.IsNotFound); ok { return status.NewNotFound(ctx, "storage provider not found") } return status.NewInternal(ctx, err, "error finding storage provider") } - - deleteReq := &provider.DeleteRequest{ - Opaque: &typesv1beta1.Opaque{ - Map: map[string]*typesv1beta1.OpaqueEntry{ - // This signals the storageprovider that we want to delete the share reference and not the underlying file. - "deleting_shared_resource": {}, - }, - }, - Ref: &provider.Reference{Path: sharePath}, - } - - deleteResp, err := homeProvider.Delete(ctx, deleteReq) + shareRefs, err := homeProvider.ListContainer(ctx, &provider.ListContainerRequest{ + Ref: &provider.Reference{Path: shareFolderPath}, + }) if err != nil { - return status.NewInternal(ctx, err, "could not delete share reference") + return status.NewInternal(ctx, err, "gateway: error listing shares folder") } - - switch deleteResp.Status.Code { - case rpc.Code_CODE_OK: - // we can continue deleting the reference - case rpc.Code_CODE_NOT_FOUND: - // This is fine, we wanted to delete it anyway - return status.NewOK(ctx) - default: - err := status.NewErrorFromCode(deleteResp.Status.GetCode(), "gateway") - return status.NewInternal(ctx, err, "could not delete share reference") + if shareRefs.Status.Code != rpc.Code_CODE_OK { + err := status.NewErrorFromCode(shareRefs.Status.GetCode(), "gateway") + return status.NewInternal(ctx, err, "could not list shares folder") } - log.Debug().Str("share_path", sharePath).Msg("share reference successfully removed") + target := fmt.Sprintf("cs3:%s/%s", share.ResourceId.GetStorageId(), share.ResourceId.GetOpaqueId()) + for _, s := range shareRefs.Infos { + if s.Target == target { + log.Debug().Str("share_path", s.Path).Msg("remove reference of share") + + deleteReq := &provider.DeleteRequest{ + Opaque: &typesv1beta1.Opaque{ + Map: map[string]*typesv1beta1.OpaqueEntry{ + // This signals the storageprovider that we want to delete the share reference and not the underlying file. + "deleting_shared_resource": {}, + }, + }, + Ref: &provider.Reference{Path: s.Path}, + } + + deleteResp, err := homeProvider.Delete(ctx, deleteReq) + if err != nil { + return status.NewInternal(ctx, err, "could not delete share reference") + } + + switch deleteResp.Status.Code { + case rpc.Code_CODE_OK: + // we can continue deleting the reference + case rpc.Code_CODE_NOT_FOUND: + // This is fine, we wanted to delete it anyway + return status.NewOK(ctx) + default: + err := status.NewErrorFromCode(deleteResp.Status.GetCode(), "gateway") + return status.NewInternal(ctx, err, "could not delete share reference") + } + log.Debug().Str("share_path", s.Path).Msg("share reference successfully removed") + } + } return status.NewOK(ctx) } -func (s *svc) createReference(ctx context.Context, resourceID *provider.ResourceId) *rpc.Status { +func (s *svc) createReference(ctx context.Context, share *collaboration.Share) *rpc.Status { ref := &provider.Reference{ - ResourceId: resourceID, + ResourceId: share.ResourceId, } log := appctx.GetLogger(ctx) @@ -461,12 +477,12 @@ func (s *svc) createReference(ctx context.Context, resourceID *provider.Resource statRes, err := c.Stat(ctx, statReq) if err != nil { - return status.NewInternal(ctx, err, "gateway: error calling Stat for the share resource id: "+resourceID.String()) + return status.NewInternal(ctx, err, "gateway: error calling Stat for the share resource id: "+share.ResourceId.String()) } if statRes.Status.Code != rpc.Code_CODE_OK { err := status.NewErrorFromCode(statRes.Status.GetCode(), "gateway") - log.Err(err).Msg("gateway: Stat failed on the share resource id: " + resourceID.String()) + log.Err(err).Msg("gateway: Stat failed on the share resource id: " + share.ResourceId.String()) return status.NewInternal(ctx, err, "error updating received share") } @@ -486,16 +502,8 @@ func (s *svc) createReference(ctx context.Context, resourceID *provider.Resource // It is the responsibility of the gateway to resolve these references and merge the response back // from the main request. // TODO(labkode): the name of the share should be the filename it points to by default. - refPath := path.Join(homeRes.Path, s.c.ShareFolder, path.Base(statRes.Info.Path)) - log.Info().Msg("mount path will be:" + refPath) - - createRefReq := &provider.CreateReferenceRequest{ - Ref: &provider.Reference{Path: refPath}, - // cs3 is the Scheme and %s/%s is the Opaque parts of a net.URL. - TargetUri: fmt.Sprintf("cs3:%s/%s", resourceID.GetStorageId(), resourceID.GetOpaqueId()), - } - - c, err = s.findByPath(ctx, refPath) + refPath := &provider.Reference{Path: path.Join(homeRes.Path, s.c.ShareFolder, path.Base(statRes.Info.Path))} + c, err = s.findByPath(ctx, refPath.Path) if err != nil { if _, ok := err.(errtypes.IsNotFound); ok { return status.NewNotFound(ctx, "storage provider not found") @@ -503,6 +511,24 @@ func (s *svc) createReference(ctx context.Context, resourceID *provider.Resource return status.NewInternal(ctx, err, "error finding storage provider") } + refPathStat, err := c.Stat(ctx, &provider.StatRequest{ + Ref: refPath, + }) + if err == nil && refPathStat.Status.Code == rpc.Code_CODE_OK { + // This reference already exists, add extra metadata to avoid conflicts + name := fmt.Sprintf("%s_%s_%s", path.Base(statRes.Info.Path), share.Owner.OpaqueId, utils.TSToTime(share.Ctime).Format("2006-01-02_15-04-05")) + refPath = &provider.Reference{Path: path.Join(homeRes.Path, s.c.ShareFolder, name)} + } + + log.Info().Msgf("refPathStat %+v %+v", refPathStat.Status, err) + log.Info().Msg("mount path will be:" + refPath.Path) + + createRefReq := &provider.CreateReferenceRequest{ + Ref: refPath, + // cs3 is the Scheme and %s/%s is the Opaque parts of a net.URL. + TargetUri: fmt.Sprintf("cs3:%s/%s", share.ResourceId.GetStorageId(), share.ResourceId.GetOpaqueId()), + } + createRefRes, err := c.CreateReference(ctx, createRefReq) if err != nil { log.Err(err).Msg("gateway: error calling GetHome") diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index 197bbcf1da..9cdb8a59f9 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -1272,7 +1272,6 @@ func (fs *eosfs) ListFolder(ctx context.Context, ref *provider.Reference, mdKeys if fs.conf.EnableHome { return fs.listWithHome(ctx, p) } - return fs.listWithNominalHome(ctx, p) }