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  }