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  }