github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/store/file/blob.go (about) 1 // Licensed under the Apache License, Version 2.0 (the "License"); 2 // you may not use this file except in compliance with the License. 3 // You may obtain a copy of the License at 4 // 5 // https://www.apache.org/licenses/LICENSE-2.0 6 // 7 // Unless required by applicable law or agreed to in writing, software 8 // distributed under the License is distributed on an "AS IS" BASIS, 9 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 // See the License for the specific language governing permissions and 11 // limitations under the License. 12 // 13 // Original source: github.com/micro/go-micro/v3/store/file/blob.go 14 15 package file 16 17 import ( 18 "bytes" 19 "io" 20 "io/ioutil" 21 "os" 22 "path/filepath" 23 "strings" 24 "time" 25 26 "github.com/tickoalcantara12/micro/v3/service/store" 27 bolt "go.etcd.io/bbolt" 28 ) 29 30 // NewBlobStore returns a blob file store 31 func NewBlobStore(opts ...store.StoreOption) (store.BlobStore, error) { 32 // parse the options 33 var options store.StoreOptions 34 for _, o := range opts { 35 o(&options) 36 } 37 38 var dir string 39 if options.Context != nil { 40 if d, ok := options.Context.Value(dirKey{}).(string); ok { 41 dir = d 42 } 43 } 44 if len(dir) == 0 { 45 dir = DefaultDir 46 } 47 48 // ensure the parent directory exists 49 os.MkdirAll(dir, 0700) 50 51 return &blobStore{dir}, nil 52 } 53 54 type blobStore struct { 55 dir string 56 } 57 58 func (b *blobStore) db() (*bolt.DB, error) { 59 dbPath := filepath.Join(b.dir, "blob.db") 60 return bolt.Open(dbPath, 0700, &bolt.Options{Timeout: 5 * time.Second}) 61 } 62 63 func (b *blobStore) Read(key string, opts ...store.BlobOption) (io.Reader, error) { 64 // validate the key 65 if len(key) == 0 { 66 return nil, store.ErrMissingKey 67 } 68 69 // parse the options 70 var options store.BlobOptions 71 for _, o := range opts { 72 o(&options) 73 } 74 if len(options.Namespace) == 0 { 75 options.Namespace = "micro" 76 } 77 78 // open a connection to the database 79 db, err := b.db() 80 if err != nil { 81 return nil, err 82 } 83 defer db.Close() 84 85 // execute the transaction 86 var value []byte 87 readValue := func(tx *bolt.Tx) error { 88 // check for the namespaces bucket 89 bucket := tx.Bucket([]byte(options.Namespace)) 90 if bucket == nil { 91 return store.ErrNotFound 92 } 93 94 // look for the blob within the bucket 95 res := bucket.Get([]byte(key)) 96 if res == nil { 97 return store.ErrNotFound 98 } 99 100 // the res object is only valid for the duration of the blot transaction, see: 101 // https://github.com/golang/go/issues/33047 102 value = make([]byte, len(res)) 103 copy(value, res) 104 105 return nil 106 } 107 if err := db.View(readValue); err != nil { 108 return nil, err 109 } 110 111 // return the blob 112 return bytes.NewBuffer(value), nil 113 } 114 115 func (b *blobStore) Write(key string, blob io.Reader, opts ...store.BlobOption) error { 116 // validate the key 117 if len(key) == 0 { 118 return store.ErrMissingKey 119 } 120 121 // parse the options 122 var options store.BlobOptions 123 for _, o := range opts { 124 o(&options) 125 } 126 if len(options.Namespace) == 0 { 127 options.Namespace = "micro" 128 } 129 130 // open a connection to the database 131 db, err := b.db() 132 if err != nil { 133 return err 134 } 135 defer db.Close() 136 137 // execute the transaction 138 return db.Update(func(tx *bolt.Tx) error { 139 // create the bucket 140 bucket, err := tx.CreateBucketIfNotExists([]byte(options.Namespace)) 141 if err != nil { 142 return err 143 } 144 145 // write to the bucket 146 value, err := ioutil.ReadAll(blob) 147 if err != nil { 148 return err 149 } 150 151 return bucket.Put([]byte(key), value) 152 }) 153 } 154 155 func (b *blobStore) Delete(key string, opts ...store.BlobOption) error { 156 // validate the key 157 if len(key) == 0 { 158 return store.ErrMissingKey 159 } 160 161 // parse the options 162 var options store.BlobOptions 163 for _, o := range opts { 164 o(&options) 165 } 166 if len(options.Namespace) == 0 { 167 options.Namespace = "micro" 168 } 169 170 // open a connection to the database 171 db, err := b.db() 172 if err != nil { 173 return err 174 } 175 defer db.Close() 176 177 // execute the transaction 178 return db.Update(func(tx *bolt.Tx) error { 179 // check for the namespaces bucket 180 bucket := tx.Bucket([]byte(options.Namespace)) 181 if bucket == nil { 182 return nil 183 } 184 185 return bucket.Delete([]byte(key)) 186 }) 187 } 188 189 func (b *blobStore) List(opts ...store.BlobListOption) ([]string, error) { 190 var options store.BlobListOptions 191 for _, o := range opts { 192 o(&options) 193 } 194 if len(options.Namespace) == 0 { 195 options.Namespace = "micro" 196 } 197 // open a connection to the database 198 db, err := b.db() 199 if err != nil { 200 return nil, err 201 } 202 defer db.Close() 203 204 // execute the transaction 205 keys := []string{} 206 readValue := func(tx *bolt.Tx) error { 207 // check for the namespaces bucket 208 bucket := tx.Bucket([]byte(options.Namespace)) 209 if bucket == nil { 210 return store.ErrNotFound 211 } 212 c := bucket.Cursor() 213 for { 214 k, _ := c.Next() 215 if k == nil { 216 break 217 } 218 kcopy := make([]byte, len(k)) 219 copy(kcopy, k) 220 kstring := string(kcopy) 221 if len(options.Prefix) == 0 || strings.HasPrefix(kstring, options.Prefix) { 222 keys = append(keys, kstring) 223 } 224 225 } 226 return nil 227 } 228 if err := db.View(readValue); err != nil { 229 return nil, err 230 } 231 232 // return the keys 233 return keys, nil 234 }