Skip to content

Commit 9c622d3

Browse files
committed
add hellofs, cleanup & document storage interface
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
1 parent 77b79b7 commit 9c622d3

File tree

5 files changed

+531
-14
lines changed

5 files changed

+531
-14
lines changed

changelog/unreleased/hellofs.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Enhancement: hellofs
2+
3+
We added a minimal hello world filesystem as an example for a read only storage driver.
4+
5+
https://github.com/cs3org/reva/pull/4478

pkg/storage/fs/hello/hello.go

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
// Copyright 2018-2021 CERN
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
// In applying this license, CERN does not waive the privileges and immunities
16+
// granted to it by virtue of its status as an Intergovernmental Organization
17+
// or submit itself to any jurisdiction.
18+
19+
package hello
20+
21+
import (
22+
"bytes"
23+
"context"
24+
"crypto/md5"
25+
"encoding/binary"
26+
"fmt"
27+
"io"
28+
"strings"
29+
"time"
30+
31+
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
32+
"github.com/cs3org/reva/v2/pkg/errtypes"
33+
"github.com/cs3org/reva/v2/pkg/events"
34+
"github.com/cs3org/reva/v2/pkg/storage"
35+
"github.com/cs3org/reva/v2/pkg/storage/fs/registry"
36+
"github.com/cs3org/reva/v2/pkg/utils"
37+
)
38+
39+
func init() {
40+
registry.Register("hello", New)
41+
}
42+
43+
type hellofs struct {
44+
bootTime time.Time
45+
}
46+
47+
const (
48+
storageid = "hello-storage-id"
49+
spaceid = "hello-space-id"
50+
rootid = "hello-root-id"
51+
fileid = "hello-file-id"
52+
filename = "Hello world.txt"
53+
content = "Hello world!"
54+
)
55+
56+
func (fs *hellofs) space(withRoot bool) *provider.StorageSpace {
57+
s := &provider.StorageSpace{
58+
Id: &provider.StorageSpaceId{OpaqueId: spaceid},
59+
Root: &provider.ResourceId{
60+
StorageId: storageid,
61+
SpaceId: spaceid,
62+
OpaqueId: rootid,
63+
},
64+
Quota: &provider.Quota{
65+
QuotaMaxBytes: uint64(len(content)),
66+
QuotaMaxFiles: 1,
67+
},
68+
Name: "Hello Space",
69+
SpaceType: "project",
70+
RootInfo: fs.rootInfo(),
71+
Mtime: utils.TimeToTS(fs.bootTime),
72+
}
73+
// FIXME move this to the CS3 API
74+
s.Opaque = utils.AppendPlainToOpaque(s.Opaque, "spaceAlias", "project/hello")
75+
76+
if withRoot {
77+
s.RootInfo = fs.rootInfo()
78+
}
79+
return s
80+
}
81+
82+
func (fs *hellofs) rootInfo() *provider.ResourceInfo {
83+
return &provider.ResourceInfo{
84+
Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER,
85+
Id: &provider.ResourceId{
86+
StorageId: storageid,
87+
SpaceId: spaceid,
88+
OpaqueId: rootid,
89+
},
90+
Etag: calcEtag(fs.bootTime, rootid),
91+
MimeType: "httpd/unix-directory",
92+
Mtime: utils.TimeToTS(fs.bootTime),
93+
Path: ".",
94+
PermissionSet: &provider.ResourcePermissions{
95+
GetPath: true,
96+
GetQuota: true,
97+
InitiateFileDownload: true,
98+
Stat: true,
99+
ListContainer: true,
100+
},
101+
Size: uint64(len(content)),
102+
}
103+
}
104+
105+
func (fs *hellofs) fileInfo() *provider.ResourceInfo {
106+
return &provider.ResourceInfo{
107+
Type: provider.ResourceType_RESOURCE_TYPE_FILE,
108+
Id: &provider.ResourceId{
109+
StorageId: storageid,
110+
SpaceId: spaceid,
111+
OpaqueId: fileid,
112+
},
113+
Etag: calcEtag(fs.bootTime, fileid),
114+
MimeType: "text/plain",
115+
Mtime: utils.TimeToTS(fs.bootTime),
116+
Path: ".",
117+
PermissionSet: &provider.ResourcePermissions{
118+
GetPath: true,
119+
GetQuota: true,
120+
InitiateFileDownload: true,
121+
Stat: true,
122+
ListContainer: true,
123+
},
124+
Size: uint64(len(content)),
125+
ParentId: &provider.ResourceId{
126+
StorageId: storageid,
127+
SpaceId: spaceid,
128+
OpaqueId: rootid,
129+
},
130+
Name: filename,
131+
Space: fs.space(false),
132+
}
133+
}
134+
135+
func calcEtag(t time.Time, nodeid string) string {
136+
h := md5.New()
137+
_ = binary.Write(h, binary.BigEndian, t.Unix())
138+
_ = binary.Write(h, binary.BigEndian, int64(t.Nanosecond()))
139+
_ = binary.Write(h, binary.BigEndian, []byte(nodeid))
140+
etag := fmt.Sprintf(`"%x"`, h.Sum(nil))
141+
return fmt.Sprintf("\"%s\"", strings.Trim(etag, "\""))
142+
}
143+
144+
// New returns an implementation to of the storage.FS interface that talks to
145+
// a local filesystem with user homes disabled.
146+
func New(_ map[string]interface{}, _ events.Stream) (storage.FS, error) {
147+
return &hellofs{
148+
bootTime: time.Now(),
149+
}, nil
150+
}
151+
152+
// Shutdown is called when the process is exiting to give the driver a chance to flush and close all open handles
153+
func (fs *hellofs) Shutdown(ctx context.Context) error {
154+
return nil
155+
}
156+
157+
// ListStorageSpaces lists the spaces in the storage.
158+
func (fs *hellofs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, unrestricted bool) ([]*provider.StorageSpace, error) {
159+
return []*provider.StorageSpace{fs.space(true)}, nil
160+
}
161+
162+
// GetQuota returns the quota on the referenced resource
163+
func (fs *hellofs) GetQuota(ctx context.Context, ref *provider.Reference) (uint64, uint64, uint64, error) {
164+
return uint64(len(content)), uint64(len(content)), 0, nil
165+
}
166+
167+
func (fs *hellofs) lookup(ctx context.Context, ref *provider.Reference) (*provider.ResourceInfo, error) {
168+
if ref.GetResourceId().GetStorageId() != storageid || ref.GetResourceId().GetSpaceId() != spaceid {
169+
return nil, errtypes.NotFound("")
170+
}
171+
172+
// switch root or file
173+
switch ref.GetResourceId().GetOpaqueId() {
174+
case rootid:
175+
switch ref.GetPath() {
176+
case "", ".":
177+
return fs.rootInfo(), nil
178+
case filename:
179+
return fs.fileInfo(), nil
180+
default:
181+
return nil, errtypes.NotFound("unknown filename")
182+
}
183+
case fileid:
184+
return fs.fileInfo(), nil
185+
}
186+
187+
return nil, errtypes.NotFound("unknown id")
188+
}
189+
190+
// GetPathByID returns the path pointed by the file id
191+
func (fs *hellofs) GetPathByID(ctx context.Context, resID *provider.ResourceId) (string, error) {
192+
info, err := fs.lookup(ctx, &provider.Reference{ResourceId: resID})
193+
if err != nil {
194+
return "", err
195+
}
196+
197+
return info.Path, nil
198+
}
199+
200+
// GetMD returns the resuorce info for the referenced resource
201+
func (fs *hellofs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []string, fieldMask []string) (*provider.ResourceInfo, error) {
202+
return fs.lookup(ctx, ref)
203+
}
204+
205+
// ListFolder returns the resource infos for all children of the referenced resource
206+
func (fs *hellofs) ListFolder(ctx context.Context, ref *provider.Reference, mdKeys, fieldMask []string) ([]*provider.ResourceInfo, error) {
207+
info, err := fs.lookup(ctx, ref)
208+
if err != nil {
209+
return nil, err
210+
}
211+
212+
if info.Type != provider.ResourceType_RESOURCE_TYPE_CONTAINER {
213+
return nil, errtypes.InternalError("expected a container")
214+
}
215+
if info.GetId().GetOpaqueId() != rootid {
216+
return nil, errtypes.InternalError("unknown folder")
217+
}
218+
219+
return []*provider.ResourceInfo{
220+
fs.fileInfo(),
221+
}, nil
222+
}
223+
224+
// Download returns a ReadCloser for the content of the referenced resource
225+
func (fs *hellofs) Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) {
226+
info, err := fs.lookup(ctx, ref)
227+
if err != nil {
228+
return nil, err
229+
}
230+
231+
if info.Type != provider.ResourceType_RESOURCE_TYPE_FILE {
232+
return nil, errtypes.InternalError("expected a file")
233+
}
234+
if info.GetId().GetOpaqueId() != fileid {
235+
return nil, errtypes.InternalError("unknown file")
236+
}
237+
238+
b := &bytes.Buffer{}
239+
b.WriteString(content)
240+
return io.NopCloser(b), nil
241+
}

0 commit comments

Comments
 (0)