diff options
| -rw-r--r-- | object.go | 63 | ||||
| -rw-r--r-- | tests/object_test.go | 73 |
2 files changed, 105 insertions, 31 deletions
@@ -60,8 +60,9 @@ func (o *Object) Name() string { } //FullName returns the container name and object name joined together with a -//slash. This identifier is used by Swift in several places (DLO manifests, -//symlink targets, etc.) to refer to an object within an account. For example: +//slash. This identifier is used by Swift in several places (large object +//manifests, symlink targets, etc.) to refer to an object within an account. +//For example: // // obj := account.Container("docs").Object("2018-02-10/invoice.pdf") // obj.Name() //returns "2018-02-10/invoice.pdf" @@ -159,7 +160,8 @@ func (o *Object) Update(headers ObjectHeaders, opts *RequestOptions) error { //these parameters, http.StatusUnprocessableEntity is returned. If Etag is not //supplied and cannot be computed in advance, Upload() will compute the Etag as //data is read from the io.Reader, and compare the result to the Etag returned -//by Swift, returning ErrChecksumMismatch in case of mismatch. +//by Swift, returning ErrChecksumMismatch in case of mismatch. The object will +//have been uploaded at that point, so you will usually want to Delete() it. // //This function can be used regardless of whether the object exists or not. // @@ -308,6 +310,7 @@ func (o *Object) Invalidate() { // // str, err := object.Download(nil, nil).AsString() // +//See struct DownloadedObject for details. func (o *Object) Download(headers ObjectHeaders, opts *RequestOptions) DownloadedObject { resp, err := Request{ Method: "GET", @@ -318,14 +321,58 @@ func (o *Object) Download(headers ObjectHeaders, opts *RequestOptions) Downloade ExpectStatusCodes: []int{200}, }.Do(o.c.a.backend) if err == nil { - headers := ObjectHeaders(headersFromHTTP(resp.Header)) - err = headers.Validate() + newHeaders := ObjectHeaders(headersFromHTTP(resp.Header)) + err = newHeaders.Validate() if err == nil { - o.headers = &headers + o.headers = &newHeaders } } return DownloadedObject{resp.Body, err} } -//TODO Object.Copy(), Object.Move() -//TODO provide a companion to Object.Upload() to connect it with content-generating functions where an io.Writer needs to be given +//CopyTo copies the object on the server side using a COPY request. To copy +//only the content, not the metadata, use the X-Fresh-Metadata header: +// +// hdr := make(ObjectHeaders) +// hdr.Set("X-Fresh-Metadata", "true") +// err := sourceObject.CopyTo(targetObject, hdr, nil) +// +//If X-Fresh-Metadata is not set (or set to false), all metadata from the +//source object will be copied to the target, but you can overwrite metadata by +//providing new values in the headers argument, like with Update(). +// +//A successful COPY implies target.Invalidate() since it may change the +//target's metadata. +func (o *Object) CopyTo(target *Object, headers ObjectHeaders, opts *RequestOptions) error { + hdr := headersToHTTP(headers) + hdr.Set("Destination", target.FullName()) + if o.c.a.name != target.c.a.name { + hdr.Set("Destination-Account", target.c.a.name) + } + + _, err := Request{ + Method: "COPY", + ContainerName: o.c.name, + ObjectName: o.name, + Headers: hdr, + Options: opts, + ExpectStatusCodes: []int{201}, + DrainResponseBody: true, + }.Do(o.c.a.backend) + if err == nil { + target.Invalidate() + } + return err +} + +//MoveTo moves the object on the server side, using a COPY request followed by +//a DELETE request on the source object. +// +//A successful move implies Invalidate() on both the source and target side. +func (o *Object) MoveTo(target *Object, headers ObjectHeaders, opts *RequestOptions) error { + err := o.CopyTo(target, headers, opts) + if err != nil { + return err + } + return o.Delete(nil, nil) +} diff --git a/tests/object_test.go b/tests/object_test.go index 740c3f2..090fc48 100644 --- a/tests/object_test.go +++ b/tests/object_test.go @@ -38,12 +38,9 @@ func TestObjectLifecycle(t *testing.T) { if o.Container() != c { t.Errorf("expected o.Container() = %#v, got %#v instead\n", c, o.Container()) } + expectObjectExistence(t, o, false) - exists, err := o.Exists() - expectSuccess(t, err) - expectBool(t, exists, false) - - _, err = o.Headers() + _, err := o.Headers() expectError(t, err, "expected 200 response, got 404 instead") expectBool(t, schwift.Is(err, http.StatusNotFound), true) expectBool(t, schwift.Is(err, http.StatusNoContent), false) @@ -56,9 +53,7 @@ func TestObjectLifecycle(t *testing.T) { err = o.Upload(bytes.NewReader([]byte("test")), nil, nil) expectSuccess(t, err) - exists, err = o.Exists() - expectSuccess(t, err) - expectBool(t, exists, true) + expectObjectExistence(t, o, true) err = o.Delete(nil, nil) expectSuccess(t, err) @@ -67,33 +62,24 @@ func TestObjectLifecycle(t *testing.T) { func TestObjectUpload(t *testing.T) { testWithContainer(t, func(c *schwift.Container) { - validateUploadedFile := func(obj *schwift.Object, expected []byte) { - str, err := obj.Download(nil, nil).AsString() - expectSuccess(t, err) - expectString(t, str, string(expected)) - obj.Invalidate() - hdr, err := obj.Headers() - expectSuccess(t, err) - expectString(t, hdr.Etag().Get(), etagOf(expected)) - } //test upload with bytes.Reader obj := c.Object("upload1") err := obj.Upload(bytes.NewReader(objectExampleContent), nil, nil) expectSuccess(t, err) - validateUploadedFile(obj, objectExampleContent) + expectObjectContent(t, obj, objectExampleContent) //test upload with bytes.Buffer obj = c.Object("upload2") err = obj.Upload(bytes.NewBuffer(objectExampleContent), nil, nil) expectSuccess(t, err) - validateUploadedFile(obj, objectExampleContent) + expectObjectContent(t, obj, objectExampleContent) //test upload with opaque io.Reader obj = c.Object("upload3") err = obj.Upload(opaqueReader{bytes.NewReader(objectExampleContent)}, nil, nil) expectSuccess(t, err) - validateUploadedFile(obj, objectExampleContent) + expectObjectContent(t, obj, objectExampleContent) //test upload with io.Writer obj = c.Object("upload4") @@ -102,19 +88,19 @@ func TestObjectUpload(t *testing.T) { return err }) expectSuccess(t, err) - validateUploadedFile(obj, objectExampleContent) + expectObjectContent(t, obj, objectExampleContent) //test upload with empty reader (should create zero-byte-sized object) obj = c.Object("upload5") err = obj.Upload(eofReader{}, nil, nil) expectSuccess(t, err) - validateUploadedFile(obj, nil) + expectObjectContent(t, obj, nil) //test upload without reader (should create zero-byte-sized object) obj = c.Object("upload6") err = obj.Upload(nil, nil, nil) expectSuccess(t, err) - validateUploadedFile(obj, nil) + expectObjectContent(t, obj, nil) }) } @@ -193,3 +179,44 @@ func TestObjectUpdate(t *testing.T) { expectString(t, hdr.ContentType().Get(), "application/json") }) } + +func TestObjectCopyMove(t *testing.T) { + testWithContainer(t, func(c *schwift.Container) { + obj1 := c.Object("location1") + err := obj1.Upload(bytes.NewReader(objectExampleContent), nil, nil) + expectSuccess(t, err) + expectObjectExistence(t, obj1, true) + + obj2 := c.Object("location2") + expectSuccess(t, obj1.CopyTo(obj2, nil, nil)) + expectObjectExistence(t, obj1, true) + expectObjectExistence(t, obj2, true) + expectObjectContent(t, obj2, objectExampleContent) + + obj3 := c.Object("location3") + expectSuccess(t, obj1.MoveTo(obj3, nil, nil)) + expectObjectExistence(t, obj1, false) + expectObjectExistence(t, obj2, true) + expectObjectExistence(t, obj3, true) + expectObjectContent(t, obj3, objectExampleContent) + }) +} + +func expectObjectExistence(t *testing.T, obj *schwift.Object, expectedExists bool) { + t.Helper() + obj.Invalidate() + actualExists, err := obj.Exists() + expectSuccess(t, err) + expectBool(t, actualExists, expectedExists) +} + +func expectObjectContent(t *testing.T, obj *schwift.Object, expected []byte) { + t.Helper() + str, err := obj.Download(nil, nil).AsString() + expectSuccess(t, err) + expectString(t, str, string(expected)) + obj.Invalidate() + hdr, err := obj.Headers() + expectSuccess(t, err) + expectString(t, hdr.Etag().Get(), etagOf(expected)) +} |
