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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
|
/******************************************************************************
*
* Copyright 2018 Stefan Majewsky <majewsky@gmx.net>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package schwift
import (
"encoding/json"
"io"
"strconv"
"strings"
)
//BulkUploadFormat enumerates possible archive formats for Container.BulkUpload().
type BulkUploadFormat string
const (
//BulkUploadTar is a plain tar archive.
BulkUploadTar BulkUploadFormat = "tar"
//BulkUploadTarGzip is a GZip-compressed tar archive.
BulkUploadTarGzip BulkUploadFormat = "tar.gz"
//BulkUploadTarBzip2 is a BZip2-compressed tar archive.
BulkUploadTarBzip2 BulkUploadFormat = "tar.bz2"
)
//BulkUpload extracts an archive (which may contain multiple files) into a
//Swift account. The path of each file in the archive is appended to the
//uploadPath to form the FullName() of the resulting Object.
//
//For example, when uploading an archive that contains the file "a/b/c":
//
// //This uploads the file into the container "a" as object "b/c".
// account.BulkUpload("", format, contents, nil, nil)
// //This uploads the file into the container "foo" as object "a/b/c".
// account.BulkUpload("foo", format, contents, nil, nil)
// //This uploads the file into the container "foo" as object "bar/baz/a/b/c".
// account.BulkUpload("foo/bar/baz", format, contents, nil, nil)
//
//The first return value indicates the number of files that have been created
//on the server side. This may be lower than the number of files in the archive
//if some files could not be saved individually (e.g. because a quota was
//exceeded in the middle of the archive extraction).
//
//If not nil, the error return value is *usually* an instance of
//schwift.BulkUploadError.
//
//This operation returns (0, ErrNotSupported) if the server does not support
//bulk-uploading.
func (a *Account) BulkUpload(uploadPath string, format BulkUploadFormat, contents io.Reader, headers AccountHeaders, opts *RequestOptions) (int, error) {
caps, err := a.Capabilities()
if err != nil {
return 0, err
}
if caps.BulkUpload == nil {
return 0, ErrNotSupported
}
req := Request{
Method: "PUT",
Body: contents,
Headers: headersToHTTP(headers),
Options: cloneRequestOptions(opts),
ExpectStatusCodes: []int{200},
}
req.Headers.Set("Accept", "application/json")
req.Options.Values.Set("extract-archive", string(format))
fields := strings.SplitN(strings.Trim(uploadPath, "/"), "/", 2)
req.ContainerName = fields[0]
if len(fields) == 2 {
req.ObjectName = fields[1]
}
resp, err := req.Do(a.backend)
if err != nil {
return 0, err
}
var result struct {
//ResponseStatus indicates the overall result as a HTTP status string, e.g.
//"201 Created" or "500 Internal Error".
ResponseStatus string `json:"Response Status"`
//ResponseBody contains an overall error message for errors that are not
//related to a single file in the archive (e.g. "invalid tar file" or "Max
//delete failures exceeded").
ResponseBody string `json:"Response Body"`
//Errors contains error messages for individual files. Each entry is a
//[]string with 2 elements, the object's fullName and the HTTP status for
//this file's upload (e.g. "412 Precondition Failed").
Errors [][]string `json:"Errors"`
//NumberFilesCreated is self-explanatory.
NumberFilesCreated int `json:"Number Files Created"`
}
err = json.NewDecoder(resp.Body).Decode(&result)
closeErr := resp.Body.Close()
if err == nil {
err = closeErr
}
if err != nil {
return 0, err
}
//parse `result` into type BulkUploadError
bulkErr := BulkUploadError{
ArchiveError: result.ResponseBody,
}
bulkErr.StatusCode, err = parseResponseStatus(result.ResponseStatus)
if err != nil {
return 0, err
}
for _, suberr := range result.Errors {
if len(suberr) != 2 {
continue //wtf
}
nameFields := strings.SplitN(suberr[0], "/", 2)
for len(nameFields) < 2 {
nameFields = append(nameFields, "")
}
statusCode, err := parseResponseStatus(suberr[1])
if err != nil {
return 0, err
}
bulkErr.ObjectErrors = append(bulkErr.ObjectErrors, BulkObjectError{
ContainerName: nameFields[0],
ObjectName: nameFields[1],
StatusCode: statusCode,
})
}
//is BulkUploadError really an error?
if len(bulkErr.ObjectErrors) == 0 && bulkErr.ArchiveError == "" && bulkErr.StatusCode >= 200 && bulkErr.StatusCode < 300 {
return result.NumberFilesCreated, nil
}
return result.NumberFilesCreated, bulkErr
}
func parseResponseStatus(status string) (int, error) {
//`status` looks like "201 Created"
fields := strings.SplitN(status, " ", 2)
return strconv.Atoi(fields[0])
}
|