launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/environs/filestorage/filestorage.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package filestorage
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"os"
    11  	"path/filepath"
    12  	"sort"
    13  	"strings"
    14  
    15  	"launchpad.net/juju-core/environs/storage"
    16  	coreerrors "launchpad.net/juju-core/errors"
    17  	"launchpad.net/juju-core/utils"
    18  )
    19  
    20  // fileStorageReader implements StorageReader backed
    21  // by the local filesystem.
    22  type fileStorageReader struct {
    23  	path string
    24  }
    25  
    26  // NewFileStorageReader returns a new storage reader for
    27  // a directory inside the local file system.
    28  func NewFileStorageReader(path string) (reader storage.StorageReader, err error) {
    29  	var p string
    30  	if p, err = utils.NormalizePath(path); err != nil {
    31  		return nil, err
    32  	}
    33  	if p, err = filepath.Abs(p); err != nil {
    34  		return nil, err
    35  	}
    36  	fi, err := os.Stat(p)
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  	if !fi.Mode().IsDir() {
    41  		return nil, fmt.Errorf("specified source path is not a directory: %s", path)
    42  	}
    43  	return &fileStorageReader{p}, nil
    44  }
    45  
    46  func (f *fileStorageReader) fullPath(name string) string {
    47  	return filepath.Join(f.path, name)
    48  }
    49  
    50  // Get implements storage.StorageReader.Get.
    51  func (f *fileStorageReader) Get(name string) (io.ReadCloser, error) {
    52  	filename := f.fullPath(name)
    53  	fi, err := os.Stat(filename)
    54  	if err != nil {
    55  		if os.IsNotExist(err) {
    56  			err = coreerrors.NewNotFoundError(err, "")
    57  		}
    58  		return nil, err
    59  	} else if fi.IsDir() {
    60  		return nil, coreerrors.NotFoundf("no such file with name %q", name)
    61  	}
    62  	file, err := os.Open(filename)
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  	return file, nil
    67  }
    68  
    69  // List implements storage.StorageReader.List.
    70  func (f *fileStorageReader) List(prefix string) ([]string, error) {
    71  	prefix = filepath.Join(f.path, prefix)
    72  	dir := filepath.Dir(prefix)
    73  	var names []string
    74  	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
    75  		if err != nil {
    76  			return err
    77  		}
    78  		if !info.IsDir() && strings.HasPrefix(path, prefix) {
    79  			names = append(names, path[len(f.path)+1:])
    80  		}
    81  		return nil
    82  	})
    83  	if err != nil && !os.IsNotExist(err) {
    84  		return nil, err
    85  	}
    86  	sort.Strings(names)
    87  	return names, nil
    88  }
    89  
    90  // URL implements storage.StorageReader.URL.
    91  func (f *fileStorageReader) URL(name string) (string, error) {
    92  	return "file://" + filepath.Join(f.path, name), nil
    93  }
    94  
    95  // ConsistencyStrategy implements storage.StorageReader.ConsistencyStrategy.
    96  func (f *fileStorageReader) DefaultConsistencyStrategy() utils.AttemptStrategy {
    97  	return utils.AttemptStrategy{}
    98  }
    99  
   100  // ShouldRetry is specified in the StorageReader interface.
   101  func (f *fileStorageReader) ShouldRetry(err error) bool {
   102  	return false
   103  }
   104  
   105  type fileStorageWriter struct {
   106  	fileStorageReader
   107  	tmpdir string
   108  }
   109  
   110  // UseDefaultTmpDir may be passed into NewFileStorageWriter
   111  // for the tmpdir argument, to signify that the default
   112  // value should be used. See NewFileStorageWriter for more.
   113  const UseDefaultTmpDir = ""
   114  
   115  // NewFileStorageWriter returns a new read/write storag for
   116  // a directory inside the local file system.
   117  //
   118  // A temporary directory may be specified, in which files will be written
   119  // to before moving to the final destination. If specified, the temporary
   120  // directory should be on the same filesystem as the storage directory
   121  // to ensure atomicity. If tmpdir == UseDefaultTmpDir (""), then path+".tmp"
   122  // will be used.
   123  //
   124  // If tmpdir == UseDefaultTmpDir, it will be created when Put is invoked,
   125  // and will be removed afterwards. If tmpdir != UseDefaultTmpDir, it must
   126  // already exist, and will never be removed.
   127  func NewFileStorageWriter(path, tmpdir string) (storage.Storage, error) {
   128  	reader, err := NewFileStorageReader(path)
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  	return &fileStorageWriter{*reader.(*fileStorageReader), tmpdir}, nil
   133  }
   134  
   135  func (f *fileStorageWriter) Put(name string, r io.Reader, length int64) error {
   136  	fullpath := f.fullPath(name)
   137  	dir := filepath.Dir(fullpath)
   138  	tmpdir := f.tmpdir
   139  	if tmpdir == UseDefaultTmpDir {
   140  		tmpdir = f.path + ".tmp"
   141  		if err := os.MkdirAll(tmpdir, 0755); err != nil && !os.IsExist(err) {
   142  			return err
   143  		}
   144  		defer os.Remove(tmpdir)
   145  	}
   146  	if err := os.MkdirAll(dir, 0755); err != nil && !os.IsExist(err) {
   147  		return err
   148  	}
   149  	// Write to a temporary file first, and then move (atomically).
   150  	file, err := ioutil.TempFile(tmpdir, "juju-filestorage-")
   151  	if err != nil {
   152  		return err
   153  	}
   154  	_, err = io.CopyN(file, r, length)
   155  	file.Close()
   156  	if err != nil {
   157  		os.Remove(file.Name())
   158  		return err
   159  	}
   160  	return utils.ReplaceFile(file.Name(), fullpath)
   161  }
   162  
   163  func (f *fileStorageWriter) Remove(name string) error {
   164  	fullpath := f.fullPath(name)
   165  	err := os.Remove(fullpath)
   166  	if os.IsNotExist(err) {
   167  		err = nil
   168  	}
   169  	return err
   170  }
   171  
   172  func (f *fileStorageWriter) RemoveAll() error {
   173  	return storage.RemoveAll(f)
   174  }