gitlab.com/beacon-software/gadget@v0.0.0-20181217202115-54565ea1ed5e/storage/disk.go (about)

     1  package storage
     2  
     3  import (
     4  	"encoding/gob"
     5  	"fmt"
     6  	"os"
     7  	"path"
     8  	"path/filepath"
     9  	"sync"
    10  
    11  	"gitlab.com/beacon-software/gadget/crypto"
    12  	"gitlab.com/beacon-software/gadget/errors"
    13  	"gitlab.com/beacon-software/gadget/fileutil"
    14  	"gitlab.com/beacon-software/gadget/stringutil"
    15  )
    16  
    17  const (
    18  	// KeySuffix is the suffix appended to key names on disk.
    19  	KeySuffix = ".gob"
    20  )
    21  
    22  // KeyFormat is used for creating the filename of the keys on disk
    23  var KeyFormat = fmt.Sprintf("%%s%s", KeySuffix)
    24  
    25  type disk struct {
    26  	RootPath string
    27  	Mode     os.FileMode
    28  	mutex    sync.RWMutex
    29  }
    30  
    31  // NewDisk storage provider using the passed root path and creating files with the specified mode.
    32  func NewDisk(rootPath string, mode os.FileMode) (Provider, error) {
    33  	provider := &disk{RootPath: rootPath, Mode: mode}
    34  	if !path.IsAbs(provider.RootPath) {
    35  		return provider, fmt.Errorf("root path must be an absolute path, received '%s'", provider.RootPath)
    36  	}
    37  	_, err := fileutil.EnsureDir(provider.RootPath, provider.Mode)
    38  	return provider, err
    39  }
    40  
    41  func (provider *disk) Shard(name string) (Provider, error) {
    42  	return NewDisk(path.Join(provider.RootPath, name), provider.Mode)
    43  }
    44  
    45  func (provider *disk) getFullPath(key string) (string, error) {
    46  	if stringutil.IsWhiteSpace(key) {
    47  		return "", fmt.Errorf("invalid key (empty)")
    48  	}
    49  	filename := fmt.Sprintf(KeyFormat, keyToFilename(key))
    50  	return path.Join(provider.RootPath, filename), nil
    51  }
    52  
    53  // Read into the target for the passed key.
    54  func (provider *disk) Read(key string, target interface{}) error {
    55  	// Put existence check in the persistence layer
    56  	filepath, err := provider.getFullPath(key)
    57  	if nil != err {
    58  		return err
    59  	}
    60  	if !fileutil.FileExists(filepath) {
    61  		return errors.New(NoEntryForKey)
    62  	}
    63  	provider.mutex.RLock()
    64  	defer provider.mutex.RUnlock()
    65  	var file *os.File
    66  	if file, err = os.Open(filepath); nil != err {
    67  		return err
    68  	}
    69  	defer file.Close()
    70  	decoder := gob.NewDecoder(file)
    71  	return decoder.Decode(target)
    72  }
    73  
    74  func keyToFilename(key string) string {
    75  	// we don't need 512 byte filenames
    76  	return crypto.Hash(key, "")[:32]
    77  }
    78  
    79  // Write the passed target for the specified key
    80  func (provider *disk) Write(key string, target interface{}) error {
    81  	provider.mutex.Lock()
    82  	defer provider.mutex.Unlock()
    83  	// get the full path
    84  	filePath, err := provider.getFullPath(key)
    85  	if nil != err {
    86  		return err
    87  	}
    88  
    89  	fileutil.EnsureDir(filepath.Dir(filePath), provider.Mode)
    90  
    91  	// create the file, this will overwrite if it already exists.
    92  	w, err := os.Create(filePath)
    93  	if nil != err {
    94  		return err
    95  	}
    96  	defer w.Close()
    97  	encoder := gob.NewEncoder(w)
    98  	return encoder.Encode(target)
    99  }
   100  
   101  // Exists verifies that a record exists for the passed key.
   102  func (provider *disk) Exists(key string) bool {
   103  	provider.mutex.RLock()
   104  	defer provider.mutex.RUnlock()
   105  	filepath, _ := provider.getFullPath(key)
   106  	return fileutil.FileExists(filepath)
   107  }
   108  
   109  // Delete removes the partition from disk
   110  func (provider *disk) Delete(key string) error {
   111  	provider.mutex.Lock()
   112  	defer provider.mutex.Unlock()
   113  	filepath, err := provider.getFullPath(key)
   114  	if nil == err {
   115  		err = os.Remove(filepath)
   116  	}
   117  	return err
   118  }