diff --git a/gcsstore/gcsservice.go b/gcsstore/gcsservice.go index 2c085dfd6..0302f31db 100644 --- a/gcsstore/gcsservice.go +++ b/gcsstore/gcsservice.go @@ -59,7 +59,8 @@ type GCSReader interface { // to work with Google's cloud storage. type GCSAPI interface { ReadObject(params GCSObjectParams) (GCSReader, error) - GetObjectAttrs(params GCSObjectParams) (*storage.ObjectAttrs, error) + GetObjectSize(params GCSObjectParams) (int64, error) + SetObjectMetadata(params GCSObjectParams, metadata map[string]string) error DeleteObject(params GCSObjectParams) error DeleteObjectsWithFilter(params GCSFilterParams) error WriteObject(params GCSObjectParams, r io.Reader) (int64, error) @@ -102,9 +103,34 @@ func (service *GCSService) ReadObject(params GCSObjectParams) (GCSReader, error) return r, nil } -// GetObjectAttrs returns the associated attributes of a GCS object. +// GetObjectSize returns the byte length of the specified GCS object. +func (service *GCSService) GetObjectSize(params GCSObjectParams) (int64, error) { + attrs, err := service.getObjectAttrs(params) + if err != nil { + return 0, err + } + + return attrs.Size, nil +} + +// SetObjectMetadata sets the metadata attribute of the supplied GCS object to the passed metadata map. +func (service *GCSService) SetObjectMetadata(params GCSObjectParams, metadata map[string]string) error { + attrs := storage.ObjectAttrsToUpdate { + Metadata: metadata, + } + + obj := service.Client.Bucket(params.Bucket).Object(params.ID) + _, err := obj.Update(service.Ctx, attrs) + if err != nil { + return err + } + + return nil +} + +// getObjectAttrs returns the associated attributes of a GCS object. // https://godoc.org/cloud.google.com/go/storage#ObjectAttrs -func (service *GCSService) GetObjectAttrs(params GCSObjectParams) (*storage.ObjectAttrs, error) { +func (service *GCSService) getObjectAttrs(params GCSObjectParams) (*storage.ObjectAttrs, error) { obj := service.Client.Bucket(params.Bucket).Object(params.ID) attrs, err := obj.Attrs(service.Ctx) diff --git a/gcsstore/gcsstore.go b/gcsstore/gcsstore.go index 0e275e5c8..33c33ae0b 100644 --- a/gcsstore/gcsstore.go +++ b/gcsstore/gcsstore.go @@ -144,12 +144,12 @@ func (store GCSStore) GetInfo(id string) (tusd.FileInfo, error) { ID: name, } - attrs, err := store.Service.GetObjectAttrs(params) + size, err := store.Service.GetObjectSize(params) if err != nil { return info, err } - offset += attrs.Size + offset += size } info.Offset = offset @@ -212,6 +212,21 @@ func (store GCSStore) FinishUpload(id string) error { return err } + info, err:= store.GetInfo(id) + if err != nil { + return err + } + + objectParams := GCSObjectParams { + Bucket: store.Bucket, + ID: id, + } + + err = store.Service.SetObjectMetadata(objectParams, info.MetaData) + if err != nil { + return err + } + return nil } diff --git a/gcsstore/gcsstore_mock_test.go b/gcsstore/gcsstore_mock_test.go index e679e1293..74549ff1e 100644 --- a/gcsstore/gcsstore_mock_test.go +++ b/gcsstore/gcsstore_mock_test.go @@ -4,7 +4,6 @@ package gcsstore_test import ( - storage "cloud.google.com/go/storage" gomock "github.com/golang/mock/gomock" gcsstore "github.com/tus/tusd/gcsstore" io "io" @@ -144,15 +143,15 @@ func (_mr *_MockGCSAPIRecorder) FilterObjects(arg0 interface{}) *gomock.Call { return _mr.mock.ctrl.RecordCall(_mr.mock, "FilterObjects", arg0) } -func (_m *MockGCSAPI) GetObjectAttrs(_param0 gcsstore.GCSObjectParams) (*storage.ObjectAttrs, error) { - ret := _m.ctrl.Call(_m, "GetObjectAttrs", _param0) - ret0, _ := ret[0].(*storage.ObjectAttrs) +func (_m *MockGCSAPI) GetObjectSize(_param0 gcsstore.GCSObjectParams) (int64, error) { + ret := _m.ctrl.Call(_m, "GetObjectSize", _param0) + ret0, _ := ret[0].(int64) ret1, _ := ret[1].(error) return ret0, ret1 } -func (_mr *_MockGCSAPIRecorder) GetObjectAttrs(arg0 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "GetObjectAttrs", arg0) +func (_mr *_MockGCSAPIRecorder) GetObjectSize(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "GetObjectSize", arg0) } func (_m *MockGCSAPI) ReadObject(_param0 gcsstore.GCSObjectParams) (gcsstore.GCSReader, error) { @@ -166,6 +165,16 @@ func (_mr *_MockGCSAPIRecorder) ReadObject(arg0 interface{}) *gomock.Call { return _mr.mock.ctrl.RecordCall(_mr.mock, "ReadObject", arg0) } +func (_m *MockGCSAPI) SetObjectMetadata(_param0 gcsstore.GCSObjectParams, _param1 map[string]string) error { + ret := _m.ctrl.Call(_m, "SetObjectMetadata", _param0, _param1) + ret0, _ := ret[0].(error) + return ret0 +} + +func (_mr *_MockGCSAPIRecorder) SetObjectMetadata(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "SetObjectMetadata", arg0, arg1) +} + func (_m *MockGCSAPI) WriteObject(_param0 gcsstore.GCSObjectParams, _param1 io.Reader) (int64, error) { ret := _m.ctrl.Call(_m, "WriteObject", _param0, _param1) ret0, _ := ret[0].(int64) diff --git a/gcsstore/gcsstore_test.go b/gcsstore/gcsstore_test.go index 6f984194a..99e645747 100644 --- a/gcsstore/gcsstore_test.go +++ b/gcsstore/gcsstore_test.go @@ -100,7 +100,43 @@ func TestGetInfo(t *testing.T) { r := MockGetInfoReader{} - service.EXPECT().ReadObject(params).Return(r, nil) + filterParams := gcsstore.GCSFilterParams { + Bucket: store.Bucket, + Prefix: fmt.Sprintf("%s_", mockID), + } + + mockObjectParams0 := gcsstore.GCSObjectParams { + Bucket: store.Bucket, + ID: mockPartial0, + } + + mockObjectParams1 := gcsstore.GCSObjectParams { + Bucket: store.Bucket, + ID: mockPartial1, + } + + mockObjectParams2 := gcsstore.GCSObjectParams { + Bucket: store.Bucket, + ID: mockPartial2, + } + + var size int64 = 100; + + mockTusdInfo.Offset = 300 + offsetInfoData, err := json.Marshal(mockTusdInfo) + assert.Nil(err) + + infoR := bytes.NewReader(offsetInfoData) + + gomock.InOrder( + service.EXPECT().ReadObject(params).Return(r, nil), + service.EXPECT().FilterObjects(filterParams).Return(mockPartials, nil), + service.EXPECT().GetObjectSize(mockObjectParams0).Return(size, nil), + service.EXPECT().GetObjectSize(mockObjectParams1).Return(size, nil), + service.EXPECT().GetObjectSize(mockObjectParams2).Return(size, nil), + service.EXPECT().WriteObject(params, infoR).Return(int64(len(offsetInfoData)), nil), + ) + info, err := store.GetInfo(mockID) assert.Nil(err) assert.Equal(mockTusdInfo, info) @@ -171,27 +207,7 @@ func TestTerminate(t *testing.T) { Prefix: mockID, } - mockObjectParams0 := gcsstore.GCSObjectParams { - Bucket: store.Bucket, - ID: mockPartial0, - } - - mockObjectParams1 := gcsstore.GCSObjectParams { - Bucket: store.Bucket, - ID: mockPartial1, - } - - mockObjectParams2 := gcsstore.GCSObjectParams { - Bucket: store.Bucket, - ID: mockPartial2, - } - - gomock.InOrder( - service.EXPECT().FilterObjects(filterParams).Return(mockPartials, nil), - service.EXPECT().DeleteObject(mockObjectParams0).Return(nil), - service.EXPECT().DeleteObject(mockObjectParams1).Return(nil), - service.EXPECT().DeleteObject(mockObjectParams2).Return(nil), - ) + service.EXPECT().DeleteObjectsWithFilter(filterParams).Return(nil) err := store.Terminate(mockID) assert.Nil(err) @@ -218,6 +234,13 @@ func TestFinishUpload(t *testing.T) { Sources: mockPartials, } + infoParams := gcsstore.GCSObjectParams { + Bucket: store.Bucket, + ID: fmt.Sprintf("%s.info", mockID), + } + + r := MockGetInfoReader{} + mockObjectParams0 := gcsstore.GCSObjectParams { Bucket: store.Bucket, ID: mockPartial0, @@ -233,15 +256,37 @@ func TestFinishUpload(t *testing.T) { ID: mockPartial2, } + var size int64 = 100; + + mockTusdInfo.Offset = 300 + offsetInfoData, err := json.Marshal(mockTusdInfo) + assert.Nil(err) + + infoR := bytes.NewReader(offsetInfoData) + + objectParams := gcsstore.GCSObjectParams { + Bucket: store.Bucket, + ID: mockID, + } + + metadata := map[string]string { + "foo": "bar", + } + gomock.InOrder( service.EXPECT().FilterObjects(filterParams).Return(mockPartials, nil), service.EXPECT().ComposeObjects(composeParams).Return(nil), - service.EXPECT().DeleteObject(mockObjectParams0).Return(nil), - service.EXPECT().DeleteObject(mockObjectParams1).Return(nil), - service.EXPECT().DeleteObject(mockObjectParams2).Return(nil), + service.EXPECT().DeleteObjectsWithFilter(filterParams).Return(nil), + service.EXPECT().ReadObject(infoParams).Return(r, nil), + service.EXPECT().FilterObjects(filterParams).Return(mockPartials, nil), + service.EXPECT().GetObjectSize(mockObjectParams0).Return(size, nil), + service.EXPECT().GetObjectSize(mockObjectParams1).Return(size, nil), + service.EXPECT().GetObjectSize(mockObjectParams2).Return(size, nil), + service.EXPECT().WriteObject(infoParams, infoR).Return(int64(len(offsetInfoData)), nil), + service.EXPECT().SetObjectMetadata(objectParams, metadata).Return(nil), ) - err := store.FinishUpload(mockID) + err = store.FinishUpload(mockID) assert.Nil(err) } @@ -286,15 +331,7 @@ func TestWriteChunk(t *testing.T) { service := NewMockGCSAPI(mockCtrl) store := gcsstore.New(mockBucket, service) - assert.Equal(store.Bucket, mockBucket); - - // First get info - getInfoObjectParams := gcsstore.GCSObjectParams { - Bucket: store.Bucket, - ID: fmt.Sprintf("%s.info", mockID), - } - - rInfo := MockWriteChunkReader{} + assert.Equal(store.Bucket, mockBucket) // filter objects filterParams := gcsstore.GCSFilterParams { @@ -312,28 +349,15 @@ func TestWriteChunk(t *testing.T) { rGet := bytes.NewReader([]byte(mockReaderData)) - // write info - writeInfoParams := gcsstore.GCSObjectParams { - Bucket: store.Bucket, - ID: fmt.Sprintf("%s.info", mockID), - } - - chunk1InfoData, err := json.Marshal(mockTusdChunk1Info) - assert.Nil(err) - - rChunk := bytes.NewReader(chunk1InfoData) - gomock.InOrder( - service.EXPECT().ReadObject(getInfoObjectParams).Return(rInfo, nil), service.EXPECT().FilterObjects(filterParams).Return(partials, nil), service.EXPECT().WriteObject(writeObjectParams, rGet).Return(int64(len(mockReaderData)), nil), - service.EXPECT().WriteObject(writeInfoParams, rChunk).Return(int64(len(chunk1InfoData)), nil), ) reader := bytes.NewReader([]byte(mockReaderData)) var offset int64; offset = mockSize / 3 - _, err = store.WriteChunk(mockID, offset, reader) + _, err := store.WriteChunk(mockID, offset, reader) assert.Nil(err) }