~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/downloader/download.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2012, 2013 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package downloader
 
5
 
 
6
import (
 
7
        "io"
 
8
        "io/ioutil"
 
9
        "net/url"
 
10
        "os"
 
11
 
 
12
        "github.com/juju/errors"
 
13
        "github.com/juju/utils"
 
14
        "launchpad.net/tomb"
 
15
)
 
16
 
 
17
// Request holds a single download request.
 
18
type Request struct {
 
19
        // URL is the location from which the file will be downloaded.
 
20
        URL *url.URL
 
21
 
 
22
        // TargetDir is the directory into which the file will be downloaded.
 
23
        // It defaults to os.TempDir().
 
24
        TargetDir string
 
25
 
 
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
 
30
}
 
31
 
 
32
// Status represents the status of a completed download.
 
33
type Status struct {
 
34
        // File holds the downloaded data on success.
 
35
        File *os.File
 
36
 
 
37
        // Err describes any error encountered while downloading.
 
38
        Err error
 
39
}
 
40
 
 
41
// Download can download a file from the network.
 
42
type Download struct {
 
43
        tomb     tomb.Tomb
 
44
        done     chan Status
 
45
        openBlob func(*url.URL) (io.ReadCloser, error)
 
46
}
 
47
 
 
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)
 
53
        go dl.run(req)
 
54
        return dl
 
55
}
 
56
 
 
57
func newDownload(openBlob func(*url.URL) (io.ReadCloser, error)) *Download {
 
58
        if openBlob == nil {
 
59
                openBlob = NewHTTPBlobOpener(utils.NoVerifySSLHostnames)
 
60
        }
 
61
        return &Download{
 
62
                done:     make(chan Status),
 
63
                openBlob: openBlob,
 
64
        }
 
65
}
 
66
 
 
67
// Stop stops any download that's in progress.
 
68
func (dl *Download) Stop() {
 
69
        dl.tomb.Kill(nil)
 
70
        dl.tomb.Wait()
 
71
}
 
72
 
 
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
 
75
// the received file.
 
76
func (dl *Download) Done() <-chan Status {
 
77
        return dl.done
 
78
}
 
79
 
 
80
// Wait blocks until the download completes or the abort channel receives.
 
81
func (dl *Download) Wait(abort <-chan struct{}) (*os.File, error) {
 
82
        defer dl.Stop()
 
83
 
 
84
        select {
 
85
        case <-abort:
 
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)
 
93
                                }
 
94
                        }
 
95
                        return nil, errors.Trace(status.Err)
 
96
                }
 
97
                return status.File, nil
 
98
        }
 
99
}
 
100
 
 
101
func (dl *Download) run(req Request) {
 
102
        defer dl.tomb.Done()
 
103
 
 
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)
 
108
        if err != nil {
 
109
                err = errors.Annotatef(err, "cannot download %q", req.URL)
 
110
        }
 
111
 
 
112
        if err == nil {
 
113
                logger.Infof("download complete (%q)", req.URL)
 
114
                if req.Verify != nil {
 
115
                        err = verifyDownload(file, req)
 
116
                }
 
117
        }
 
118
 
 
119
        status := Status{
 
120
                File: file,
 
121
                Err:  err,
 
122
        }
 
123
        select {
 
124
        case dl.done <- status:
 
125
                // no-op
 
126
        case <-dl.tomb.Dying():
 
127
                cleanTempFile(file)
 
128
        }
 
129
}
 
130
 
 
131
func verifyDownload(file *os.File, req Request) error {
 
132
        err := req.Verify(file)
 
133
        if err != nil {
 
134
                if errors.IsNotValid(err) {
 
135
                        logger.Errorf("download of %s invalid: %v", req.URL, err)
 
136
                }
 
137
                return errors.Trace(err)
 
138
        }
 
139
        logger.Infof("download verified (%q)", req.URL)
 
140
 
 
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)
 
144
        }
 
145
        return nil
 
146
}
 
147
 
 
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)
 
150
 
 
151
        dir := req.TargetDir
 
152
        if dir == "" {
 
153
                dir = os.TempDir()
 
154
        }
 
155
        tempFile, err := ioutil.TempFile(dir, "inprogress-")
 
156
        if err != nil {
 
157
                return nil, errors.Trace(err)
 
158
        }
 
159
        defer func() {
 
160
                if err != nil {
 
161
                        cleanTempFile(tempFile)
 
162
                }
 
163
        }()
 
164
 
 
165
        reader, err := openBlob(req.URL)
 
166
        if err != nil {
 
167
                return nil, errors.Trace(err)
 
168
        }
 
169
        defer reader.Close()
 
170
 
 
171
        _, err = io.Copy(tempFile, reader)
 
172
        if err != nil {
 
173
                return nil, errors.Trace(err)
 
174
        }
 
175
        if _, err := tempFile.Seek(0, 0); err != nil {
 
176
                return nil, errors.Trace(err)
 
177
        }
 
178
        return tempFile, nil
 
179
}
 
180
 
 
181
func cleanTempFile(f *os.File) {
 
182
        if f == nil {
 
183
                return
 
184
        }
 
185
 
 
186
        f.Close()
 
187
        if err := os.Remove(f.Name()); err != nil {
 
188
                logger.Errorf("cannot remove temp file %q: %v", f.Name(), err)
 
189
        }
 
190
}