14
// Multi represents an unfinished multipart upload.
16
// Multipart uploads allow sending big objects in smaller chunks.
17
// After all parts have been sent, the upload must be explicitly
18
// completed by calling Complete with the list of parts.
20
// See http://goo.gl/vJfTG for an overview of multipart uploads.
27
var listMultiMax = 1000
29
type listMultiResp struct {
31
NextUploadIdMarker string
34
CommonPrefixes []string `xml:"CommonPrefixes>Prefix"`
37
// ListMulti returns the list of unfinished multipart uploads in b.
39
// The prefix parameter limits the response to keys that begin with the
40
// specified prefix. You can use prefixes to separate a bucket into different
41
// groupings of keys (to get the feeling of folders, for example).
43
// The delim parameter causes the response to group all of the keys that
44
// share a common prefix up to the next delimiter in a single entry within
45
// the CommonPrefixes field. You can use delimiters to separate a bucket
46
// into different groupings of keys, similar to how folders would work.
48
// See http://goo.gl/ePioY for details.
49
func (b *Bucket) ListMulti(prefix, delim string) (multis []*Multi, prefixes []string, err error) {
50
params := map[string][]string{
52
"max-uploads": {strconv.FormatInt(int64(listMultiMax), 10)},
56
for attempt := attempts.Start(); attempt.Next(); {
62
var resp listMultiResp
63
err := b.S3.query(req, &resp)
64
if shouldRetry(err) && attempt.HasNext() {
70
for i := range resp.Upload {
71
multi := &resp.Upload[i]
73
multis = append(multis, multi)
75
prefixes = append(prefixes, resp.CommonPrefixes...)
76
if !resp.IsTruncated {
77
return multis, prefixes, nil
79
params["key-marker"] = []string{resp.NextKeyMarker}
80
params["upload-id-marker"] = []string{resp.NextUploadIdMarker}
81
attempt = attempts.Start() // Last request worked.
86
// InitMulti initializes a new multipart upload at the provided
87
// key inside b and returns a value for manipulating it.
89
// See http://goo.gl/XP8kL for details.
90
func (b *Bucket) InitMulti(key string, contType string, perm ACL) (*Multi, error) {
91
headers := map[string][]string{
92
"Content-Type": {contType},
93
"Content-Length": {"0"},
94
"x-amz-acl": {string(perm)},
96
params := map[string][]string{
108
UploadId string `xml:"UploadId"`
110
for attempt := attempts.Start(); attempt.Next(); {
111
err = b.S3.query(req, &resp)
112
if !shouldRetry(err) {
119
return &Multi{Bucket: b, Key: key, UploadId: resp.UploadId}, nil
122
// PutPart sends part n of the multipart upload, reading all the content from r.
123
// Each part, except for the last one, must be at least 5MB in size.
125
// See http://goo.gl/pqZer for details.
126
func (m *Multi) PutPart(n int, r io.ReadSeeker) (Part, error) {
127
length, b64md5, err := seekerInfo(r)
131
headers := map[string][]string{
132
"Content-Length": {strconv.FormatInt(length, 10)},
133
"Content-MD5": {b64md5},
135
params := map[string][]string{
136
"uploadId": {m.UploadId},
137
"partNumber": {strconv.FormatInt(int64(n), 10)},
139
for attempt := attempts.Start(); attempt.Next(); {
140
_, err := r.Seek(0, 0)
146
bucket: m.Bucket.Name,
152
err = m.Bucket.S3.prepare(req)
156
resp, err := m.Bucket.S3.run(req, nil)
157
if shouldRetry(err) && attempt.HasNext() {
163
etag := resp.Header.Get("ETag")
165
return Part{}, errors.New("part upload succeeded with no ETag")
167
return Part{n, etag, length}, nil
172
func seekerInfo(r io.ReadSeeker) (length int64, b64md5 string, err error) {
173
_, err = r.Seek(0, 0)
178
length, err = io.Copy(digest, r)
182
b64md5 = base64.StdEncoding.EncodeToString(digest.Sum(nil))
183
return length, b64md5, nil
187
N int `xml:"PartNumber"`
192
type listPartsResp struct {
193
NextPartNumberMarker string
198
var listPartsMax = 1000
200
// ListParts returns the list of previously uploaded parts in m.
202
// See http://goo.gl/ePioY for details.
203
func (m *Multi) ListParts() ([]Part, error) {
204
params := map[string][]string{
205
"uploadId": {m.UploadId},
206
"max-parts": {strconv.FormatInt(int64(listPartsMax), 10)},
209
for attempt := attempts.Start(); attempt.Next(); {
212
bucket: m.Bucket.Name,
216
var resp listPartsResp
217
err := m.Bucket.S3.query(req, &resp)
218
if shouldRetry(err) && attempt.HasNext() {
224
parts = append(parts, resp.Part...)
225
if !resp.IsTruncated {
228
params["part-number-marker"] = []string{resp.NextPartNumberMarker}
229
attempt = attempts.Start() // Last request worked.
234
type completeUpload struct {
235
XMLName xml.Name `xml:"CompleteMultipartUpload"`
236
Parts completeParts `xml:"Part"`
239
type completePart struct {
244
type completeParts []completePart
246
func (p completeParts) Len() int { return len(p) }
247
func (p completeParts) Less(i, j int) bool { return p[i].PartNumber < p[j].PartNumber }
248
func (p completeParts) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
250
// Complete assembles the given previously uploaded parts into the
251
// final object. This operation may take several minutes.
253
// See http://goo.gl/2Z7Tw for details.
254
func (m *Multi) Complete(parts []Part) error {
255
params := map[string][]string{
256
"uploadId": {m.UploadId},
258
c := completeUpload{}
259
for _, p := range parts {
260
c.Parts = append(c.Parts, completePart{p.N, p.ETag})
263
data, err := xml.Marshal(&c)
267
for attempt := attempts.Start(); attempt.Next(); {
270
bucket: m.Bucket.Name,
273
payload: bytes.NewReader(data),
275
err := m.Bucket.S3.query(req, nil)
276
if shouldRetry(err) && attempt.HasNext() {
284
// Abort deletes an unifinished multipart upload and any previously
285
// uploaded parts for it.
287
// After a multipart upload is aborted, no additional parts can be
288
// uploaded using it. However, if any part uploads are currently in
289
// progress, those part uploads might or might not succeed. As a result,
290
// it might be necessary to abort a given multipart upload multiple
291
// times in order to completely free all storage consumed by all parts.
293
// NOTE: If the described scenario happens to you, please report back to
294
// the goamz authors with details. In the future such retrying should be
295
// handled internally, but it's not clear what happens precisely (Is an
296
// error returned? Is the issue completely undetectable?).
298
// See http://goo.gl/dnyJw for details.
299
func (m *Multi) Abort() error {
300
params := map[string][]string{
301
"uploadId": {m.UploadId},
303
for attempt := attempts.Start(); attempt.Next(); {
306
bucket: m.Bucket.Name,
310
err := m.Bucket.S3.query(req, nil)
311
if shouldRetry(err) && attempt.HasNext() {