27
26
// the download is invalid then the func must return errors.NotValid.
28
27
// If no func is provided then no verification happens.
29
28
Verify func(*os.File) error
30
// Abort is a channel that will cancel the download when it is closed.
32
34
// Status represents the status of a completed download.
33
35
type Status struct {
34
// File holds the downloaded data on success.
36
// Filename is the name of the file which holds the downloaded
37
40
// Err describes any error encountered while downloading.
44
// StartDownload starts a new download as specified by `req` using
45
// `openBlob` to actually pull the remote data.
46
func StartDownload(req Request, openBlob func(*url.URL) (io.ReadCloser, error)) *Download {
48
openBlob = NewHTTPBlobOpener(utils.NoVerifySSLHostnames)
51
done: make(chan Status, 1),
41
58
// Download can download a file from the network.
42
59
type Download struct {
45
61
openBlob func(*url.URL) (io.ReadCloser, error)
48
// StartDownload returns a new Download instance based on the provided
49
// request. openBlob is used to gain access to the blob, whether through
50
// an HTTP request or some other means.
51
func StartDownload(req Request, openBlob func(*url.URL) (io.ReadCloser, error)) *Download {
52
dl := newDownload(openBlob)
57
func newDownload(openBlob func(*url.URL) (io.ReadCloser, error)) *Download {
59
openBlob = NewHTTPBlobOpener(utils.NoVerifySSLHostnames)
62
done: make(chan Status),
67
// Stop stops any download that's in progress.
68
func (dl *Download) Stop() {
73
64
// Done returns a channel that receives a status when the download has
74
// completed. It is the receiver's responsibility to close and remove
65
// completed or is aborted. Exactly one Status value will be sent for
66
// each download once it finishes (successfully or otherwise) or is
69
// It is the receiver's responsibility to handle and remove the
76
71
func (dl *Download) Done() <-chan Status {
80
// Wait blocks until the download completes or the abort channel receives.
81
func (dl *Download) Wait(abort <-chan struct{}) (*os.File, error) {
86
logger.Infof("download aborted")
87
return nil, errors.New("aborted")
88
case status := <-dl.Done():
89
if status.Err != nil {
90
if status.File != nil {
91
if err := status.File.Close(); err != nil {
92
logger.Errorf("failed to close file: %v", err)
95
return nil, errors.Trace(status.Err)
97
return status.File, nil
75
// Wait blocks until the download finishes (successfully or
76
// otherwise), or the download is aborted. There will only be a
77
// filename if err is nil.
78
func (dl *Download) Wait() (string, error) {
79
// No select required here because each download will always
80
// return a value once it completes. Downloads can be aborted via
81
// the Abort channel provided a creation time.
83
return status.Filename, errors.Trace(status.Err)
101
86
func (dl *Download) run(req Request) {
104
87
// TODO(dimitern) 2013-10-03 bug #1234715
105
88
// Add a testing HTTPS storage to verify the
106
89
// disableSSLHostnameVerification behavior here.
107
file, err := download(req, dl.openBlob)
90
filename, err := dl.download(req)
109
err = errors.Annotatef(err, "cannot download %q", req.URL)
92
err = errors.Trace(err)
113
94
logger.Infof("download complete (%q)", req.URL)
114
if req.Verify != nil {
115
err = verifyDownload(file, req)
124
case dl.done <- status:
126
case <-dl.tomb.Dying():
131
func verifyDownload(file *os.File, req Request) error {
132
err := req.Verify(file)
134
if errors.IsNotValid(err) {
135
logger.Errorf("download of %s invalid: %v", req.URL, err)
137
return errors.Trace(err)
139
logger.Infof("download verified (%q)", req.URL)
141
if _, err := file.Seek(0, os.SEEK_SET); err != nil {
142
logger.Errorf("failed to seek to beginning of file: %v", err)
143
return errors.Trace(err)
148
func download(req Request, openBlob func(*url.URL) (io.ReadCloser, error)) (file *os.File, err error) {
95
err = verifyDownload(filename, req)
102
// No select needed here because the channel has a size of 1 and
103
// will only be written to once.
110
func (dl *Download) download(req Request) (filename string, err error) {
149
111
logger.Infof("downloading from %s", req.URL)
151
113
dir := req.TargetDir
155
117
tempFile, err := ioutil.TempFile(dir, "inprogress-")
157
return nil, errors.Trace(err)
119
return "", errors.Trace(err)
161
cleanTempFile(tempFile)
124
os.Remove(tempFile.Name())
165
reader, err := openBlob(req.URL)
128
blobReader, err := dl.openBlob(req.URL)
167
return nil, errors.Trace(err)
130
return "", errors.Trace(err)
132
defer blobReader.Close()
134
reader := &abortableReader{blobReader, req.Abort}
171
135
_, err = io.Copy(tempFile, reader)
173
return nil, errors.Trace(err)
175
if _, err := tempFile.Seek(0, 0); err != nil {
176
return nil, errors.Trace(err)
181
func cleanTempFile(f *os.File) {
187
if err := os.Remove(f.Name()); err != nil {
188
logger.Errorf("cannot remove temp file %q: %v", f.Name(), err)
137
return "", errors.Trace(err)
140
return tempFile.Name(), nil
143
// abortableReader wraps a Reader, returning an error from Read calls
144
// if the abort channel provided is closed.
145
type abortableReader struct {
147
abort <-chan struct{}
150
// Read implements io.Reader.
151
func (ar *abortableReader) Read(p []byte) (int, error) {
154
return 0, errors.New("download aborted")
160
func verifyDownload(filename string, req Request) error {
161
if req.Verify == nil {
165
file, err := os.Open(filename)
167
return errors.Annotate(err, "opening for verify")
171
if err := req.Verify(file); err != nil {
172
return errors.Trace(err)
174
logger.Infof("download verified (%q)", req.URL)