Skip to content

Commit

Permalink
Add downloader.Downloader.
Browse files Browse the repository at this point in the history
  • Loading branch information
ericsnowcurrently committed Apr 26, 2016
1 parent 72e8cd1 commit 04f5eef
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 21 deletions.
37 changes: 21 additions & 16 deletions downloader/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@ import (
"os"

"github.com/juju/errors"
"github.com/juju/loggo"
"github.com/juju/utils"
"launchpad.net/tomb"
)

var logger = loggo.GetLogger("juju.downloader")

// Request holds a single download request.
type Request struct {
// URL is the location from which the file will be downloaded.
Expand Down Expand Up @@ -46,25 +44,32 @@ type Download struct {
// request. openBlob is used to gain access to the blob, whether through
// an HTTP request or some other means.
func StartDownload(req Request, openBlob func(*url.URL) (io.ReadCloser, error)) *Download {
d := &Download{
dl := newDownload(openBlob)
go dl.run(req)
return dl
}

func newDownload(openBlob func(*url.URL) (io.ReadCloser, error)) *Download {
if openBlob == nil {
openBlob = NewHTTPBlobOpener(utils.NoVerifySSLHostnames)
}
return &Download{
done: make(chan Status),
openBlob: openBlob,
}
go d.run(req)
return d
}

// Stop stops any download that's in progress.
func (d *Download) Stop() {
d.tomb.Kill(nil)
d.tomb.Wait()
func (dl *Download) Stop() {
dl.tomb.Kill(nil)
dl.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 (dl *Download) Done() <-chan Status {
return dl.done
}

// Wait blocks until the download completes or the abort channel receives.
Expand All @@ -83,13 +88,13 @@ func (dl *Download) Wait(abort <-chan struct{}) (Status, error) {
}
}

func (d *Download) run(req Request) {
defer d.tomb.Done()
func (dl *Download) run(req Request) {
defer dl.tomb.Done()

// TODO(dimitern) 2013-10-03 bug #1234715
// Add a testing HTTPS storage to verify the
// disableSSLHostnameVerification behavior here.
file, err := download(req, d.openBlob)
file, err := download(req, dl.openBlob)
if err != nil {
err = errors.Errorf("cannot download %q: %v", req.URL, err)
}
Expand All @@ -99,8 +104,8 @@ func (d *Download) run(req Request) {
Err: err,
}
select {
case d.done <- status:
case <-d.tomb.Dying():
case dl.done <- status:
case <-dl.tomb.Dying():
cleanTempFile(file)
}
}
Expand Down
5 changes: 0 additions & 5 deletions downloader/download_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"net/url"
"os"
"path/filepath"
stdtesting "testing"
"time"

gitjujutesting "github.com/juju/testing"
Expand Down Expand Up @@ -47,10 +46,6 @@ func (s *DownloadSuite) TearDownTest(c *gc.C) {

var _ = gc.Suite(&DownloadSuite{})

func Test(t *stdtesting.T) {
gc.TestingT(t)
}

func (s *DownloadSuite) URL(c *gc.C, path string) *url.URL {
urlStr := s.HTTPSuite.URL(path)
URL, err := url.Parse(urlStr)
Expand Down
49 changes: 49 additions & 0 deletions downloader/downloader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2016 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package downloader

import (
"io"
"net/url"

"github.com/juju/loggo"
"github.com/juju/utils"
)

var logger = loggo.GetLogger("juju.downloader")

// Downloader provides the functionality for downloading files.
type Downloader struct {
// OpenBlob is the func used to gain access to the blob, whether
// through an HTTP request or some other means.
OpenBlob func(url *url.URL) (io.ReadCloser, error)
}

// NewArgs holds the arguments to New().
type NewArgs struct {
// HostnameVerification is that which should be used for the client.
// If it is disableSSLHostnameVerification then a non-validating
// client will be used.
HostnameVerification utils.SSLHostnameVerification
}

// New returns a new Downloader for the given args.
func New(args NewArgs) *Downloader {
return &Downloader{
OpenBlob: NewHTTPBlobOpener(args.HostnameVerification),
}
}

// Start starts a new download and returns it.
func (dlr Downloader) Start(req Request) *Download {
dl := StartDownload(req, dlr.OpenBlob)
return dl
}

// Download starts a new download, waits for it to complete, and
// returns the result.
func (dlr Downloader) Download(req Request, abort <-chan struct{}) (Status, error) {
dl := dlr.Start(req)
return dl.Wait(abort)
}
117 changes: 117 additions & 0 deletions downloader/downloader_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright 2016 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package downloader_test

import (
"io/ioutil"
"net/url"
"os"
"path/filepath"
"time"

gitjujutesting "github.com/juju/testing"
jc "github.com/juju/testing/checkers"
"github.com/juju/utils"
gc "gopkg.in/check.v1"

"github.com/juju/juju/downloader"
"github.com/juju/juju/testing"
)

type DownloaderSuite struct {
testing.BaseSuite
gitjujutesting.HTTPSuite
}

func (s *DownloaderSuite) SetUpSuite(c *gc.C) {
s.BaseSuite.SetUpSuite(c)
s.HTTPSuite.SetUpSuite(c)
}

func (s *DownloaderSuite) TearDownSuite(c *gc.C) {
s.HTTPSuite.TearDownSuite(c)
s.BaseSuite.TearDownSuite(c)
}

func (s *DownloaderSuite) SetUpTest(c *gc.C) {
s.BaseSuite.SetUpTest(c)
s.HTTPSuite.SetUpTest(c)
}

func (s *DownloaderSuite) TearDownTest(c *gc.C) {
s.HTTPSuite.TearDownTest(c)
s.BaseSuite.TearDownTest(c)
}

var _ = gc.Suite(&DownloaderSuite{})

func (s *DownloaderSuite) URL(c *gc.C, path string) *url.URL {
urlStr := s.HTTPSuite.URL(path)
URL, err := url.Parse(urlStr)
c.Assert(err, jc.ErrorIsNil)
return URL
}

func (s *DownloaderSuite) testDownload(c *gc.C, hostnameVerification utils.SSLHostnameVerification) {
tmp := c.MkDir()
gitjujutesting.Server.Response(200, nil, []byte("archive"))
d := downloader.StartDownload(
downloader.Request{
URL: s.URL(c, "/archive.tgz"),
TargetDir: tmp,
},
downloader.NewHTTPBlobOpener(hostnameVerification),
)
status := <-d.Done()
c.Assert(status.Err, gc.IsNil)
c.Assert(status.File, gc.NotNil)
defer os.Remove(status.File.Name())
defer status.File.Close()

dir, _ := filepath.Split(status.File.Name())
c.Assert(filepath.Clean(dir), gc.Equals, tmp)
assertFileContents(c, status.File, "archive")
}

func (s *DownloaderSuite) TestDownloadWithoutDisablingSSLHostnameVerification(c *gc.C) {
s.testDownload(c, utils.VerifySSLHostnames)
}

func (s *DownloaderSuite) TestDownloadWithDisablingSSLHostnameVerification(c *gc.C) {
s.testDownload(c, utils.NoVerifySSLHostnames)
}

func (s *DownloaderSuite) TestDownloadError(c *gc.C) {
gitjujutesting.Server.Response(404, nil, nil)
d := downloader.StartDownload(
downloader.Request{
URL: s.URL(c, "/archive.tgz"),
TargetDir: c.MkDir(),
},
downloader.NewHTTPBlobOpener(utils.VerifySSLHostnames),
)
status := <-d.Done()
c.Assert(status.File, gc.IsNil)
c.Assert(status.Err, gc.ErrorMatches, `cannot download ".*": bad http response: 404 Not Found`)
}

func (s *DownloaderSuite) TestStopDownload(c *gc.C) {
tmp := c.MkDir()
d := downloader.StartDownload(
downloader.Request{
URL: s.URL(c, "/x.tgz"),
TargetDir: tmp,
},
downloader.NewHTTPBlobOpener(utils.VerifySSLHostnames),
)
d.Stop()
select {
case status := <-d.Done():
c.Fatalf("received status %#v after stop", status)
case <-time.After(testing.ShortWait):
}
infos, err := ioutil.ReadDir(tmp)
c.Assert(err, jc.ErrorIsNil)
c.Assert(infos, gc.HasLen, 0)
}

0 comments on commit 04f5eef

Please sign in to comment.