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