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