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  }