1
// Copyright 2012, 2013 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
12
"github.com/juju/errors"
13
"github.com/juju/utils"
17
// Request holds a single download request.
19
// URL is the location from which the file will be downloaded.
22
// TargetDir is the directory into which the file will be downloaded.
23
// It defaults to os.TempDir().
26
// Verify is used to ensure that the download result is correct. If
27
// the download is invalid then the func must return errors.NotValid.
28
// If no func is provided then no verification happens.
29
Verify func(*os.File) error
32
// Status represents the status of a completed download.
34
// File holds the downloaded data on success.
37
// Err describes any error encountered while downloading.
41
// Download can download a file from the network.
42
type Download struct {
45
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
// Done returns a channel that receives a status when the download has
74
// completed. It is the receiver's responsibility to close and remove
76
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
101
func (dl *Download) run(req Request) {
104
// TODO(dimitern) 2013-10-03 bug #1234715
105
// Add a testing HTTPS storage to verify the
106
// disableSSLHostnameVerification behavior here.
107
file, err := download(req, dl.openBlob)
109
err = errors.Annotatef(err, "cannot download %q", req.URL)
113
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) {
149
logger.Infof("downloading from %s", req.URL)
155
tempFile, err := ioutil.TempFile(dir, "inprogress-")
157
return nil, errors.Trace(err)
161
cleanTempFile(tempFile)
165
reader, err := openBlob(req.URL)
167
return nil, errors.Trace(err)
171
_, 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)