blob: e29e3a88879591d7425006c020e69a3f34a052f3 [file] [log] [blame]
Jon Perritt8c93a302014-09-28 22:35:57 -05001package objects
2
3import (
4 "fmt"
Jamie Hannaford2e784862014-10-27 10:40:27 +01005 "io"
6 "io/ioutil"
Jon Perritt8c93a302014-09-28 22:35:57 -05007 "strings"
Jon Perritt8c31b2a2014-12-03 10:21:11 -07008 "time"
Jon Perritt8c93a302014-09-28 22:35:57 -05009
Ash Wilsonaf262872014-10-20 09:32:29 -040010 "github.com/rackspace/gophercloud"
Jon Perritt8c93a302014-09-28 22:35:57 -050011 "github.com/rackspace/gophercloud/pagination"
Jon Perrittea4e3012014-10-09 22:03:19 -050012
13 "github.com/mitchellh/mapstructure"
Jon Perritt8c93a302014-09-28 22:35:57 -050014)
15
16// Object is a structure that holds information related to a storage object.
Jon Perritt8aa40262014-09-29 15:41:32 -050017type Object struct {
Jon Perritt9415ca72014-11-03 11:58:48 -060018 // Bytes is the total number of bytes that comprise the object.
19 Bytes int64 `json:"bytes" mapstructure:"bytes"`
20
21 // ContentType is the content type of the object.
22 ContentType string `json:"content_type" mapstructure:"content_type"`
23
24 // Hash represents the MD5 checksum value of the object's content.
25 Hash string `json:"hash" mapstructure:"hash"`
26
27 // LastModified is the RFC3339Milli time the object was last modified, represented
28 // as a string. For any given object (obj), this value may be parsed to a time.Time:
29 // lastModified, err := time.Parse(gophercloud.RFC3339Milli, obj.LastModified)
Jon Perrittf3171c12014-09-30 17:39:31 -050030 LastModified string `json:"last_modified" mapstructure:"last_modified"`
Jon Perritt9415ca72014-11-03 11:58:48 -060031
32 // Name is the unique name for the object.
33 Name string `json:"name" mapstructure:"name"`
Jon Perritt8aa40262014-09-29 15:41:32 -050034}
Jon Perritt8c93a302014-09-28 22:35:57 -050035
Jamie Hannafordc9cdc8f2014-10-06 16:32:56 +020036// ObjectPage is a single page of objects that is returned from a call to the
37// List function.
Jon Perritt8c93a302014-09-28 22:35:57 -050038type ObjectPage struct {
39 pagination.MarkerPageBase
40}
41
42// IsEmpty returns true if a ListResult contains no object names.
43func (r ObjectPage) IsEmpty() (bool, error) {
44 names, err := ExtractNames(r)
45 if err != nil {
46 return true, err
47 }
48 return len(names) == 0, nil
49}
50
51// LastMarker returns the last object name in a ListResult.
52func (r ObjectPage) LastMarker() (string, error) {
53 names, err := ExtractNames(r)
54 if err != nil {
55 return "", err
56 }
57 if len(names) == 0 {
58 return "", nil
59 }
60 return names[len(names)-1], nil
61}
62
Jon Perritt8c93a302014-09-28 22:35:57 -050063// ExtractInfo is a function that takes a page of objects and returns their full information.
64func ExtractInfo(page pagination.Page) ([]Object, error) {
65 untyped := page.(ObjectPage).Body.([]interface{})
66 results := make([]Object, len(untyped))
67 for index, each := range untyped {
Jon Perritt8aa40262014-09-29 15:41:32 -050068 object := each.(map[string]interface{})
Jon Perrittfdac6e52014-09-29 19:43:45 -050069 err := mapstructure.Decode(object, &results[index])
Jon Perritt8aa40262014-09-29 15:41:32 -050070 if err != nil {
71 return results, err
72 }
Jon Perritt8c93a302014-09-28 22:35:57 -050073 }
74 return results, nil
75}
76
77// ExtractNames is a function that takes a page of objects and returns only their names.
78func ExtractNames(page pagination.Page) ([]string, error) {
79 casted := page.(ObjectPage)
Ash Wilson72e4d2c2014-10-20 10:27:30 -040080 ct := casted.Header.Get("Content-Type")
Jon Perritt8c93a302014-09-28 22:35:57 -050081 switch {
82 case strings.HasPrefix(ct, "application/json"):
83 parsed, err := ExtractInfo(page)
84 if err != nil {
85 return nil, err
86 }
87
88 names := make([]string, 0, len(parsed))
89 for _, object := range parsed {
Jon Perritt8aa40262014-09-29 15:41:32 -050090 names = append(names, object.Name)
Jon Perritt8c93a302014-09-28 22:35:57 -050091 }
Jon Perrittfdac6e52014-09-29 19:43:45 -050092
Jon Perritt8c93a302014-09-28 22:35:57 -050093 return names, nil
94 case strings.HasPrefix(ct, "text/plain"):
95 names := make([]string, 0, 50)
96
97 body := string(page.(ObjectPage).Body.([]uint8))
98 for _, name := range strings.Split(body, "\n") {
99 if len(name) > 0 {
100 names = append(names, name)
101 }
102 }
103
104 return names, nil
Jon Perrittfdac6e52014-09-29 19:43:45 -0500105 case strings.HasPrefix(ct, "text/html"):
106 return []string{}, nil
Jon Perritt8c93a302014-09-28 22:35:57 -0500107 default:
108 return nil, fmt.Errorf("Cannot extract names from response with content-type: [%s]", ct)
109 }
110}
111
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700112// DownloadHeader represents the headers returned in the response from a Download request.
113type DownloadHeader struct {
114 AcceptRanges string `mapstructure:"Accept-Ranges"`
115 ContentDisposition string `mapstructure:"Content-Disposition"`
116 ContentEncoding string `mapstructure:"Content-Encoding"`
117 ContentLength int64 `mapstructure:"Content-Length"`
118 ContentType string `mapstructure:"Content-Type"`
119 Date time.Time `mapstructure:"-"`
120 DeleteAt int `mapstructure:"X-Delete-At"`
121 ETag string `mapstructure:"Etag"`
122 LastModified time.Time `mapstructure:"-"`
123 ObjectManifest string `mapstructure:"X-Object-Manifest"`
124 StaticLargeObject bool `mapstructure:"X-Static-Large-Object"`
125 TransID string `mapstructure:"X-Trans-Id"`
126}
127
Jon Perritt5db08922014-09-30 21:32:48 -0500128// DownloadResult is a *http.Response that is returned from a call to the Download function.
129type DownloadResult struct {
Jon Perrittd50f93e2014-10-27 14:19:27 -0500130 gophercloud.HeaderResult
Jamie Hannafordee115552014-10-27 16:11:05 +0100131 Body io.ReadCloser
Jon Perritt5db08922014-09-30 21:32:48 -0500132}
133
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700134// Extract will return a struct of headers returned from a call to Download. To obtain
135// a map of headers, call the ExtractHeader method on the DownloadResult.
136func (dr DownloadResult) Extract() (DownloadHeader, error) {
137 var dh DownloadHeader
138 if dr.Err != nil {
139 return dh, dr.Err
140 }
141
Jon Perritt63e7a482014-12-04 09:47:23 -0700142 if err := gophercloud.DecodeHeader(dr.Header, &dh); err != nil {
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700143 return dh, err
144 }
145
146 if date, ok := dr.Header["Date"]; ok && len(date) > 0 {
147 t, err := time.Parse(time.RFC1123, dr.Header["Date"][0])
148 if err != nil {
149 return dh, err
150 }
151 dh.Date = t
152 }
153
154 if date, ok := dr.Header["Last-Modified"]; ok && len(date) > 0 {
155 t, err := time.Parse(time.RFC1123, dr.Header["Last-Modified"][0])
156 if err != nil {
157 return dh, err
158 }
159 dh.LastModified = t
160 }
161
162 return dh, nil
163}
164
Jamie Hannaford2e784862014-10-27 10:40:27 +0100165// ExtractContent is a function that takes a DownloadResult's io.Reader body
166// and reads all available data into a slice of bytes. Please be aware that due
167// the nature of io.Reader is forward-only - meaning that it can only be read
168// once and not rewound. You can recreate a reader from the output of this
169// function by using bytes.NewReader(downloadBytes)
Jon Perritt8c93a302014-09-28 22:35:57 -0500170func (dr DownloadResult) ExtractContent() ([]byte, error) {
171 if dr.Err != nil {
Ash Wilsonaf262872014-10-20 09:32:29 -0400172 return nil, dr.Err
Jon Perritt8c93a302014-09-28 22:35:57 -0500173 }
Jamie Hannaford2e784862014-10-27 10:40:27 +0100174 body, err := ioutil.ReadAll(dr.Body)
175 if err != nil {
176 return nil, err
177 }
Jamie Hannafordee115552014-10-27 16:11:05 +0100178 dr.Body.Close()
Jamie Hannaford2e784862014-10-27 10:40:27 +0100179 return body, nil
Jon Perritt8c93a302014-09-28 22:35:57 -0500180}
181
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700182// GetHeader represents the headers returned in the response from a Get request.
183type GetHeader struct {
184 ContentDisposition string `mapstructure:"Content-Disposition"`
185 ContentEncoding string `mapstructure:"Content-Encoding"`
186 ContentLength int64 `mapstructure:"Content-Length"`
187 ContentType string `mapstructure:"Content-Type"`
188 Date time.Time `mapstructure:"-"`
189 DeleteAt int `mapstructure:"X-Delete-At"`
190 ETag string `mapstructure:"Etag"`
191 LastModified time.Time `mapstructure:"-"`
192 ObjectManifest string `mapstructure:"X-Object-Manifest"`
193 StaticLargeObject bool `mapstructure:"X-Static-Large-Object"`
194 TransID string `mapstructure:"X-Trans-Id"`
195}
196
Jon Perritt5db08922014-09-30 21:32:48 -0500197// GetResult is a *http.Response that is returned from a call to the Get function.
198type GetResult struct {
Jon Perrittd50f93e2014-10-27 14:19:27 -0500199 gophercloud.HeaderResult
Jon Perritt5db08922014-09-30 21:32:48 -0500200}
201
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700202// Extract will return a struct of headers returned from a call to Get. To obtain
203// a map of headers, call the ExtractHeader method on the GetResult.
204func (gr GetResult) Extract() (GetHeader, error) {
205 var gh GetHeader
206 if gr.Err != nil {
207 return gh, gr.Err
208 }
209
Jon Perritt63e7a482014-12-04 09:47:23 -0700210 if err := gophercloud.DecodeHeader(gr.Header, &gh); err != nil {
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700211 return gh, err
212 }
213
214 if date, ok := gr.Header["Date"]; ok && len(date) > 0 {
215 t, err := time.Parse(time.RFC1123, gr.Header["Date"][0])
216 if err != nil {
217 return gh, err
218 }
219 gh.Date = t
220 }
221
222 if date, ok := gr.Header["Last-Modified"]; ok && len(date) > 0 {
223 t, err := time.Parse(time.RFC1123, gr.Header["Last-Modified"][0])
224 if err != nil {
225 return gh, err
226 }
227 gh.LastModified = t
228 }
229
230 return gh, nil
231}
232
Jon Perritt8c93a302014-09-28 22:35:57 -0500233// ExtractMetadata is a function that takes a GetResult (of type *http.Response)
234// and returns the custom metadata associated with the object.
235func (gr GetResult) ExtractMetadata() (map[string]string, error) {
236 if gr.Err != nil {
237 return nil, gr.Err
238 }
239 metadata := make(map[string]string)
Ash Wilson72e4d2c2014-10-20 10:27:30 -0400240 for k, v := range gr.Header {
Jon Perritt8c93a302014-09-28 22:35:57 -0500241 if strings.HasPrefix(k, "X-Object-Meta-") {
242 key := strings.TrimPrefix(k, "X-Object-Meta-")
243 metadata[key] = v[0]
244 }
245 }
246 return metadata, nil
247}
Jon Perritt5db08922014-09-30 21:32:48 -0500248
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700249// CreateHeader represents the headers returned in the response from a Create request.
250type CreateHeader struct {
251 ContentLength int64 `mapstructure:"Content-Length"`
252 ContentType string `mapstructure:"Content-Type"`
253 Date time.Time `mapstructure:"-"`
254 ETag string `mapstructure:"Etag"`
255 LastModified time.Time `mapstructure:"-"`
256 TransID string `mapstructure:"X-Trans-Id"`
257}
258
Jamie Hannafordc9cdc8f2014-10-06 16:32:56 +0200259// CreateResult represents the result of a create operation.
Jon Perritt5db08922014-09-30 21:32:48 -0500260type CreateResult struct {
Jon Perrittd50f93e2014-10-27 14:19:27 -0500261 gophercloud.HeaderResult
Jon Perritt5db08922014-09-30 21:32:48 -0500262}
263
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700264// Extract will return a struct of headers returned from a call to Create. To obtain
265// a map of headers, call the ExtractHeader method on the CreateResult.
266func (cr CreateResult) Extract() (CreateHeader, error) {
267 var ch CreateHeader
268 if cr.Err != nil {
269 return ch, cr.Err
270 }
271
Jon Perritt63e7a482014-12-04 09:47:23 -0700272 if err := gophercloud.DecodeHeader(cr.Header, &ch); err != nil {
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700273 return ch, err
274 }
275
276 if date, ok := cr.Header["Date"]; ok && len(date) > 0 {
277 t, err := time.Parse(time.RFC1123, cr.Header["Date"][0])
278 if err != nil {
279 return ch, err
280 }
281 ch.Date = t
282 }
283
284 if date, ok := cr.Header["Last-Modified"]; ok && len(date) > 0 {
285 t, err := time.Parse(time.RFC1123, cr.Header["Last-Modified"][0])
286 if err != nil {
287 return ch, err
288 }
289 ch.LastModified = t
290 }
291
292 return ch, nil
293}
294
295// UpdateHeader represents the headers returned in the response from a Update request.
296type UpdateHeader struct {
297 ContentLength int64 `mapstructure:"Content-Length"`
298 ContentType string `mapstructure:"Content-Type"`
299 Date time.Time `mapstructure:"-"`
300 TransID string `mapstructure:"X-Trans-Id"`
301}
302
Jamie Hannafordc9cdc8f2014-10-06 16:32:56 +0200303// UpdateResult represents the result of an update operation.
Jon Perritt5db08922014-09-30 21:32:48 -0500304type UpdateResult struct {
Jon Perrittd50f93e2014-10-27 14:19:27 -0500305 gophercloud.HeaderResult
Jon Perritt5db08922014-09-30 21:32:48 -0500306}
307
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700308// Extract will return a struct of headers returned from a call to Update. To obtain
309// a map of headers, call the ExtractHeader method on the UpdateResult.
310func (ur UpdateResult) Extract() (UpdateHeader, error) {
311 var uh UpdateHeader
312 if ur.Err != nil {
313 return uh, ur.Err
314 }
315
Jon Perritt63e7a482014-12-04 09:47:23 -0700316 if err := gophercloud.DecodeHeader(ur.Header, &uh); err != nil {
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700317 return uh, err
318 }
319
320 if date, ok := ur.Header["Date"]; ok && len(date) > 0 {
321 t, err := time.Parse(time.RFC1123, ur.Header["Date"][0])
322 if err != nil {
323 return uh, err
324 }
325 uh.Date = t
326 }
327
328 return uh, nil
329}
330
331// DeleteHeader represents the headers returned in the response from a Delete request.
332type DeleteHeader struct {
333 ContentLength int64 `mapstructure:"Content-Length"`
334 ContentType string `mapstructure:"Content-Type"`
335 Date time.Time `mapstructure:"-"`
336 TransID string `mapstructure:"X-Trans-Id"`
337}
338
Jamie Hannafordc9cdc8f2014-10-06 16:32:56 +0200339// DeleteResult represents the result of a delete operation.
Jon Perritt5db08922014-09-30 21:32:48 -0500340type DeleteResult struct {
Jon Perrittd50f93e2014-10-27 14:19:27 -0500341 gophercloud.HeaderResult
Jon Perritt5db08922014-09-30 21:32:48 -0500342}
343
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700344// Extract will return a struct of headers returned from a call to Delete. To obtain
345// a map of headers, call the ExtractHeader method on the DeleteResult.
346func (dr DeleteResult) Extract() (DeleteHeader, error) {
347 var dh DeleteHeader
348 if dr.Err != nil {
349 return dh, dr.Err
350 }
351
Jon Perritt63e7a482014-12-04 09:47:23 -0700352 if err := gophercloud.DecodeHeader(dr.Header, &dh); err != nil {
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700353 return dh, err
354 }
355
356 if date, ok := dr.Header["Date"]; ok && len(date) > 0 {
357 t, err := time.Parse(time.RFC1123, dr.Header["Date"][0])
358 if err != nil {
359 return dh, err
360 }
361 dh.Date = t
362 }
363
364 return dh, nil
365}
366
367// CopyHeader represents the headers returned in the response from a Copy request.
368type CopyHeader struct {
369 ContentLength int64 `mapstructure:"Content-Length"`
370 ContentType string `mapstructure:"Content-Type"`
371 CopiedFrom string `mapstructure:"X-Copied-From"`
372 CopiedFromLastModified time.Time `mapstructure:"-"`
373 Date time.Time `mapstructure:"-"`
374 ETag string `mapstructure:"Etag"`
375 LastModified time.Time `mapstructure:"-"`
376 TransID string `mapstructure:"X-Trans-Id"`
377}
378
Jamie Hannafordc9cdc8f2014-10-06 16:32:56 +0200379// CopyResult represents the result of a copy operation.
Jon Perritt5db08922014-09-30 21:32:48 -0500380type CopyResult struct {
Jon Perrittd50f93e2014-10-27 14:19:27 -0500381 gophercloud.HeaderResult
Jon Perritt5db08922014-09-30 21:32:48 -0500382}
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700383
384// Extract will return a struct of headers returned from a call to Copy. To obtain
385// a map of headers, call the ExtractHeader method on the CopyResult.
386func (cr CopyResult) Extract() (CopyHeader, error) {
387 var ch CopyHeader
388 if cr.Err != nil {
389 return ch, cr.Err
390 }
391
Jon Perritt63e7a482014-12-04 09:47:23 -0700392 if err := gophercloud.DecodeHeader(cr.Header, &ch); err != nil {
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700393 return ch, err
394 }
395
396 if date, ok := cr.Header["Date"]; ok && len(date) > 0 {
397 t, err := time.Parse(time.RFC1123, cr.Header["Date"][0])
398 if err != nil {
399 return ch, err
400 }
401 ch.Date = t
402 }
403
404 if date, ok := cr.Header["Last-Modified"]; ok && len(date) > 0 {
405 t, err := time.Parse(time.RFC1123, cr.Header["Last-Modified"][0])
406 if err != nil {
407 return ch, err
408 }
409 ch.LastModified = t
410 }
411
412 if date, ok := cr.Header["X-Copied-From-Last-Modified"]; ok && len(date) > 0 {
413 t, err := time.Parse(time.RFC1123, cr.Header["X-Copied-From-Last-Modified"][0])
414 if err != nil {
415 return ch, err
416 }
417 ch.CopiedFromLastModified = t
418 }
419
420 return ch, nil
421}