diff --git a/changelog/unreleased/hellofs.md b/changelog/unreleased/hellofs.md new file mode 100644 index 0000000000..6949f1cf65 --- /dev/null +++ b/changelog/unreleased/hellofs.md @@ -0,0 +1,5 @@ +Enhancement: hellofs + +We added a minimal hello world filesystem as an example for a read only storage driver. + +https://github.com/cs3org/reva/pull/4478 \ No newline at end of file diff --git a/pkg/storage/fs/hello/hello.go b/pkg/storage/fs/hello/hello.go new file mode 100644 index 0000000000..0a9d72e2d0 --- /dev/null +++ b/pkg/storage/fs/hello/hello.go @@ -0,0 +1,241 @@ +// 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 hello + +import ( + "bytes" + "context" + "crypto/md5" + "encoding/binary" + "fmt" + "io" + "strings" + "time" + + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/v2/pkg/errtypes" + "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/storage" + "github.com/cs3org/reva/v2/pkg/storage/fs/registry" + "github.com/cs3org/reva/v2/pkg/utils" +) + +func init() { + registry.Register("hello", New) +} + +type hellofs struct { + bootTime time.Time +} + +const ( + storageid = "hello-storage-id" + spaceid = "hello-space-id" + rootid = "hello-root-id" + fileid = "hello-file-id" + filename = "Hello world.txt" + content = "Hello world!" +) + +func (fs *hellofs) space(withRoot bool) *provider.StorageSpace { + s := &provider.StorageSpace{ + Id: &provider.StorageSpaceId{OpaqueId: spaceid}, + Root: &provider.ResourceId{ + StorageId: storageid, + SpaceId: spaceid, + OpaqueId: rootid, + }, + Quota: &provider.Quota{ + QuotaMaxBytes: uint64(len(content)), + QuotaMaxFiles: 1, + }, + Name: "Hello Space", + SpaceType: "project", + RootInfo: fs.rootInfo(), + Mtime: utils.TimeToTS(fs.bootTime), + } + // FIXME move this to the CS3 API + s.Opaque = utils.AppendPlainToOpaque(s.Opaque, "spaceAlias", "project/hello") + + if withRoot { + s.RootInfo = fs.rootInfo() + } + return s +} + +func (fs *hellofs) rootInfo() *provider.ResourceInfo { + return &provider.ResourceInfo{ + Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER, + Id: &provider.ResourceId{ + StorageId: storageid, + SpaceId: spaceid, + OpaqueId: rootid, + }, + Etag: calcEtag(fs.bootTime, rootid), + MimeType: "httpd/unix-directory", + Mtime: utils.TimeToTS(fs.bootTime), + Path: ".", + PermissionSet: &provider.ResourcePermissions{ + GetPath: true, + GetQuota: true, + InitiateFileDownload: true, + Stat: true, + ListContainer: true, + }, + Size: uint64(len(content)), + } +} + +func (fs *hellofs) fileInfo() *provider.ResourceInfo { + return &provider.ResourceInfo{ + Type: provider.ResourceType_RESOURCE_TYPE_FILE, + Id: &provider.ResourceId{ + StorageId: storageid, + SpaceId: spaceid, + OpaqueId: fileid, + }, + Etag: calcEtag(fs.bootTime, fileid), + MimeType: "text/plain", + Mtime: utils.TimeToTS(fs.bootTime), + Path: ".", + PermissionSet: &provider.ResourcePermissions{ + GetPath: true, + GetQuota: true, + InitiateFileDownload: true, + Stat: true, + ListContainer: true, + }, + Size: uint64(len(content)), + ParentId: &provider.ResourceId{ + StorageId: storageid, + SpaceId: spaceid, + OpaqueId: rootid, + }, + Name: filename, + Space: fs.space(false), + } +} + +func calcEtag(t time.Time, nodeid string) string { + h := md5.New() + _ = binary.Write(h, binary.BigEndian, t.Unix()) + _ = binary.Write(h, binary.BigEndian, int64(t.Nanosecond())) + _ = binary.Write(h, binary.BigEndian, []byte(nodeid)) + etag := fmt.Sprintf(`"%x"`, h.Sum(nil)) + return fmt.Sprintf("\"%s\"", strings.Trim(etag, "\"")) +} + +// New returns an implementation to of the storage.FS interface that talks to +// a local filesystem with user homes disabled. +func New(_ map[string]interface{}, _ events.Stream) (storage.FS, error) { + return &hellofs{ + bootTime: time.Now(), + }, nil +} + +// Shutdown is called when the process is exiting to give the driver a chance to flush and close all open handles +func (fs *hellofs) Shutdown(ctx context.Context) error { + return nil +} + +// ListStorageSpaces lists the spaces in the storage. +func (fs *hellofs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, unrestricted bool) ([]*provider.StorageSpace, error) { + return []*provider.StorageSpace{fs.space(true)}, nil +} + +// GetQuota returns the quota on the referenced resource +func (fs *hellofs) GetQuota(ctx context.Context, ref *provider.Reference) (uint64, uint64, uint64, error) { + return uint64(len(content)), uint64(len(content)), 0, nil +} + +func (fs *hellofs) lookup(ctx context.Context, ref *provider.Reference) (*provider.ResourceInfo, error) { + if ref.GetResourceId().GetStorageId() != storageid || ref.GetResourceId().GetSpaceId() != spaceid { + return nil, errtypes.NotFound("") + } + + // switch root or file + switch ref.GetResourceId().GetOpaqueId() { + case rootid: + switch ref.GetPath() { + case "", ".": + return fs.rootInfo(), nil + case filename: + return fs.fileInfo(), nil + default: + return nil, errtypes.NotFound("unknown filename") + } + case fileid: + return fs.fileInfo(), nil + } + + return nil, errtypes.NotFound("unknown id") +} + +// GetPathByID returns the path pointed by the file id +func (fs *hellofs) GetPathByID(ctx context.Context, resID *provider.ResourceId) (string, error) { + info, err := fs.lookup(ctx, &provider.Reference{ResourceId: resID}) + if err != nil { + return "", err + } + + return info.Path, nil +} + +// GetMD returns the resuorce info for the referenced resource +func (fs *hellofs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []string, fieldMask []string) (*provider.ResourceInfo, error) { + return fs.lookup(ctx, ref) +} + +// ListFolder returns the resource infos for all children of the referenced resource +func (fs *hellofs) ListFolder(ctx context.Context, ref *provider.Reference, mdKeys, fieldMask []string) ([]*provider.ResourceInfo, error) { + info, err := fs.lookup(ctx, ref) + if err != nil { + return nil, err + } + + if info.Type != provider.ResourceType_RESOURCE_TYPE_CONTAINER { + return nil, errtypes.InternalError("expected a container") + } + if info.GetId().GetOpaqueId() != rootid { + return nil, errtypes.InternalError("unknown folder") + } + + return []*provider.ResourceInfo{ + fs.fileInfo(), + }, nil +} + +// Download returns a ReadCloser for the content of the referenced resource +func (fs *hellofs) Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) { + info, err := fs.lookup(ctx, ref) + if err != nil { + return nil, err + } + + if info.Type != provider.ResourceType_RESOURCE_TYPE_FILE { + return nil, errtypes.InternalError("expected a file") + } + if info.GetId().GetOpaqueId() != fileid { + return nil, errtypes.InternalError("unknown file") + } + + b := &bytes.Buffer{} + b.WriteString(content) + return io.NopCloser(b), nil +} diff --git a/pkg/storage/fs/hello/unimplemented.go b/pkg/storage/fs/hello/unimplemented.go new file mode 100644 index 0000000000..3490b81083 --- /dev/null +++ b/pkg/storage/fs/hello/unimplemented.go @@ -0,0 +1,198 @@ +// 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 hello + +import ( + "context" + "io" + "net/url" + + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/v2/pkg/errtypes" + "github.com/cs3org/reva/v2/pkg/storage" +) + +// hellofs is readonly so these remain unimplemented + +// CreateReference creates a resource of type reference +func (fs *hellofs) CreateReference(ctx context.Context, path string, targetURI *url.URL) error { + return errtypes.NotSupported("unimplemented") +} + +// CreateStorageSpace creates a storage space +func (fs *hellofs) CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (*provider.CreateStorageSpaceResponse, error) { + return nil, errtypes.NotSupported("unimplemented: CreateStorageSpace") +} + +// UpdateStorageSpace updates a storage space +func (fs *hellofs) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) { + return nil, errtypes.NotSupported("update storage space") +} + +// DeleteStorageSpace deletes a storage space +func (fs *hellofs) DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorageSpaceRequest) error { + return errtypes.NotSupported("delete storage space") +} + +// CreateDir creates a resource of type container +func (fs *hellofs) CreateDir(ctx context.Context, ref *provider.Reference) error { + return errtypes.NotSupported("unimplemented") +} + +// TouchFile sets the mtime of a resource, creating an empty file if it does not exist +// FIXME the markprocessing flag is an implementation detail of decomposedfs, remove it from the function +// FIXME the mtime should either be a time.Time or a CS3 Timestamp, not a string +func (fs *hellofs) TouchFile(ctx context.Context, ref *provider.Reference, _ bool, _ string) error { + return errtypes.NotSupported("unimplemented") +} + +// Delete deletes a resource. +// If the storage driver supports a recycle bin it should moves it to the recycle bin +func (fs *hellofs) Delete(ctx context.Context, ref *provider.Reference) error { + return errtypes.NotSupported("unimplemented") +} + +// Move changes the path of a resource +func (fs *hellofs) Move(ctx context.Context, oldRef, newRef *provider.Reference) error { + return errtypes.NotSupported("unimplemented") +} + +// Upload creates or updates a resource of type file with a new revision +func (fs *hellofs) Upload(ctx context.Context, req storage.UploadRequest, uff storage.UploadFinishedFunc) (*provider.ResourceInfo, error) { + return nil, errtypes.NotSupported("hellofs: upload not supported") +} + +// InitiateUpload returns a list of protocols with urls that can be used to append bytes to a new upload session +func (fs *hellofs) InitiateUpload(ctx context.Context, ref *provider.Reference, uploadLength int64, metadata map[string]string) (map[string]string, error) { + return nil, errtypes.NotSupported("hellofs: initiate upload not supported") +} + +// grants + +// DenyGrant marks a resource as denied for a recipient +// The resource and its children must be completely hidden for the recipient +func (fs *hellofs) DenyGrant(ctx context.Context, ref *provider.Reference, g *provider.Grantee) error { + return errtypes.NotSupported("hellofs: deny grant not supported") +} + +// AddGrant adds a grant to a resource +func (fs *hellofs) AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error { + return errtypes.NotSupported("unimplemented") +} + +// ListGrants lists all grants on a resource +func (fs *hellofs) ListGrants(ctx context.Context, ref *provider.Reference) ([]*provider.Grant, error) { + return nil, errtypes.NotSupported("unimplemented") +} + +// RemoveGrant removes a grant from a resource +func (fs *hellofs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error { + return errtypes.NotSupported("unimplemented") +} + +// UpdateGrant updates a grant on a resource +func (fs *hellofs) UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error { + return errtypes.NotSupported("unimplemented") +} + +// arbitrary metadata + +// SetArbitraryMetadata sets arbitraty metadata on a resource +func (fs *hellofs) SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) error { + return errtypes.NotSupported("unimplemented") +} + +// UnsetArbitraryMetadata removes arbitraty metadata from a resource +func (fs *hellofs) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) error { + return errtypes.NotSupported("unimplemented") +} + +// locks + +// GetLock returns an existing lock on the given reference +func (fs *hellofs) GetLock(ctx context.Context, ref *provider.Reference) (*provider.Lock, error) { + return nil, errtypes.NotSupported("unimplemented") +} + +// SetLock puts a lock on the given reference +func (fs *hellofs) SetLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { + return errtypes.NotSupported("unimplemented") +} + +// RefreshLock refreshes an existing lock on the given reference +func (fs *hellofs) RefreshLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock, existingLockID string) error { + return errtypes.NotSupported("unimplemented") +} + +// Unlock removes an existing lock from the given reference +func (fs *hellofs) Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { + return errtypes.NotSupported("unimplemented") +} + +// revisions + +// ListRevisions lists all revisions for the referenced resource +func (fs *hellofs) ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) { + return nil, errtypes.NotSupported("unimplemented") +} + +// DownloadRevision downloads a revision +func (fs *hellofs) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string) (io.ReadCloser, error) { + return nil, errtypes.NotSupported("unimplemented") +} + +// RestoreRevision restores a revision +func (fs *hellofs) RestoreRevision(ctx context.Context, ref *provider.Reference, revisionKey string) error { + return errtypes.NotSupported("unimplemented") +} + +// trash + +// PurgeRecycleItem removes a resource from the recycle bin +func (fs *hellofs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference, key, relativePath string) error { + return errtypes.NotSupported("unimplemented") +} + +// EmptyRecycle removes all resource from the recycle bin +func (fs *hellofs) EmptyRecycle(ctx context.Context, ref *provider.Reference) error { + return errtypes.NotSupported("unimplemented") +} + +// ListRecycle lists the content of the recycle bin +func (fs *hellofs) ListRecycle(ctx context.Context, ref *provider.Reference, key, relativePath string) ([]*provider.RecycleItem, error) { + return nil, errtypes.NotSupported("unimplemented") +} + +// RestoreRecycleItem restores an item from the recyle bin +// if restoreRef is nil the resource should be restored at the original path +func (fs *hellofs) RestoreRecycleItem(ctx context.Context, ref *provider.Reference, key, relativePath string, restoreRef *provider.Reference) error { + return errtypes.NotSupported("unimplemented") +} + +// CreateHome creates a users home +// Deprecated: use CreateStorageSpace with type personal +func (fs *hellofs) CreateHome(ctx context.Context) error { + return errtypes.NotSupported("unimplemented") +} + +// GetHome returns the path to the users home +// Deprecated: use ListStorageSpaces with type personal +func (fs *hellofs) GetHome(ctx context.Context) (string, error) { + return "", errtypes.NotSupported("unimplemented") +} diff --git a/pkg/storage/fs/loader/loader.go b/pkg/storage/fs/loader/loader.go index 68575752c9..bb662eb238 100644 --- a/pkg/storage/fs/loader/loader.go +++ b/pkg/storage/fs/loader/loader.go @@ -26,6 +26,7 @@ import ( _ "github.com/cs3org/reva/v2/pkg/storage/fs/eosgrpc" _ "github.com/cs3org/reva/v2/pkg/storage/fs/eosgrpchome" _ "github.com/cs3org/reva/v2/pkg/storage/fs/eoshome" + _ "github.com/cs3org/reva/v2/pkg/storage/fs/hello" _ "github.com/cs3org/reva/v2/pkg/storage/fs/local" _ "github.com/cs3org/reva/v2/pkg/storage/fs/localhome" _ "github.com/cs3org/reva/v2/pkg/storage/fs/nextcloud" diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index 6fc19a0a23..93ed89865b 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -31,46 +31,118 @@ import ( // FS is the interface to implement access to the storage. type FS interface { - GetHome(ctx context.Context) (string, error) - CreateHome(ctx context.Context) error + // Minimal set for a readonly storage driver + + // Shutdown is called when the process is exiting to give the driver a chance to flush and close all open handles + Shutdown(ctx context.Context) error + + // ListStorageSpaces lists the spaces in the storage. + // FIXME The unrestricted parameter is an implementation detail of decomposedfs, remove it from the function? + ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, unrestricted bool) ([]*provider.StorageSpace, error) + + // GetQuota returns the quota on the referenced resource + GetQuota(ctx context.Context, ref *provider.Reference) ( /*TotalBytes*/ uint64 /*UsedBytes*/, uint64 /*RemainingBytes*/, uint64, error) + + // GetMD returns the resuorce info for the referenced resource + GetMD(ctx context.Context, ref *provider.Reference, mdKeys, fieldMask []string) (*provider.ResourceInfo, error) + // ListFolder returns the resource infos for all children of the referenced resource + ListFolder(ctx context.Context, ref *provider.Reference, mdKeys, fieldMask []string) ([]*provider.ResourceInfo, error) + // Download returns a ReadCloser for the content of the referenced resource + Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) + + // GetPathByID returns the path for the given resource id relative to the space root + // It should only reveal the path visible to the current user to not leak the names uf unshared parent resources + // FIXME should be deprecated in favor of calls to GetMD and the fieldmask 'path' + GetPathByID(ctx context.Context, id *provider.ResourceId) (string, error) + + // Functions for a writeable storage space + + // CreateReference creates a resource of type reference + CreateReference(ctx context.Context, path string, targetURI *url.URL) error + // CreateDir creates a resource of type container CreateDir(ctx context.Context, ref *provider.Reference) error + // TouchFile sets the mtime of a resource, creating an empty file if it does not exist + // FIXME the markprocessing flag is an implementation detail of decomposedfs, remove it from the function + // FIXME the mtime should either be a time.Time or a CS3 Timestamp, not a string TouchFile(ctx context.Context, ref *provider.Reference, markprocessing bool, mtime string) error + // Delete deletes a resource. + // If the storage driver supports a recycle bin it should moves it to the recycle bin Delete(ctx context.Context, ref *provider.Reference) error + // Move changes the path of a resource Move(ctx context.Context, oldRef, newRef *provider.Reference) error - GetMD(ctx context.Context, ref *provider.Reference, mdKeys, fieldMask []string) (*provider.ResourceInfo, error) - ListFolder(ctx context.Context, ref *provider.Reference, mdKeys, fieldMask []string) ([]*provider.ResourceInfo, error) + // InitiateUpload returns a list of protocols with urls that can be used to append bytes to a new upload session InitiateUpload(ctx context.Context, ref *provider.Reference, uploadLength int64, metadata map[string]string) (map[string]string, error) + // Upload creates or updates a resource of type file with a new revision Upload(ctx context.Context, req UploadRequest, uploadFunc UploadFinishedFunc) (*provider.ResourceInfo, error) - Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) + + // Revisions + + // ListRevisions lists all revisions for the referenced resource ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) + // DownloadRevision downloads a revision DownloadRevision(ctx context.Context, ref *provider.Reference, key string) (io.ReadCloser, error) + // RestoreRevision restores a revision RestoreRevision(ctx context.Context, ref *provider.Reference, key string) error + + // Recyce bin + + // ListRecycle lists the content of the recycle bin ListRecycle(ctx context.Context, ref *provider.Reference, key, relativePath string) ([]*provider.RecycleItem, error) + // RestoreRecycleItem restores an item from the recyle bin + // if restoreRef is nil the resource should be restored at the original path RestoreRecycleItem(ctx context.Context, ref *provider.Reference, key, relativePath string, restoreRef *provider.Reference) error + // PurgeRecycleItem removes a resource from the recycle bin PurgeRecycleItem(ctx context.Context, ref *provider.Reference, key, relativePath string) error + // EmptyRecycle removes all resource from the recycle bin EmptyRecycle(ctx context.Context, ref *provider.Reference) error - GetPathByID(ctx context.Context, id *provider.ResourceId) (string, error) + + // Grants + + // AddGrant adds a grant to a resource AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error + // DenyGrant marks a resource as denied for a recipient + // The resource and its children must be completely hidden for the recipient DenyGrant(ctx context.Context, ref *provider.Reference, g *provider.Grantee) error + // RemoveGrant removes a grant from a resource RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error + // UpdateGrant updates a grant on a resource UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error + // ListGrants lists all grants on a resource ListGrants(ctx context.Context, ref *provider.Reference) ([]*provider.Grant, error) - GetQuota(ctx context.Context, ref *provider.Reference) ( /*TotalBytes*/ uint64 /*UsedBytes*/, uint64 /*RemainingBytes*/, uint64, error) - CreateReference(ctx context.Context, path string, targetURI *url.URL) error - Shutdown(ctx context.Context) error + + // Arbitrary Metadata + + // SetArbitraryMetadata sets arbitraty metadata on a resource SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) error + // UnsetArbitraryMetadata removes arbitraty metadata from a resource UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) error - SetLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error + + // Locks + + // GetLock returns an existing lock on the given reference GetLock(ctx context.Context, ref *provider.Reference) (*provider.Lock, error) + // SetLock puts a lock on the given reference + SetLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error + // RefreshLock refreshes an existing lock on the given reference RefreshLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock, existingLockID string) error + // Unlock removes an existing lock from the given reference Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error - // ListStorageSpaces lists the spaces in the storage. - // The unrestricted parameter can be used to list other user's spaces when - // the user has the necessary permissions. - ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, unrestricted bool) ([]*provider.StorageSpace, error) + + // Spaces + + // CreateStorageSpace creates a storage space CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (*provider.CreateStorageSpaceResponse, error) + // UpdateStorageSpace updates a storage space UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) + // DeleteStorageSpace deletes a storage space DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorageSpaceRequest) error + + // CreateHome creates a users home + // Deprecated: use CreateStorageSpace with type personal + CreateHome(ctx context.Context) error + // GetHome returns the path to the users home + // Deprecated: use ListStorageSpaces with type personal + GetHome(ctx context.Context) (string, error) } // UnscopeFunc is a function that unscopes a user