github.com/cs3org/reva/v2@v2.27.7/pkg/storage/fs/ocis/blobstore/blobstore.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 blobstore 20 21 import ( 22 "bufio" 23 "fmt" 24 "io" 25 "os" 26 "path/filepath" 27 "strings" 28 29 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup" 30 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" 31 "github.com/cs3org/reva/v2/pkg/utils" 32 "github.com/pkg/errors" 33 ) 34 35 // ErrBlobIDEmpty is returned when the BlobID is empty 36 var ErrBlobIDEmpty = fmt.Errorf("blobstore: BlobID is empty") 37 38 // Blobstore provides an interface to an filesystem based blobstore 39 type Blobstore struct { 40 root string 41 } 42 43 // New returns a new Blobstore 44 func New(root string) (*Blobstore, error) { 45 err := os.MkdirAll(root, 0700) 46 if err != nil { 47 return nil, err 48 } 49 50 return &Blobstore{ 51 root: root, 52 }, nil 53 } 54 55 // Upload stores some data in the blobstore under the given key 56 func (bs *Blobstore) Upload(node *node.Node, source string) error { 57 if node.BlobID == "" { 58 return ErrBlobIDEmpty 59 } 60 61 dest := bs.Path(node) 62 63 // ensure parent path exists 64 if err := os.MkdirAll(filepath.Dir(dest), 0700); err != nil { 65 return errors.Wrap(err, "Decomposedfs: oCIS blobstore: error creating parent folders for blob") 66 } 67 68 if err := os.Rename(source, dest); err == nil { 69 return nil 70 } 71 72 // Rename failed, file needs to be copied. 73 file, err := os.Open(source) 74 if err != nil { 75 return errors.Wrap(err, "Decomposedfs: oCIS blobstore: Can not open source file to upload") 76 } 77 defer file.Close() 78 79 f, err := os.OpenFile(dest, os.O_CREATE|os.O_WRONLY, 0700) 80 if err != nil { 81 return errors.Wrapf(err, "could not open blob '%s' for writing", dest) 82 } 83 84 w := bufio.NewWriter(f) 85 _, err = w.ReadFrom(file) 86 if err != nil { 87 return errors.Wrapf(err, "could not write blob '%s'", dest) 88 } 89 90 return w.Flush() 91 } 92 93 // Download retrieves a blob from the blobstore for reading 94 func (bs *Blobstore) Download(node *node.Node) (io.ReadCloser, error) { 95 if node.BlobID == "" { 96 return nil, ErrBlobIDEmpty 97 } 98 99 dest := bs.Path(node) 100 file, err := os.Open(dest) 101 if err != nil { 102 return nil, errors.Wrapf(err, "could not read blob '%s'", dest) 103 } 104 return file, nil 105 } 106 107 // Delete deletes a blob from the blobstore 108 func (bs *Blobstore) Delete(node *node.Node) error { 109 if node.BlobID == "" { 110 return ErrBlobIDEmpty 111 } 112 dest := bs.Path(node) 113 if err := utils.RemoveItem(dest); err != nil { 114 return errors.Wrapf(err, "could not delete blob '%s'", dest) 115 } 116 return nil 117 } 118 119 // List lists all blobs in the Blobstore 120 func (bs *Blobstore) List() ([]*node.Node, error) { 121 dirs, err := filepath.Glob(filepath.Join(bs.root, "spaces", "*", "*", "blobs", "*", "*", "*", "*", "*")) 122 if err != nil { 123 return nil, err 124 } 125 blobids := make([]*node.Node, 0, len(dirs)) 126 for _, d := range dirs { 127 _, s, _ := strings.Cut(d, "spaces") 128 spaceraw, blobraw, _ := strings.Cut(s, "blobs") 129 blobids = append(blobids, &node.Node{ 130 SpaceID: strings.ReplaceAll(spaceraw, "/", ""), 131 BlobID: strings.ReplaceAll(blobraw, "/", ""), 132 }) 133 } 134 return blobids, nil 135 } 136 137 func (bs *Blobstore) Path(node *node.Node) string { 138 return filepath.Join( 139 bs.root, 140 filepath.Clean(filepath.Join( 141 "/", "spaces", lookup.Pathify(node.SpaceID, 1, 2), "blobs", lookup.Pathify(node.BlobID, 4, 2)), 142 ), 143 ) 144 }