github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/resource/context/internal/resourcedir.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package internal 5 6 // TODO(ericsnow) Move this file elsewhere? 7 // (e.g. top-level resource pkg, charm/resource) 8 9 import ( 10 "io" 11 12 "github.com/juju/errors" 13 charmresource "gopkg.in/juju/charm.v6-unstable/resource" 14 ) 15 16 // DirectorySpec identifies information for a resource directory. 17 type DirectorySpec struct { 18 // Name is the resource name. 19 Name string 20 21 // Dirname is the path to the resource directory. 22 Dirname string 23 24 // Deps is the external dependencies of DirectorySpec. 25 Deps DirectorySpecDeps 26 } 27 28 // NewDirectorySpec returns a new directory spec for the given info. 29 func NewDirectorySpec(dataDir, name string, deps DirectorySpecDeps) *DirectorySpec { 30 dirname := deps.Join(dataDir, name) 31 32 spec := &DirectorySpec{ 33 Name: name, 34 Dirname: dirname, 35 36 Deps: deps, 37 } 38 return spec 39 } 40 41 // Resolve returns the fully resolved file path, relative to the directory. 42 func (spec DirectorySpec) Resolve(path ...string) string { 43 return spec.Deps.Join(append([]string{spec.Dirname}, path...)...) 44 } 45 46 // TODO(ericsnow) Make IsUpToDate a stand-alone function? 47 48 // IsUpToDate determines whether or not the content matches the resource directory. 49 func (spec DirectorySpec) IsUpToDate(content Content) (bool, error) { 50 filename := spec.Resolve(spec.Name) 51 ok, err := spec.Deps.FingerprintMatches(filename, content.Fingerprint) 52 return ok, errors.Trace(err) 53 } 54 55 // Initialize preps the spec'ed directory and returns it. 56 func (spec DirectorySpec) Initialize() (*Directory, error) { 57 if err := spec.Deps.MkdirAll(spec.Dirname); err != nil { 58 return nil, errors.Annotate(err, "could not create resource dir") 59 } 60 61 return NewDirectory(&spec, spec.Deps), nil 62 } 63 64 // DirectorySpecDeps exposes the external depenedencies of DirectorySpec. 65 type DirectorySpecDeps interface { 66 DirectoryDeps 67 68 // FingerprintMatches determines whether or not the identified file 69 // exists and has the provided fingerprint. 70 FingerprintMatches(filename string, fp charmresource.Fingerprint) (bool, error) 71 72 // Join exposes the functionality of filepath.Join(). 73 Join(...string) string 74 75 // MkdirAll exposes the functionality of os.MkdirAll(). 76 MkdirAll(string) error 77 } 78 79 // TempDirectorySpec represents a resource directory placed under a temporary data dir. 80 type TempDirectorySpec struct { 81 *DirectorySpec 82 83 // CleanUp cleans up the temp directory in which the resource 84 // directory is placed. 85 CleanUp func() error 86 } 87 88 // NewTempDirectorySpec creates a new temp directory spec 89 // for the given resource. 90 func NewTempDirectorySpec(name string, deps TempDirDeps) (*TempDirectorySpec, error) { 91 tempDir, err := deps.NewTempDir() 92 if err != nil { 93 return nil, errors.Trace(err) 94 } 95 96 spec := &TempDirectorySpec{ 97 DirectorySpec: NewDirectorySpec(tempDir, name, deps), 98 CleanUp: func() error { 99 return deps.RemoveDir(tempDir) 100 }, 101 } 102 return spec, nil 103 } 104 105 // TempDirDeps exposes the external functionality needed by 106 // NewTempDirectorySpec(). 107 type TempDirDeps interface { 108 DirectorySpecDeps 109 110 // NewTempDir returns the path to a new temporary directory. 111 NewTempDir() (string, error) 112 113 // RemoveDir deletes the specified directory. 114 RemoveDir(string) error 115 } 116 117 // Close implements io.Closer. 118 func (spec TempDirectorySpec) Close() error { 119 if err := spec.CleanUp(); err != nil { 120 return errors.Annotate(err, "could not clean up temp dir") 121 } 122 return nil 123 } 124 125 // Directory represents a resource directory. 126 type Directory struct { 127 *DirectorySpec 128 129 // Deps holds the external dependencies of the directory. 130 Deps DirectoryDeps 131 } 132 133 // NewDirectory returns a new directory for the provided spec. 134 func NewDirectory(spec *DirectorySpec, deps DirectoryDeps) *Directory { 135 dir := &Directory{ 136 DirectorySpec: spec, 137 Deps: deps, 138 } 139 return dir 140 } 141 142 // Write writes all relevant files from the given source 143 // to the directory. 144 func (dir *Directory) Write(opened ContentSource) error { 145 // TODO(ericsnow) Also write the info file... 146 147 relPath := opened.Info().Path 148 if err := dir.WriteContent(relPath, opened.Content()); err != nil { 149 return errors.Trace(err) 150 } 151 152 return nil 153 } 154 155 // WriteContent writes the resource file to the given path 156 // within the directory. 157 func (dir *Directory) WriteContent(relPath string, content Content) error { 158 if len(relPath) == 0 { 159 // TODO(ericsnow) Use rd.readInfo().Path, like openResource() does? 160 return errors.NotImplementedf("") 161 } 162 filename := dir.Resolve(relPath) 163 164 target, err := dir.Deps.CreateWriter(filename) 165 if err != nil { 166 return errors.Annotate(err, "could not create new file for resource") 167 } 168 defer dir.Deps.CloseAndLog(target, filename) 169 170 if err := dir.Deps.WriteContent(target, content); err != nil { 171 return errors.Trace(err) 172 } 173 174 return nil 175 } 176 177 // DirectoryDeps exposes the external functionality needed by Directory. 178 type DirectoryDeps interface { 179 // CreateWriter creates a new writer to which the resource file 180 // will be written. 181 CreateWriter(string) (io.WriteCloser, error) 182 183 // CloseAndLog closes the closer and logs any error. 184 CloseAndLog(io.Closer, string) 185 186 // WriteContent writes the content to the directory. 187 WriteContent(io.Writer, Content) error 188 }