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 }