github.com/huaweicloud/golangsdk@v0.0.0-20210831081626-d823fe11ceba/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/huaweicloud/golangsdk" 14 "github.com/huaweicloud/golangsdk/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.([]uint8)) 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 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:"-"` 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 } 236 237 func (r *GetHeader) UnmarshalJSON(b []byte) error { 238 type tmp GetHeader 239 var s struct { 240 tmp 241 ContentLength string `json:"Content-Length"` 242 Date golangsdk.JSONRFC1123 `json:"Date"` 243 DeleteAt golangsdk.JSONUnix `json:"X-Delete-At"` 244 LastModified golangsdk.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 s.ContentLength { 255 case "": 256 r.ContentLength = 0 257 default: 258 r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64) 259 if err != nil { 260 return err 261 } 262 } 263 264 switch t := s.StaticLargeObject.(type) { 265 case string: 266 if t == "True" || t == "true" { 267 r.StaticLargeObject = true 268 } 269 case bool: 270 r.StaticLargeObject = t 271 } 272 273 r.Date = time.Time(s.Date) 274 r.DeleteAt = time.Time(s.DeleteAt) 275 r.LastModified = time.Time(s.LastModified) 276 277 return nil 278 } 279 280 // GetResult is a *http.Response that is returned from a call to the Get 281 // function. 282 type GetResult struct { 283 golangsdk.HeaderResult 284 } 285 286 // Extract will return a struct of headers returned from a call to Get. 287 func (r GetResult) Extract() (*GetHeader, error) { 288 var s *GetHeader 289 err := r.ExtractInto(&s) 290 return s, err 291 } 292 293 // ExtractMetadata is a function that takes a GetResult (of type *http.Response) 294 // and returns the custom metadata associated with the object. 295 func (r GetResult) ExtractMetadata() (map[string]string, error) { 296 if r.Err != nil { 297 return nil, r.Err 298 } 299 metadata := make(map[string]string) 300 for k, v := range r.Header { 301 if strings.HasPrefix(k, "X-Object-Meta-") { 302 key := strings.TrimPrefix(k, "X-Object-Meta-") 303 metadata[key] = v[0] 304 } 305 } 306 return metadata, nil 307 } 308 309 // CreateHeader represents the headers returned in the response from a 310 // Create request. 311 type CreateHeader struct { 312 ContentLength int64 `json:"-"` 313 ContentType string `json:"Content-Type"` 314 Date time.Time `json:"-"` 315 ETag string `json:"Etag"` 316 LastModified time.Time `json:"-"` 317 TransID string `json:"X-Trans-Id"` 318 } 319 320 func (r *CreateHeader) UnmarshalJSON(b []byte) error { 321 type tmp CreateHeader 322 var s struct { 323 tmp 324 ContentLength string `json:"Content-Length"` 325 Date golangsdk.JSONRFC1123 `json:"Date"` 326 LastModified golangsdk.JSONRFC1123 `json:"Last-Modified"` 327 } 328 err := json.Unmarshal(b, &s) 329 if err != nil { 330 return err 331 } 332 333 *r = CreateHeader(s.tmp) 334 335 switch s.ContentLength { 336 case "": 337 r.ContentLength = 0 338 default: 339 r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64) 340 if err != nil { 341 return err 342 } 343 } 344 345 r.Date = time.Time(s.Date) 346 r.LastModified = time.Time(s.LastModified) 347 348 return nil 349 } 350 351 // CreateResult represents the result of a create operation. 352 type CreateResult struct { 353 checksum string 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.([]uint8)) 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 }