github.com/cs3org/reva/v2@v2.27.7/pkg/storage/fs/hello/hello.go (about) 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/rs/zerolog" 33 34 "github.com/cs3org/reva/v2/pkg/errtypes" 35 "github.com/cs3org/reva/v2/pkg/events" 36 "github.com/cs3org/reva/v2/pkg/storage" 37 "github.com/cs3org/reva/v2/pkg/storage/fs/registry" 38 "github.com/cs3org/reva/v2/pkg/utils" 39 ) 40 41 func init() { 42 registry.Register("hello", New) 43 } 44 45 type hellofs struct { 46 bootTime time.Time 47 } 48 49 const ( 50 storageid = "hello-storage-id" 51 spaceid = "hello-space-id" 52 rootid = "hello-root-id" 53 fileid = "hello-file-id" 54 filename = "Hello world.txt" 55 content = "Hello world!" 56 ) 57 58 func (fs *hellofs) space(withRoot bool) *provider.StorageSpace { 59 s := &provider.StorageSpace{ 60 Id: &provider.StorageSpaceId{OpaqueId: spaceid}, 61 Root: &provider.ResourceId{ 62 StorageId: storageid, 63 SpaceId: spaceid, 64 OpaqueId: rootid, 65 }, 66 Quota: &provider.Quota{ 67 QuotaMaxBytes: uint64(len(content)), 68 QuotaMaxFiles: 1, 69 }, 70 Name: "Hello Space", 71 SpaceType: "project", 72 RootInfo: fs.rootInfo(), 73 Mtime: utils.TimeToTS(fs.bootTime), 74 } 75 // FIXME move this to the CS3 API 76 s.Opaque = utils.AppendPlainToOpaque(s.Opaque, "spaceAlias", "project/hello") 77 78 if withRoot { 79 s.RootInfo = fs.rootInfo() 80 } 81 return s 82 } 83 84 func (fs *hellofs) rootInfo() *provider.ResourceInfo { 85 return &provider.ResourceInfo{ 86 Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER, 87 Id: &provider.ResourceId{ 88 StorageId: storageid, 89 SpaceId: spaceid, 90 OpaqueId: rootid, 91 }, 92 Etag: calcEtag(fs.bootTime, rootid), 93 MimeType: "httpd/unix-directory", 94 Mtime: utils.TimeToTS(fs.bootTime), 95 Path: ".", 96 PermissionSet: &provider.ResourcePermissions{ 97 GetPath: true, 98 GetQuota: true, 99 InitiateFileDownload: true, 100 Stat: true, 101 ListContainer: true, 102 }, 103 Size: uint64(len(content)), 104 } 105 } 106 107 func (fs *hellofs) fileInfo() *provider.ResourceInfo { 108 return &provider.ResourceInfo{ 109 Type: provider.ResourceType_RESOURCE_TYPE_FILE, 110 Id: &provider.ResourceId{ 111 StorageId: storageid, 112 SpaceId: spaceid, 113 OpaqueId: fileid, 114 }, 115 Etag: calcEtag(fs.bootTime, fileid), 116 MimeType: "text/plain", 117 Mtime: utils.TimeToTS(fs.bootTime), 118 Path: ".", 119 PermissionSet: &provider.ResourcePermissions{ 120 GetPath: true, 121 GetQuota: true, 122 InitiateFileDownload: true, 123 Stat: true, 124 ListContainer: true, 125 }, 126 Size: uint64(len(content)), 127 ParentId: &provider.ResourceId{ 128 StorageId: storageid, 129 SpaceId: spaceid, 130 OpaqueId: rootid, 131 }, 132 Name: filename, 133 Space: fs.space(false), 134 } 135 } 136 137 func calcEtag(t time.Time, nodeid string) string { 138 h := md5.New() 139 _ = binary.Write(h, binary.BigEndian, t.Unix()) 140 _ = binary.Write(h, binary.BigEndian, int64(t.Nanosecond())) 141 _ = binary.Write(h, binary.BigEndian, []byte(nodeid)) 142 etag := fmt.Sprintf(`"%x"`, h.Sum(nil)) 143 return fmt.Sprintf("\"%s\"", strings.Trim(etag, "\"")) 144 } 145 146 // New returns an implementation to of the storage.FS interface that talks to 147 // a local filesystem with user homes disabled. 148 func New(_ map[string]interface{}, _ events.Stream, _ *zerolog.Logger) (storage.FS, error) { 149 return &hellofs{ 150 bootTime: time.Now(), 151 }, nil 152 } 153 154 // Shutdown is called when the process is exiting to give the driver a chance to flush and close all open handles 155 func (fs *hellofs) Shutdown(ctx context.Context) error { 156 return nil 157 } 158 159 // ListStorageSpaces lists the spaces in the storage. 160 func (fs *hellofs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, unrestricted bool) ([]*provider.StorageSpace, error) { 161 return []*provider.StorageSpace{fs.space(true)}, nil 162 } 163 164 // GetQuota returns the quota on the referenced resource 165 func (fs *hellofs) GetQuota(ctx context.Context, ref *provider.Reference) (uint64, uint64, uint64, error) { 166 return uint64(len(content)), uint64(len(content)), 0, nil 167 } 168 169 func (fs *hellofs) lookup(ctx context.Context, ref *provider.Reference) (*provider.ResourceInfo, error) { 170 if ref.GetResourceId().GetStorageId() != storageid || ref.GetResourceId().GetSpaceId() != spaceid { 171 return nil, errtypes.NotFound("") 172 } 173 174 // switch root or file 175 switch ref.GetResourceId().GetOpaqueId() { 176 case rootid: 177 switch ref.GetPath() { 178 case "", ".": 179 return fs.rootInfo(), nil 180 case filename: 181 return fs.fileInfo(), nil 182 default: 183 return nil, errtypes.NotFound("unknown filename") 184 } 185 case fileid: 186 return fs.fileInfo(), nil 187 } 188 189 return nil, errtypes.NotFound("unknown id") 190 } 191 192 // GetPathByID returns the path pointed by the file id 193 func (fs *hellofs) GetPathByID(ctx context.Context, resID *provider.ResourceId) (string, error) { 194 info, err := fs.lookup(ctx, &provider.Reference{ResourceId: resID}) 195 if err != nil { 196 return "", err 197 } 198 199 return info.Path, nil 200 } 201 202 // GetMD returns the resuorce info for the referenced resource 203 func (fs *hellofs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []string, fieldMask []string) (*provider.ResourceInfo, error) { 204 return fs.lookup(ctx, ref) 205 } 206 207 // ListFolder returns the resource infos for all children of the referenced resource 208 func (fs *hellofs) ListFolder(ctx context.Context, ref *provider.Reference, mdKeys, fieldMask []string) ([]*provider.ResourceInfo, error) { 209 info, err := fs.lookup(ctx, ref) 210 if err != nil { 211 return nil, err 212 } 213 214 if info.Type != provider.ResourceType_RESOURCE_TYPE_CONTAINER { 215 return nil, errtypes.InternalError("expected a container") 216 } 217 if info.GetId().GetOpaqueId() != rootid { 218 return nil, errtypes.InternalError("unknown folder") 219 } 220 221 return []*provider.ResourceInfo{ 222 fs.fileInfo(), 223 }, nil 224 } 225 226 // Download returns a ReadCloser for the content of the referenced resource 227 func (fs *hellofs) Download(ctx context.Context, ref *provider.Reference, openReaderFunc func(md *provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) { 228 info, err := fs.lookup(ctx, ref) 229 if err != nil { 230 return nil, nil, err 231 } 232 if info.Type != provider.ResourceType_RESOURCE_TYPE_FILE { 233 return nil, nil, errtypes.InternalError("expected a file") 234 } 235 if info.GetId().GetOpaqueId() != fileid { 236 return nil, nil, errtypes.InternalError("unknown file") 237 } 238 239 if !openReaderFunc(info) { 240 return info, nil, nil 241 } 242 243 b := &bytes.Buffer{} 244 b.WriteString(content) 245 return info, io.NopCloser(b), nil 246 }