-
Notifications
You must be signed in to change notification settings - Fork 0
/
downloader.go
121 lines (109 loc) · 2.89 KB
/
downloader.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
// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package downloader
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"github.com/juju/loggo"
"github.com/juju/utils"
"launchpad.net/tomb"
)
var logger = loggo.GetLogger("juju.downloader")
// Status represents the status of a completed download.
type Status struct {
// File holds the downloaded data on success.
File *os.File
// Err describes any error encountered while downloading.
Err error
}
// Download can download a file from the network.
type Download struct {
tomb tomb.Tomb
done chan Status
hostnameVerification utils.SSLHostnameVerification
}
// New returns a new Download instance downloading from the given URL
// to the given directory. If dir is empty, it defaults to
// os.TempDir(). If disableSSLHostnameVerification is true then a non-
// validating http client will be used.
func New(url, dir string, hostnameVerification utils.SSLHostnameVerification) *Download {
d := &Download{
done: make(chan Status),
hostnameVerification: hostnameVerification,
}
go d.run(url, dir)
return d
}
// Stop stops any download that's in progress.
func (d *Download) Stop() {
d.tomb.Kill(nil)
d.tomb.Wait()
}
// Done returns a channel that receives a status when the download has
// completed. It is the receiver's responsibility to close and remove
// the received file.
func (d *Download) Done() <-chan Status {
return d.done
}
func (d *Download) run(url, dir string) {
defer d.tomb.Done()
// TODO(dimitern) 2013-10-03 bug #1234715
// Add a testing HTTPS storage to verify the
// disableSSLHostnameVerification behavior here.
file, err := download(url, dir, d.hostnameVerification)
if err != nil {
err = fmt.Errorf("cannot download %q: %v", url, err)
}
status := Status{
File: file,
Err: err,
}
select {
case d.done <- status:
case <-d.tomb.Dying():
cleanTempFile(status.File)
}
}
func download(url, dir string, hostnameVerification utils.SSLHostnameVerification) (file *os.File, err error) {
if dir == "" {
dir = os.TempDir()
}
tempFile, err := ioutil.TempFile(dir, "inprogress-")
if err != nil {
return nil, err
}
defer func() {
if err != nil {
cleanTempFile(tempFile)
}
}()
// TODO(rog) make the download operation interruptible.
client := utils.GetHTTPClient(hostnameVerification)
resp, err := client.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("bad http response: %v", resp.Status)
}
_, err = io.Copy(tempFile, resp.Body)
if err != nil {
return nil, err
}
if _, err := tempFile.Seek(0, 0); err != nil {
return nil, err
}
return tempFile, nil
}
func cleanTempFile(f *os.File) {
if f != nil {
f.Close()
if err := os.Remove(f.Name()); err != nil {
logger.Warningf("cannot remove temp file %q: %v", f.Name(), err)
}
}
}