func (u *uploader) singlePart(buf readerAtSeeker) (*S3UploadOutput, error) {
	params := &s3.PutObjectInput{}
	awsutil.Copy(params, u.in)
	params.Body = wrapReader(u.cfg, buf)
	req, out := u.cfg.S3.PutObjectRequest(params)
	err := computeHashes(buf, req)
	if err != nil {
		return nil, err
	}
	req.SetContext(u.ctx)
	if err := req.Send(); err != nil {
		return nil, err
	}
	url := req.HTTPRequest.URL.String()
	return &S3UploadOutput{
		Location:  url,
		VersionID: out.VersionId,
		ETag:      out.ETag,
	}, nil
}
singlePart contains upload logic for uploading a single chunk via
a regular PutObject request. Multipart requests require at least two
parts, or at least 5MB of data.
package s3util
import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"sort"
	"sync"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/awsutil"
	"github.com/aws/aws-sdk-go/aws/request"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/s3/s3iface"
	"github.com/aws/aws-sdk-go/service/s3/s3manager"
	"github.com/NVIDIA/vdisc/pkg/countio"
)
type readerAtSeeker interface {
	io.ReaderAt
	io.ReadSeeker
}
type S3UploadInput struct {
	Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
	Key    *string `location:"uri" locationName:"Key" type:"string" required:"true"`
	Body   io.Reader
}
type S3UploadOutput struct {
	Location  string
	VersionID *string
	UploadID  string
	ETag      *string
}
type S3Uploader struct {
	PartSize        int64
	Concurrency     int
	MaxUploadParts  int
	ProgressCounter *int64
	S3              s3iface.S3API
}
func NewS3Uploader(svc s3iface.S3API, options ...func(*S3Uploader)) *S3Uploader {
	u := &S3Uploader{
		S3:             svc,
		PartSize:       s3manager.DefaultUploadPartSize,
		Concurrency:    s3manager.DefaultUploadConcurrency,
		MaxUploadParts: s3manager.MaxUploadParts,
	}
	for _, option := range options {
		option(u)
	}
	return u
}
func (u S3Uploader) Upload(input *S3UploadInput, options ...func(*S3Uploader)) (*S3UploadOutput, error) {
	return u.UploadWithContext(aws.BackgroundContext(), input, options...)
}
func (u S3Uploader) UploadWithContext(ctx aws.Context, input *S3UploadInput, opts ...func(*S3Uploader)) (*S3UploadOutput, error) {
	i := uploader{in: input, cfg: u, ctx: ctx}
	for _, opt := range opts {
		opt(&i.cfg)
	}
	return i.upload()
}
type uploader struct {
	ctx aws.Context
	cfg S3Uploader
	in *S3UploadInput
	readerPos int64  
	totalSize int64  
	bufferPool sync.Pool
}
func (u *uploader) upload() (*S3UploadOutput, error) {
	u.init()
	if u.cfg.PartSize < s3manager.MinUploadPartSize {
		msg := fmt.Sprintf("part size must be at least %d bytes", s3manager.MinUploadPartSize)
		return nil, awserr.New("ConfigError", msg, nil)
	}
	reader, _, part, err := u.nextReader()
	if err == io.EOF {  
		return u.singlePart(reader)
	} else if err != nil {
		return nil, awserr.New("ReadRequestBody", "read upload data failed", err)
	}
	mu := multiuploader{uploader: u}
	return mu.upload(reader, part)
}
func (u *uploader) init() {
	if u.cfg.Concurrency == 0 {
		u.cfg.Concurrency = s3manager.DefaultUploadConcurrency
	}
	if u.cfg.PartSize == 0 {
		u.cfg.PartSize = s3manager.DefaultUploadPartSize
	}
	if u.cfg.MaxUploadParts == 0 {
		u.cfg.MaxUploadParts = s3manager.MaxUploadParts
	}
	u.bufferPool = sync.Pool{
		New: func() interface{} { return make([]byte, u.cfg.PartSize) },
	}
	u.initSize()
}
func (u *uploader) initSize() {
	u.totalSize = -1
	switch r := u.in.Body.(type) {
	case io.Seeker:
		n, err := aws.SeekerLen(r)
		if err != nil {
			return
		}
		u.totalSize = n
		if u.totalSize/u.cfg.PartSize >= int64(u.cfg.MaxUploadParts) {
			u.cfg.PartSize = (u.totalSize / int64(u.cfg.MaxUploadParts)) + 1
		}
	}
}
func (u *uploader) nextReader() (readerAtSeeker, int, []byte, error) {
	type readerAtSeeker interface {
		io.ReaderAt
		io.ReadSeeker
	}
	switch r := u.in.Body.(type) {
	case readerAtSeeker:
		var err error
		n := u.cfg.PartSize
		if u.totalSize >= 0 {
			bytesLeft := u.totalSize - u.readerPos
			if bytesLeft <= u.cfg.PartSize {
				err = io.EOF
				n = bytesLeft
			}
		}
		reader := io.NewSectionReader(r, u.readerPos, n)
		u.readerPos += n
		return reader, int(n), nil, err
	default:
		part := u.bufferPool.Get().([]byte)
		n, err := readFillBuf(r, part)
		u.readerPos += int64(n)
		return bytes.NewReader(part[0:n]), n, part, err
	}
}
func readFillBuf(r io.Reader, b []byte) (offset int, err error) {
	for offset < len(b) && err == nil {
		var n int
		n, err = r.Read(b[offset:])
		offset += n
	}
	return offset, err
}
func wrapReader(cfg S3Uploader, r readerAtSeeker) readerAtSeeker {
	if cfg.ProgressCounter != nil {
		return countio.NewReaderAtSeekerWithAtomicCounter(r, cfg.ProgressCounter)
	}
	return r
}