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