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  }