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 }