aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--object.go63
-rw-r--r--tests/object_test.go73
2 files changed, 105 insertions, 31 deletions
diff --git a/object.go b/object.go
index ca049f4..b4bf41e 100644
--- a/object.go
+++ b/object.go
@@ -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))
+}