github.com/gophercloud/gophercloud@v1.11.0/openstack/objectstorage/v1/objects/results.go (about) 1 package objects 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "net/url" 9 "strings" 10 "time" 11 12 "github.com/gophercloud/gophercloud" 13 "github.com/gophercloud/gophercloud/pagination" 14 ) 15 16 // Object is a structure that holds information related to a storage object. 17 type Object struct { 18 // Bytes is the total number of bytes that comprise the object. 19 Bytes int64 `json:"bytes"` 20 21 // ContentType is the content type of the object. 22 ContentType string `json:"content_type"` 23 24 // Hash represents the MD5 checksum value of the object's content. 25 Hash string `json:"hash"` 26 27 // LastModified is the time the object was last modified. 28 LastModified time.Time `json:"-"` 29 30 // Name is the unique name for the object. 31 Name string `json:"name"` 32 33 // Subdir denotes if the result contains a subdir. 34 Subdir string `json:"subdir"` 35 36 // IsLatest indicates whether the object version is the latest one. 37 IsLatest bool `json:"is_latest"` 38 39 // VersionID contains a version ID of the object, when container 40 // versioning is enabled. 41 VersionID string `json:"version_id"` 42 } 43 44 func (r *Object) UnmarshalJSON(b []byte) error { 45 type tmp Object 46 var s *struct { 47 tmp 48 LastModified string `json:"last_modified"` 49 } 50 51 err := json.Unmarshal(b, &s) 52 if err != nil { 53 return err 54 } 55 56 *r = Object(s.tmp) 57 58 if s.LastModified != "" { 59 t, err := time.Parse(gophercloud.RFC3339MilliNoZ, s.LastModified) 60 if err != nil { 61 t, err = time.Parse(gophercloud.RFC3339Milli, s.LastModified) 62 if err != nil { 63 return err 64 } 65 } 66 r.LastModified = t 67 } 68 69 return nil 70 } 71 72 // ObjectPage is a single page of objects that is returned from a call to the 73 // List function. 74 type ObjectPage struct { 75 pagination.MarkerPageBase 76 } 77 78 // IsEmpty returns true if a ListResult contains no object names. 79 func (r ObjectPage) IsEmpty() (bool, error) { 80 if r.StatusCode == 204 { 81 return true, nil 82 } 83 84 names, err := ExtractNames(r) 85 return len(names) == 0, err 86 } 87 88 // LastMarker returns the last object name in a ListResult. 89 func (r ObjectPage) LastMarker() (string, error) { 90 return extractLastMarker(r) 91 } 92 93 // ExtractInfo is a function that takes a page of objects and returns their 94 // full information. 95 func ExtractInfo(r pagination.Page) ([]Object, error) { 96 var s []Object 97 err := (r.(ObjectPage)).ExtractInto(&s) 98 return s, err 99 } 100 101 // ExtractNames is a function that takes a page of objects and returns only 102 // their names. 103 func ExtractNames(r pagination.Page) ([]string, error) { 104 casted := r.(ObjectPage) 105 ct := casted.Header.Get("Content-Type") 106 switch { 107 case strings.HasPrefix(ct, "application/json"): 108 parsed, err := ExtractInfo(r) 109 if err != nil { 110 return nil, err 111 } 112 113 names := make([]string, 0, len(parsed)) 114 for _, object := range parsed { 115 if object.Subdir != "" { 116 names = append(names, object.Subdir) 117 } else { 118 names = append(names, object.Name) 119 } 120 } 121 122 return names, nil 123 case strings.HasPrefix(ct, "text/plain"): 124 names := make([]string, 0, 50) 125 126 body := string(r.(ObjectPage).Body.([]uint8)) 127 for _, name := range strings.Split(body, "\n") { 128 if len(name) > 0 { 129 names = append(names, name) 130 } 131 } 132 133 return names, nil 134 case strings.HasPrefix(ct, "text/html"): 135 return []string{}, nil 136 default: 137 return nil, fmt.Errorf("Cannot extract names from response with content-type: [%s]", ct) 138 } 139 } 140 141 // DownloadHeader represents the headers returned in the response from a 142 // Download request. 143 type DownloadHeader struct { 144 AcceptRanges string `json:"Accept-Ranges"` 145 ContentDisposition string `json:"Content-Disposition"` 146 ContentEncoding string `json:"Content-Encoding"` 147 ContentLength int64 `json:"Content-Length,string"` 148 ContentType string `json:"Content-Type"` 149 Date time.Time `json:"-"` 150 DeleteAt time.Time `json:"-"` 151 ETag string `json:"Etag"` 152 LastModified time.Time `json:"-"` 153 ObjectManifest string `json:"X-Object-Manifest"` 154 StaticLargeObject bool `json:"-"` 155 TransID string `json:"X-Trans-Id"` 156 ObjectVersionID string `json:"X-Object-Version-Id"` 157 } 158 159 func (r *DownloadHeader) UnmarshalJSON(b []byte) error { 160 type tmp DownloadHeader 161 var s struct { 162 tmp 163 Date gophercloud.JSONRFC1123 `json:"Date"` 164 DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"` 165 LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"` 166 StaticLargeObject interface{} `json:"X-Static-Large-Object"` 167 } 168 err := json.Unmarshal(b, &s) 169 if err != nil { 170 return err 171 } 172 173 *r = DownloadHeader(s.tmp) 174 175 switch t := s.StaticLargeObject.(type) { 176 case string: 177 if t == "True" || t == "true" { 178 r.StaticLargeObject = true 179 } 180 case bool: 181 r.StaticLargeObject = t 182 } 183 184 r.Date = time.Time(s.Date) 185 r.DeleteAt = time.Time(s.DeleteAt) 186 r.LastModified = time.Time(s.LastModified) 187 188 return nil 189 } 190 191 // DownloadResult is a *http.Response that is returned from a call to the 192 // Download function. 193 type DownloadResult struct { 194 gophercloud.HeaderResult 195 Body io.ReadCloser 196 } 197 198 // Extract will return a struct of headers returned from a call to Download. 199 func (r DownloadResult) Extract() (*DownloadHeader, error) { 200 var s DownloadHeader 201 err := r.ExtractInto(&s) 202 return &s, err 203 } 204 205 // ExtractContent is a function that takes a DownloadResult's io.Reader body 206 // and reads all available data into a slice of bytes. Please be aware that due 207 // the nature of io.Reader is forward-only - meaning that it can only be read 208 // once and not rewound. You can recreate a reader from the output of this 209 // function by using bytes.NewReader(downloadBytes) 210 func (r *DownloadResult) ExtractContent() ([]byte, error) { 211 if r.Err != nil { 212 return nil, r.Err 213 } 214 defer r.Body.Close() 215 body, err := ioutil.ReadAll(r.Body) 216 if err != nil { 217 return nil, err 218 } 219 return body, nil 220 } 221 222 // GetHeader represents the headers returned in the response from a Get request. 223 type GetHeader struct { 224 ContentDisposition string `json:"Content-Disposition"` 225 ContentEncoding string `json:"Content-Encoding"` 226 ContentLength int64 `json:"Content-Length,string"` 227 ContentType string `json:"Content-Type"` 228 Date time.Time `json:"-"` 229 DeleteAt time.Time `json:"-"` 230 ETag string `json:"Etag"` 231 LastModified time.Time `json:"-"` 232 ObjectManifest string `json:"X-Object-Manifest"` 233 StaticLargeObject bool `json:"-"` 234 TransID string `json:"X-Trans-Id"` 235 ObjectVersionID string `json:"X-Object-Version-Id"` 236 } 237 238 func (r *GetHeader) UnmarshalJSON(b []byte) error { 239 type tmp GetHeader 240 var s struct { 241 tmp 242 Date gophercloud.JSONRFC1123 `json:"Date"` 243 DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"` 244 LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"` 245 StaticLargeObject interface{} `json:"X-Static-Large-Object"` 246 } 247 err := json.Unmarshal(b, &s) 248 if err != nil { 249 return err 250 } 251 252 *r = GetHeader(s.tmp) 253 254 switch t := s.StaticLargeObject.(type) { 255 case string: 256 if t == "True" || t == "true" { 257 r.StaticLargeObject = true 258 } 259 case bool: 260 r.StaticLargeObject = t 261 } 262 263 r.Date = time.Time(s.Date) 264 r.DeleteAt = time.Time(s.DeleteAt) 265 r.LastModified = time.Time(s.LastModified) 266 267 return nil 268 } 269 270 // GetResult is a *http.Response that is returned from a call to the Get 271 // function. 272 type GetResult struct { 273 gophercloud.HeaderResult 274 } 275 276 // Extract will return a struct of headers returned from a call to Get. 277 func (r GetResult) Extract() (*GetHeader, error) { 278 var s GetHeader 279 err := r.ExtractInto(&s) 280 return &s, err 281 } 282 283 // ExtractMetadata is a function that takes a GetResult (of type *http.Response) 284 // and returns the custom metadata associated with the object. 285 func (r GetResult) ExtractMetadata() (map[string]string, error) { 286 if r.Err != nil { 287 return nil, r.Err 288 } 289 metadata := make(map[string]string) 290 for k, v := range r.Header { 291 if strings.HasPrefix(k, "X-Object-Meta-") { 292 key := strings.TrimPrefix(k, "X-Object-Meta-") 293 metadata[key] = v[0] 294 } 295 } 296 return metadata, nil 297 } 298 299 // CreateHeader represents the headers returned in the response from a 300 // Create request. 301 type CreateHeader struct { 302 ContentLength int64 `json:"Content-Length,string"` 303 ContentType string `json:"Content-Type"` 304 Date time.Time `json:"-"` 305 ETag string `json:"Etag"` 306 LastModified time.Time `json:"-"` 307 TransID string `json:"X-Trans-Id"` 308 ObjectVersionID string `json:"X-Object-Version-Id"` 309 } 310 311 func (r *CreateHeader) UnmarshalJSON(b []byte) error { 312 type tmp CreateHeader 313 var s struct { 314 tmp 315 Date gophercloud.JSONRFC1123 `json:"Date"` 316 LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"` 317 } 318 err := json.Unmarshal(b, &s) 319 if err != nil { 320 return err 321 } 322 323 *r = CreateHeader(s.tmp) 324 325 r.Date = time.Time(s.Date) 326 r.LastModified = time.Time(s.LastModified) 327 328 return nil 329 } 330 331 // CreateResult represents the result of a create operation. 332 type CreateResult struct { 333 checksum string 334 gophercloud.HeaderResult 335 } 336 337 // Extract will return a struct of headers returned from a call to Create. 338 func (r CreateResult) Extract() (*CreateHeader, error) { 339 //if r.Header.Get("ETag") != fmt.Sprintf("%x", localChecksum) { 340 // return nil, ErrWrongChecksum{} 341 //} 342 var s CreateHeader 343 err := r.ExtractInto(&s) 344 return &s, err 345 } 346 347 // UpdateHeader represents the headers returned in the response from a 348 // Update request. 349 type UpdateHeader struct { 350 ContentLength int64 `json:"Content-Length,string"` 351 ContentType string `json:"Content-Type"` 352 Date time.Time `json:"-"` 353 TransID string `json:"X-Trans-Id"` 354 ObjectVersionID string `json:"X-Object-Version-Id"` 355 } 356 357 func (r *UpdateHeader) UnmarshalJSON(b []byte) error { 358 type tmp UpdateHeader 359 var s struct { 360 tmp 361 Date gophercloud.JSONRFC1123 `json:"Date"` 362 } 363 err := json.Unmarshal(b, &s) 364 if err != nil { 365 return err 366 } 367 368 *r = UpdateHeader(s.tmp) 369 370 r.Date = time.Time(s.Date) 371 372 return nil 373 } 374 375 // UpdateResult represents the result of an update operation. 376 type UpdateResult struct { 377 gophercloud.HeaderResult 378 } 379 380 // Extract will return a struct of headers returned from a call to Update. 381 func (r UpdateResult) Extract() (*UpdateHeader, error) { 382 var s UpdateHeader 383 err := r.ExtractInto(&s) 384 return &s, err 385 } 386 387 // DeleteHeader represents the headers returned in the response from a 388 // Delete request. 389 type DeleteHeader struct { 390 ContentLength int64 `json:"Content-Length,string"` 391 ContentType string `json:"Content-Type"` 392 Date time.Time `json:"-"` 393 TransID string `json:"X-Trans-Id"` 394 ObjectVersionID string `json:"X-Object-Version-Id"` 395 ObjectCurrentVersionID string `json:"X-Object-Current-Version-Id"` 396 } 397 398 func (r *DeleteHeader) UnmarshalJSON(b []byte) error { 399 type tmp DeleteHeader 400 var s struct { 401 tmp 402 Date gophercloud.JSONRFC1123 `json:"Date"` 403 } 404 err := json.Unmarshal(b, &s) 405 if err != nil { 406 return err 407 } 408 409 *r = DeleteHeader(s.tmp) 410 411 r.Date = time.Time(s.Date) 412 413 return nil 414 } 415 416 // DeleteResult represents the result of a delete operation. 417 type DeleteResult struct { 418 gophercloud.HeaderResult 419 } 420 421 // Extract will return a struct of headers returned from a call to Delete. 422 func (r DeleteResult) Extract() (*DeleteHeader, error) { 423 var s DeleteHeader 424 err := r.ExtractInto(&s) 425 return &s, err 426 } 427 428 // CopyHeader represents the headers returned in the response from a 429 // Copy request. 430 type CopyHeader struct { 431 ContentLength int64 `json:"Content-Length,string"` 432 ContentType string `json:"Content-Type"` 433 CopiedFrom string `json:"X-Copied-From"` 434 CopiedFromLastModified time.Time `json:"-"` 435 Date time.Time `json:"-"` 436 ETag string `json:"Etag"` 437 LastModified time.Time `json:"-"` 438 TransID string `json:"X-Trans-Id"` 439 ObjectVersionID string `json:"X-Object-Version-Id"` 440 } 441 442 func (r *CopyHeader) UnmarshalJSON(b []byte) error { 443 type tmp CopyHeader 444 var s struct { 445 tmp 446 CopiedFromLastModified gophercloud.JSONRFC1123 `json:"X-Copied-From-Last-Modified"` 447 Date gophercloud.JSONRFC1123 `json:"Date"` 448 LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"` 449 } 450 err := json.Unmarshal(b, &s) 451 if err != nil { 452 return err 453 } 454 455 *r = CopyHeader(s.tmp) 456 457 r.Date = time.Time(s.Date) 458 r.CopiedFromLastModified = time.Time(s.CopiedFromLastModified) 459 r.LastModified = time.Time(s.LastModified) 460 461 return nil 462 } 463 464 // CopyResult represents the result of a copy operation. 465 type CopyResult struct { 466 gophercloud.HeaderResult 467 } 468 469 // Extract will return a struct of headers returned from a call to Copy. 470 func (r CopyResult) Extract() (*CopyHeader, error) { 471 var s CopyHeader 472 err := r.ExtractInto(&s) 473 return &s, err 474 } 475 476 type BulkDeleteResponse struct { 477 ResponseStatus string `json:"Response Status"` 478 ResponseBody string `json:"Response Body"` 479 Errors [][]string `json:"Errors"` 480 NumberDeleted int `json:"Number Deleted"` 481 NumberNotFound int `json:"Number Not Found"` 482 } 483 484 // BulkDeleteResult represents the result of a bulk delete operation. To extract 485 // the response object from the HTTP response, call its Extract method. 486 type BulkDeleteResult struct { 487 gophercloud.Result 488 } 489 490 // Extract will return a BulkDeleteResponse struct returned from a BulkDelete 491 // call. 492 func (r BulkDeleteResult) Extract() (*BulkDeleteResponse, error) { 493 var s BulkDeleteResponse 494 err := r.ExtractInto(&s) 495 return &s, err 496 } 497 498 // extractLastMarker is a function that takes a page of objects and returns the 499 // marker for the page. This can either be a subdir or the last object's name. 500 func extractLastMarker(r pagination.Page) (string, error) { 501 casted := r.(ObjectPage) 502 503 // If a delimiter was requested, check if a subdir exists. 504 queryParams, err := url.ParseQuery(casted.URL.RawQuery) 505 if err != nil { 506 return "", err 507 } 508 509 var delimeter bool 510 if v, ok := queryParams["delimiter"]; ok && len(v) > 0 { 511 delimeter = true 512 } 513 514 ct := casted.Header.Get("Content-Type") 515 switch { 516 case strings.HasPrefix(ct, "application/json"): 517 parsed, err := ExtractInfo(r) 518 if err != nil { 519 return "", err 520 } 521 522 var lastObject Object 523 if len(parsed) > 0 { 524 lastObject = parsed[len(parsed)-1] 525 } 526 527 if !delimeter { 528 return lastObject.Name, nil 529 } 530 531 if lastObject.Name != "" { 532 return lastObject.Name, nil 533 } 534 535 return lastObject.Subdir, nil 536 case strings.HasPrefix(ct, "text/plain"): 537 names := make([]string, 0, 50) 538 539 body := string(r.(ObjectPage).Body.([]uint8)) 540 for _, name := range strings.Split(body, "\n") { 541 if len(name) > 0 { 542 names = append(names, name) 543 } 544 } 545 546 return names[len(names)-1], err 547 case strings.HasPrefix(ct, "text/html"): 548 return "", nil 549 default: 550 return "", fmt.Errorf("Cannot extract names from response with content-type: [%s]", ct) 551 } 552 }