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