github.com/hoffie/larasync@v0.0.0-20151025221940-0384d2bddcef/repository/content/fileStorage.go (about) 1 package content 2 3 import ( 4 "errors" 5 "io" 6 "os" 7 "path" 8 "path/filepath" 9 "strings" 10 11 "github.com/hoffie/larasync/helpers/atomic" 12 ) 13 14 const ( 15 // default permissions 16 defaultFilePerms = 0600 17 defaultDirPerms = 0700 18 ) 19 20 // ErrInvalidPath is returned if storage at a path not rooted at the FileStorage's 21 // root path is attempted. 22 var ErrInvalidPath = errors.New("invalid path") 23 24 // FileStorage is the basic implementation of the Storage 25 // implementation which stores the data into the file system. 26 type FileStorage struct { 27 path string 28 } 29 30 // NewFileStorage generates a file content storage with the 31 // given path. 32 func NewFileStorage(path string) *FileStorage { 33 return &FileStorage{ 34 path: path, 35 } 36 } 37 38 // CreateDir ensures that the file blob storage directory exists. 39 func (f *FileStorage) CreateDir() error { 40 err := os.Mkdir(f.path, defaultDirPerms) 41 42 if err != nil && !os.IsExist(err) { 43 return err 44 } 45 return nil 46 } 47 48 // storagePathFor returns the storage path for the data entry. 49 func (f *FileStorage) storagePathFor(contentID string) (string, error) { 50 p := path.Join(f.path, contentID) 51 p = filepath.Clean(p) 52 root := f.path 53 /*if len(root) > 1 && root[len(root)-1] != filepath.Separator { 54 root += filepath.Separator 55 }*/ 56 if !strings.HasPrefix(p, root) { 57 return "", ErrInvalidPath 58 } 59 return p, nil 60 } 61 62 // Get returns the file handle for the given contentID. 63 // If there is no data stored for the Id it should return a 64 // os.ErrNotExists error. 65 func (f *FileStorage) Get(contentID string) (io.ReadCloser, error) { 66 if !f.Exists(contentID) { 67 return nil, os.ErrNotExist 68 } 69 // FIXME TOCTU race 70 p, err := f.storagePathFor(contentID) 71 if err != nil { 72 return nil, err 73 } 74 return os.Open(p) 75 } 76 77 // Set sets the data of the given contentID in the blob storage. 78 func (f *FileStorage) Set(contentID string, reader io.Reader) error { 79 blobStoragePath, err := f.storagePathFor(contentID) 80 if err != nil { 81 return err 82 } 83 84 writer, err := atomic.NewStandardWriter(blobStoragePath, defaultFilePerms) 85 if err != nil { 86 return err 87 } 88 89 _, err = io.Copy(writer, reader) 90 if err != nil { 91 writer.Abort() 92 writer.Close() 93 return err 94 } 95 96 err = writer.Close() 97 if err != nil { 98 return err 99 } 100 101 return nil 102 } 103 104 // Exists checks if the given entry is stored in the database. 105 func (f *FileStorage) Exists(contentID string) bool { 106 p, err := f.storagePathFor(contentID) 107 if err != nil { 108 // FIXME maybe return error instead? 109 return false 110 } 111 _, err = os.Stat(p) 112 if err != nil { 113 return !os.IsNotExist(err) 114 } 115 return true 116 } 117 118 // Delete removes the data with the given contentID from the store. 119 func (f *FileStorage) Delete(contentID string) error { 120 p, err := f.storagePathFor(contentID) 121 if err != nil { 122 return err 123 } 124 return os.Remove(p) 125 }