func (d *downloader) geterr() error {
	d.m.Lock()
	defer d.m.Unlock()
	return d.err
}
geterr is a thread-safe getter for the error object
package s3manager
import (
	"fmt"
	"io"
	"strconv"
	"strings"
	"sync"
	"time"
	"github.com/ks3sdklib/aws-sdk-go/aws/awsutil"
	"github.com/ks3sdklib/aws-sdk-go/service/s3"
)
var DefaultDownloadPartSize int64 = 1024 * 1024 * 5
var DefaultDownloadConcurrency = 5
var DefaultDownloadOptions = &DownloadOptions{
	PartSize:    DefaultDownloadPartSize,
	Concurrency: DefaultDownloadConcurrency,
}
type DownloadOptions struct {
	PartSize int64
	Concurrency int
	S3 *s3.S3
}
func NewDownloader(opts *DownloadOptions) *Downloader {
	if opts == nil {
		opts = DefaultDownloadOptions
	}
	return &Downloader{opts: opts}
}
type Downloader struct {
	opts *DownloadOptions
}
func (d *Downloader) Download(w io.WriterAt, input *s3.GetObjectInput) (n int64, err error) {
	impl := downloader{w: w, in: input, opts: *d.opts}
	return impl.download()
}
type downloader struct {
	opts DownloadOptions
	in   *s3.GetObjectInput
	w    io.WriterAt
	wg sync.WaitGroup
	m  sync.Mutex
	pos        int64
	totalBytes int64
	written    int64
	err        error
}
func (d *downloader) init() {
	d.totalBytes = -1
	if d.opts.Concurrency == 0 {
		d.opts.Concurrency = DefaultDownloadConcurrency
	}
	if d.opts.PartSize == 0 {
		d.opts.PartSize = DefaultDownloadPartSize
	}
	if d.opts.S3 == nil {
		d.opts.S3 = s3.New(nil)
	}
}
func (d *downloader) download() (n int64, err error) {
	d.init()
	ch := make(chan dlchunk, d.opts.Concurrency)
	for i := 0; i < d.opts.Concurrency; i++ {
		d.wg.Add(1)
		go d.downloadPart(ch)
	}
	for d.geterr() == nil {
		if d.pos != 0 {
			total := d.getTotalBytes()
			if total < 0 {
				time.Sleep(10 * time.Millisecond)
				continue
			} else if d.pos >= total {
				break  
			}
		}
		ch <- dlchunk{w: d.w, start: d.pos, size: d.opts.PartSize}
		d.pos += d.opts.PartSize
	}
	close(ch)
	d.wg.Wait()
	return d.written, d.err
}
func (d *downloader) downloadPart(ch chan dlchunk) {
	defer d.wg.Done()
	for {
		chunk, ok := <-ch
		if !ok {
			break
		}
		if d.geterr() == nil {
			in := &s3.GetObjectInput{}
			awsutil.Copy(in, d.in)
			rng := fmt.Sprintf("bytes=%d-%d",
				chunk.start, chunk.start+chunk.size-1)
			in.Range = &rng
			resp, err := d.opts.S3.GetObject(in)
			if err != nil {
				d.seterr(err)
			} else {
				d.setTotalBytes(resp)  
				n, err := io.Copy(&chunk, resp.Body)
				resp.Body.Close()
				if err != nil {
					d.seterr(err)
				}
				d.incrwritten(n)
			}
		}
	}
}
func (d *downloader) getTotalBytes() int64 {
	d.m.Lock()
	defer d.m.Unlock()
	return d.totalBytes
}
func (d *downloader) setTotalBytes(resp *s3.GetObjectOutput) {
	d.m.Lock()
	defer d.m.Unlock()
	if d.totalBytes >= 0 {
		return
	}
	parts := strings.Split(*resp.ContentRange, "/")
	total, err := strconv.ParseInt(parts[len(parts)-1], 10, 64)
	if err != nil {
		d.err = err
		return
	}
	d.totalBytes = total
}
func (d *downloader) incrwritten(n int64) {
	d.m.Lock()
	defer d.m.Unlock()
	d.written += n
}