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  }