blob: ecb2c54582fe5ac4a0736c699be3ae1ae3f5c1e2 [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 Perritt4f93f8e2015-02-02 11:19:41 -07007 "strconv"
Jon Perritt8c93a302014-09-28 22:35:57 -05008 "strings"
Jon Perritt8c31b2a2014-12-03 10:21:11 -07009 "time"
Jon Perritt8c93a302014-09-28 22:35:57 -050010
Ash Wilsonaf262872014-10-20 09:32:29 -040011 "github.com/rackspace/gophercloud"
Jon Perritt8c93a302014-09-28 22:35:57 -050012 "github.com/rackspace/gophercloud/pagination"
Jon Perrittea4e3012014-10-09 22:03:19 -050013
14 "github.com/mitchellh/mapstructure"
Jon Perritt8c93a302014-09-28 22:35:57 -050015)
16
17// Object is a structure that holds information related to a storage object.
Jon Perritt8aa40262014-09-29 15:41:32 -050018type Object struct {
Jon Perritt9415ca72014-11-03 11:58:48 -060019 // Bytes is the total number of bytes that comprise the object.
20 Bytes int64 `json:"bytes" mapstructure:"bytes"`
21
22 // ContentType is the content type of the object.
23 ContentType string `json:"content_type" mapstructure:"content_type"`
24
25 // Hash represents the MD5 checksum value of the object's content.
26 Hash string `json:"hash" mapstructure:"hash"`
27
28 // LastModified is the RFC3339Milli time the object was last modified, represented
29 // as a string. For any given object (obj), this value may be parsed to a time.Time:
30 // lastModified, err := time.Parse(gophercloud.RFC3339Milli, obj.LastModified)
Jon Perrittf3171c12014-09-30 17:39:31 -050031 LastModified string `json:"last_modified" mapstructure:"last_modified"`
Jon Perritt9415ca72014-11-03 11:58:48 -060032
33 // Name is the unique name for the object.
34 Name string `json:"name" mapstructure:"name"`
Jon Perritt8aa40262014-09-29 15:41:32 -050035}
Jon Perritt8c93a302014-09-28 22:35:57 -050036
Jamie Hannafordc9cdc8f2014-10-06 16:32:56 +020037// ObjectPage is a single page of objects that is returned from a call to the
38// List function.
Jon Perritt8c93a302014-09-28 22:35:57 -050039type ObjectPage struct {
40 pagination.MarkerPageBase
41}
42
43// IsEmpty returns true if a ListResult contains no object names.
44func (r ObjectPage) IsEmpty() (bool, error) {
45 names, err := ExtractNames(r)
46 if err != nil {
47 return true, err
48 }
49 return len(names) == 0, nil
50}
51
52// LastMarker returns the last object name in a ListResult.
53func (r ObjectPage) LastMarker() (string, error) {
54 names, err := ExtractNames(r)
55 if err != nil {
56 return "", err
57 }
58 if len(names) == 0 {
59 return "", nil
60 }
61 return names[len(names)-1], nil
62}
63
Jon Perritt8c93a302014-09-28 22:35:57 -050064// ExtractInfo is a function that takes a page of objects and returns their full information.
65func ExtractInfo(page pagination.Page) ([]Object, error) {
66 untyped := page.(ObjectPage).Body.([]interface{})
67 results := make([]Object, len(untyped))
68 for index, each := range untyped {
Jon Perritt8aa40262014-09-29 15:41:32 -050069 object := each.(map[string]interface{})
Jon Perrittfdac6e52014-09-29 19:43:45 -050070 err := mapstructure.Decode(object, &results[index])
Jon Perritt8aa40262014-09-29 15:41:32 -050071 if err != nil {
72 return results, err
73 }
Jon Perritt8c93a302014-09-28 22:35:57 -050074 }
75 return results, nil
76}
77
78// ExtractNames is a function that takes a page of objects and returns only their names.
79func ExtractNames(page pagination.Page) ([]string, error) {
80 casted := page.(ObjectPage)
Ash Wilson72e4d2c2014-10-20 10:27:30 -040081 ct := casted.Header.Get("Content-Type")
Jon Perritt8c93a302014-09-28 22:35:57 -050082 switch {
83 case strings.HasPrefix(ct, "application/json"):
84 parsed, err := ExtractInfo(page)
85 if err != nil {
86 return nil, err
87 }
88
89 names := make([]string, 0, len(parsed))
90 for _, object := range parsed {
Jon Perritt8aa40262014-09-29 15:41:32 -050091 names = append(names, object.Name)
Jon Perritt8c93a302014-09-28 22:35:57 -050092 }
Jon Perrittfdac6e52014-09-29 19:43:45 -050093
Jon Perritt8c93a302014-09-28 22:35:57 -050094 return names, nil
95 case strings.HasPrefix(ct, "text/plain"):
96 names := make([]string, 0, 50)
97
98 body := string(page.(ObjectPage).Body.([]uint8))
99 for _, name := range strings.Split(body, "\n") {
100 if len(name) > 0 {
101 names = append(names, name)
102 }
103 }
104
105 return names, nil
Jon Perrittfdac6e52014-09-29 19:43:45 -0500106 case strings.HasPrefix(ct, "text/html"):
107 return []string{}, nil
Jon Perritt8c93a302014-09-28 22:35:57 -0500108 default:
109 return nil, fmt.Errorf("Cannot extract names from response with content-type: [%s]", ct)
110 }
111}
112
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700113// DownloadHeader represents the headers returned in the response from a Download request.
114type DownloadHeader struct {
115 AcceptRanges string `mapstructure:"Accept-Ranges"`
116 ContentDisposition string `mapstructure:"Content-Disposition"`
117 ContentEncoding string `mapstructure:"Content-Encoding"`
118 ContentLength int64 `mapstructure:"Content-Length"`
119 ContentType string `mapstructure:"Content-Type"`
120 Date time.Time `mapstructure:"-"`
Jon Perritt4f93f8e2015-02-02 11:19:41 -0700121 DeleteAt time.Time `mapstructure:"-"`
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700122 ETag string `mapstructure:"Etag"`
123 LastModified time.Time `mapstructure:"-"`
124 ObjectManifest string `mapstructure:"X-Object-Manifest"`
125 StaticLargeObject bool `mapstructure:"X-Static-Large-Object"`
126 TransID string `mapstructure:"X-Trans-Id"`
127}
128
Jon Perritt5db08922014-09-30 21:32:48 -0500129// DownloadResult is a *http.Response that is returned from a call to the Download function.
130type DownloadResult struct {
Jon Perrittd50f93e2014-10-27 14:19:27 -0500131 gophercloud.HeaderResult
Jamie Hannafordee115552014-10-27 16:11:05 +0100132 Body io.ReadCloser
Jon Perritt5db08922014-09-30 21:32:48 -0500133}
134
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700135// Extract will return a struct of headers returned from a call to Download. To obtain
136// a map of headers, call the ExtractHeader method on the DownloadResult.
137func (dr DownloadResult) Extract() (DownloadHeader, error) {
138 var dh DownloadHeader
139 if dr.Err != nil {
140 return dh, dr.Err
141 }
142
Jon Perritt63e7a482014-12-04 09:47:23 -0700143 if err := gophercloud.DecodeHeader(dr.Header, &dh); err != nil {
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700144 return dh, err
145 }
146
147 if date, ok := dr.Header["Date"]; ok && len(date) > 0 {
Jon Perritt4f93f8e2015-02-02 11:19:41 -0700148 t, err := time.Parse(time.RFC1123, date[0])
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700149 if err != nil {
150 return dh, err
151 }
152 dh.Date = t
153 }
154
155 if date, ok := dr.Header["Last-Modified"]; ok && len(date) > 0 {
Jon Perritt4f93f8e2015-02-02 11:19:41 -0700156 t, err := time.Parse(time.RFC1123, date[0])
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700157 if err != nil {
158 return dh, err
159 }
160 dh.LastModified = t
161 }
162
Jon Perritt4f93f8e2015-02-02 11:19:41 -0700163 if date, ok := dr.Header["X-Delete-At"]; ok && len(date) > 0 {
164 unix, err := strconv.ParseInt(date[0], 10, 64)
165 if err != nil {
166 return dh, err
167 }
168 dh.DeleteAt = time.Unix(unix, 0)
169 }
170
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700171 return dh, nil
172}
173
Jamie Hannaford2e784862014-10-27 10:40:27 +0100174// ExtractContent is a function that takes a DownloadResult's io.Reader body
175// and reads all available data into a slice of bytes. Please be aware that due
176// the nature of io.Reader is forward-only - meaning that it can only be read
177// once and not rewound. You can recreate a reader from the output of this
178// function by using bytes.NewReader(downloadBytes)
Jon Perritt8c93a302014-09-28 22:35:57 -0500179func (dr DownloadResult) ExtractContent() ([]byte, error) {
180 if dr.Err != nil {
Ash Wilsonaf262872014-10-20 09:32:29 -0400181 return nil, dr.Err
Jon Perritt8c93a302014-09-28 22:35:57 -0500182 }
Jamie Hannaford2e784862014-10-27 10:40:27 +0100183 body, err := ioutil.ReadAll(dr.Body)
184 if err != nil {
185 return nil, err
186 }
Jamie Hannafordee115552014-10-27 16:11:05 +0100187 dr.Body.Close()
Jamie Hannaford2e784862014-10-27 10:40:27 +0100188 return body, nil
Jon Perritt8c93a302014-09-28 22:35:57 -0500189}
190
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700191// GetHeader represents the headers returned in the response from a Get request.
192type GetHeader struct {
193 ContentDisposition string `mapstructure:"Content-Disposition"`
194 ContentEncoding string `mapstructure:"Content-Encoding"`
195 ContentLength int64 `mapstructure:"Content-Length"`
196 ContentType string `mapstructure:"Content-Type"`
197 Date time.Time `mapstructure:"-"`
Jon Perritt4f93f8e2015-02-02 11:19:41 -0700198 DeleteAt time.Time `mapstructure:"-"`
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700199 ETag string `mapstructure:"Etag"`
200 LastModified time.Time `mapstructure:"-"`
201 ObjectManifest string `mapstructure:"X-Object-Manifest"`
202 StaticLargeObject bool `mapstructure:"X-Static-Large-Object"`
203 TransID string `mapstructure:"X-Trans-Id"`
204}
205
Jon Perritt5db08922014-09-30 21:32:48 -0500206// GetResult is a *http.Response that is returned from a call to the Get function.
207type GetResult struct {
Jon Perrittd50f93e2014-10-27 14:19:27 -0500208 gophercloud.HeaderResult
Jon Perritt5db08922014-09-30 21:32:48 -0500209}
210
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700211// Extract will return a struct of headers returned from a call to Get. To obtain
212// a map of headers, call the ExtractHeader method on the GetResult.
213func (gr GetResult) Extract() (GetHeader, error) {
214 var gh GetHeader
215 if gr.Err != nil {
216 return gh, gr.Err
217 }
218
Jon Perritt63e7a482014-12-04 09:47:23 -0700219 if err := gophercloud.DecodeHeader(gr.Header, &gh); err != nil {
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700220 return gh, err
221 }
222
223 if date, ok := gr.Header["Date"]; ok && len(date) > 0 {
224 t, err := time.Parse(time.RFC1123, gr.Header["Date"][0])
225 if err != nil {
226 return gh, err
227 }
228 gh.Date = t
229 }
230
231 if date, ok := gr.Header["Last-Modified"]; ok && len(date) > 0 {
232 t, err := time.Parse(time.RFC1123, gr.Header["Last-Modified"][0])
233 if err != nil {
234 return gh, err
235 }
236 gh.LastModified = t
237 }
238
Jon Perritt4f93f8e2015-02-02 11:19:41 -0700239 if date, ok := gr.Header["X-Delete-At"]; ok && len(date) > 0 {
240 unix, err := strconv.ParseInt(date[0], 10, 64)
241 if err != nil {
242 return gh, err
243 }
244 gh.DeleteAt = time.Unix(unix, 0)
245 }
246
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700247 return gh, nil
248}
249
Jon Perritt8c93a302014-09-28 22:35:57 -0500250// ExtractMetadata is a function that takes a GetResult (of type *http.Response)
251// and returns the custom metadata associated with the object.
252func (gr GetResult) ExtractMetadata() (map[string]string, error) {
253 if gr.Err != nil {
254 return nil, gr.Err
255 }
256 metadata := make(map[string]string)
Ash Wilson72e4d2c2014-10-20 10:27:30 -0400257 for k, v := range gr.Header {
Jon Perritt8c93a302014-09-28 22:35:57 -0500258 if strings.HasPrefix(k, "X-Object-Meta-") {
259 key := strings.TrimPrefix(k, "X-Object-Meta-")
260 metadata[key] = v[0]
261 }
262 }
263 return metadata, nil
264}
Jon Perritt5db08922014-09-30 21:32:48 -0500265
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700266// CreateHeader represents the headers returned in the response from a Create request.
267type CreateHeader struct {
268 ContentLength int64 `mapstructure:"Content-Length"`
269 ContentType string `mapstructure:"Content-Type"`
270 Date time.Time `mapstructure:"-"`
271 ETag string `mapstructure:"Etag"`
272 LastModified time.Time `mapstructure:"-"`
273 TransID string `mapstructure:"X-Trans-Id"`
274}
275
Jamie Hannafordc9cdc8f2014-10-06 16:32:56 +0200276// CreateResult represents the result of a create operation.
Jon Perritt5db08922014-09-30 21:32:48 -0500277type CreateResult struct {
Jon Perrittd50f93e2014-10-27 14:19:27 -0500278 gophercloud.HeaderResult
Jon Perritt5db08922014-09-30 21:32:48 -0500279}
280
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700281// Extract will return a struct of headers returned from a call to Create. To obtain
282// a map of headers, call the ExtractHeader method on the CreateResult.
283func (cr CreateResult) Extract() (CreateHeader, error) {
284 var ch CreateHeader
285 if cr.Err != nil {
286 return ch, cr.Err
287 }
288
Jon Perritt63e7a482014-12-04 09:47:23 -0700289 if err := gophercloud.DecodeHeader(cr.Header, &ch); err != nil {
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700290 return ch, err
291 }
292
293 if date, ok := cr.Header["Date"]; ok && len(date) > 0 {
294 t, err := time.Parse(time.RFC1123, cr.Header["Date"][0])
295 if err != nil {
296 return ch, err
297 }
298 ch.Date = t
299 }
300
301 if date, ok := cr.Header["Last-Modified"]; ok && len(date) > 0 {
302 t, err := time.Parse(time.RFC1123, cr.Header["Last-Modified"][0])
303 if err != nil {
304 return ch, err
305 }
306 ch.LastModified = t
307 }
308
309 return ch, nil
310}
311
312// UpdateHeader represents the headers returned in the response from a Update request.
313type UpdateHeader struct {
314 ContentLength int64 `mapstructure:"Content-Length"`
315 ContentType string `mapstructure:"Content-Type"`
316 Date time.Time `mapstructure:"-"`
317 TransID string `mapstructure:"X-Trans-Id"`
318}
319
Jamie Hannafordc9cdc8f2014-10-06 16:32:56 +0200320// UpdateResult represents the result of an update operation.
Jon Perritt5db08922014-09-30 21:32:48 -0500321type UpdateResult struct {
Jon Perrittd50f93e2014-10-27 14:19:27 -0500322 gophercloud.HeaderResult
Jon Perritt5db08922014-09-30 21:32:48 -0500323}
324
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700325// Extract will return a struct of headers returned from a call to Update. To obtain
326// a map of headers, call the ExtractHeader method on the UpdateResult.
327func (ur UpdateResult) Extract() (UpdateHeader, error) {
328 var uh UpdateHeader
329 if ur.Err != nil {
330 return uh, ur.Err
331 }
332
Jon Perritt63e7a482014-12-04 09:47:23 -0700333 if err := gophercloud.DecodeHeader(ur.Header, &uh); err != nil {
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700334 return uh, err
335 }
336
337 if date, ok := ur.Header["Date"]; ok && len(date) > 0 {
338 t, err := time.Parse(time.RFC1123, ur.Header["Date"][0])
339 if err != nil {
340 return uh, err
341 }
342 uh.Date = t
343 }
344
345 return uh, nil
346}
347
348// DeleteHeader represents the headers returned in the response from a Delete request.
349type DeleteHeader struct {
350 ContentLength int64 `mapstructure:"Content-Length"`
351 ContentType string `mapstructure:"Content-Type"`
352 Date time.Time `mapstructure:"-"`
353 TransID string `mapstructure:"X-Trans-Id"`
354}
355
Jamie Hannafordc9cdc8f2014-10-06 16:32:56 +0200356// DeleteResult represents the result of a delete operation.
Jon Perritt5db08922014-09-30 21:32:48 -0500357type DeleteResult struct {
Jon Perrittd50f93e2014-10-27 14:19:27 -0500358 gophercloud.HeaderResult
Jon Perritt5db08922014-09-30 21:32:48 -0500359}
360
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700361// Extract will return a struct of headers returned from a call to Delete. To obtain
362// a map of headers, call the ExtractHeader method on the DeleteResult.
363func (dr DeleteResult) Extract() (DeleteHeader, error) {
364 var dh DeleteHeader
365 if dr.Err != nil {
366 return dh, dr.Err
367 }
368
Jon Perritt63e7a482014-12-04 09:47:23 -0700369 if err := gophercloud.DecodeHeader(dr.Header, &dh); err != nil {
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700370 return dh, err
371 }
372
373 if date, ok := dr.Header["Date"]; ok && len(date) > 0 {
374 t, err := time.Parse(time.RFC1123, dr.Header["Date"][0])
375 if err != nil {
376 return dh, err
377 }
378 dh.Date = t
379 }
380
381 return dh, nil
382}
383
384// CopyHeader represents the headers returned in the response from a Copy request.
385type CopyHeader struct {
386 ContentLength int64 `mapstructure:"Content-Length"`
387 ContentType string `mapstructure:"Content-Type"`
388 CopiedFrom string `mapstructure:"X-Copied-From"`
389 CopiedFromLastModified time.Time `mapstructure:"-"`
390 Date time.Time `mapstructure:"-"`
391 ETag string `mapstructure:"Etag"`
392 LastModified time.Time `mapstructure:"-"`
393 TransID string `mapstructure:"X-Trans-Id"`
394}
395
Jamie Hannafordc9cdc8f2014-10-06 16:32:56 +0200396// CopyResult represents the result of a copy operation.
Jon Perritt5db08922014-09-30 21:32:48 -0500397type CopyResult struct {
Jon Perrittd50f93e2014-10-27 14:19:27 -0500398 gophercloud.HeaderResult
Jon Perritt5db08922014-09-30 21:32:48 -0500399}
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700400
401// Extract will return a struct of headers returned from a call to Copy. To obtain
402// a map of headers, call the ExtractHeader method on the CopyResult.
403func (cr CopyResult) Extract() (CopyHeader, error) {
404 var ch CopyHeader
405 if cr.Err != nil {
406 return ch, cr.Err
407 }
408
Jon Perritt63e7a482014-12-04 09:47:23 -0700409 if err := gophercloud.DecodeHeader(cr.Header, &ch); err != nil {
Jon Perritt8c31b2a2014-12-03 10:21:11 -0700410 return ch, err
411 }
412
413 if date, ok := cr.Header["Date"]; ok && len(date) > 0 {
414 t, err := time.Parse(time.RFC1123, cr.Header["Date"][0])
415 if err != nil {
416 return ch, err
417 }
418 ch.Date = t
419 }
420
421 if date, ok := cr.Header["Last-Modified"]; ok && len(date) > 0 {
422 t, err := time.Parse(time.RFC1123, cr.Header["Last-Modified"][0])
423 if err != nil {
424 return ch, err
425 }
426 ch.LastModified = t
427 }
428
429 if date, ok := cr.Header["X-Copied-From-Last-Modified"]; ok && len(date) > 0 {
430 t, err := time.Parse(time.RFC1123, cr.Header["X-Copied-From-Last-Modified"][0])
431 if err != nil {
432 return ch, err
433 }
434 ch.CopiedFromLastModified = t
435 }
436
437 return ch, nil
438}