2019-01-24 20:15:10 +00:00
|
|
|
// Copyright (C) 2019 Storj Labs, Inc.
|
2018-11-06 11:40:06 +00:00
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package kvmetainfo
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
|
2018-11-20 18:29:07 +00:00
|
|
|
"github.com/gogo/protobuf/proto"
|
2018-11-06 11:40:06 +00:00
|
|
|
|
|
|
|
"storj.io/storj/pkg/encryption"
|
2019-06-24 20:23:07 +01:00
|
|
|
"storj.io/storj/pkg/paths"
|
2018-11-06 11:40:06 +00:00
|
|
|
"storj.io/storj/pkg/pb"
|
|
|
|
"storj.io/storj/pkg/storj"
|
2019-11-14 19:46:15 +00:00
|
|
|
"storj.io/storj/private/memory"
|
2019-09-10 16:39:47 +01:00
|
|
|
"storj.io/storj/uplink/metainfo"
|
2019-07-28 06:55:36 +01:00
|
|
|
"storj.io/storj/uplink/storage/meta"
|
|
|
|
"storj.io/storj/uplink/storage/objects"
|
|
|
|
"storj.io/storj/uplink/storage/segments"
|
|
|
|
"storj.io/storj/uplink/storage/streams"
|
2018-11-06 11:40:06 +00:00
|
|
|
)
|
|
|
|
|
2019-02-04 16:56:10 +00:00
|
|
|
// DefaultRS default values for RedundancyScheme
|
|
|
|
var DefaultRS = storj.RedundancyScheme{
|
2018-11-30 13:50:52 +00:00
|
|
|
Algorithm: storj.ReedSolomon,
|
|
|
|
RequiredShares: 20,
|
|
|
|
RepairShares: 30,
|
|
|
|
OptimalShares: 40,
|
|
|
|
TotalShares: 50,
|
2019-03-18 10:55:06 +00:00
|
|
|
ShareSize: 1 * memory.KiB.Int32(),
|
2018-11-30 13:50:52 +00:00
|
|
|
}
|
|
|
|
|
2019-07-03 19:07:44 +01:00
|
|
|
// DefaultES default values for EncryptionParameters
|
2019-06-06 19:55:10 +01:00
|
|
|
// BlockSize should default to the size of a stripe
|
2019-07-03 19:07:44 +01:00
|
|
|
var DefaultES = storj.EncryptionParameters{
|
|
|
|
CipherSuite: storj.EncAESGCM,
|
|
|
|
BlockSize: DefaultRS.StripeSize(),
|
2018-11-30 13:50:52 +00:00
|
|
|
}
|
|
|
|
|
2018-11-06 11:40:06 +00:00
|
|
|
// GetObject returns information about an object
|
2019-11-15 10:06:17 +00:00
|
|
|
func (db *DB) GetObject(ctx context.Context, bucket storj.Bucket, path storj.Path) (info storj.Object, err error) {
|
2018-11-16 13:59:27 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
2019-06-24 20:23:07 +01:00
|
|
|
_, info, err = db.getInfo(ctx, bucket, path)
|
2018-11-16 13:59:27 +00:00
|
|
|
|
2018-11-06 11:40:06 +00:00
|
|
|
return info, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetObjectStream returns interface for reading the object stream
|
2019-11-24 18:26:18 +00:00
|
|
|
func (db *DB) GetObjectStream(ctx context.Context, bucket storj.Bucket, object storj.Object) (stream storj.ReadOnlyStream, err error) {
|
2018-11-16 13:59:27 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
2019-11-24 18:26:18 +00:00
|
|
|
if bucket.Name == "" {
|
|
|
|
return nil, storj.ErrNoBucket.New("")
|
2018-11-06 11:40:06 +00:00
|
|
|
}
|
|
|
|
|
2019-11-24 18:26:18 +00:00
|
|
|
if object.Path == "" {
|
|
|
|
return nil, storj.ErrNoPath.New("")
|
2018-11-06 11:40:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return &readonlyStream{
|
2019-11-24 18:26:18 +00:00
|
|
|
db: db,
|
|
|
|
info: object,
|
2018-11-06 11:40:06 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateObject creates an uploading object and returns an interface for uploading Object information
|
2019-11-15 10:06:17 +00:00
|
|
|
func (db *DB) CreateObject(ctx context.Context, bucket storj.Bucket, path storj.Path, createInfo *storj.CreateObject) (object storj.MutableObject, err error) {
|
2018-11-16 13:59:27 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
2018-11-30 13:50:52 +00:00
|
|
|
|
2019-11-15 10:06:17 +00:00
|
|
|
if bucket.Name == "" {
|
|
|
|
return nil, storj.ErrNoBucket.New("")
|
2018-11-30 13:50:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if path == "" {
|
|
|
|
return nil, storj.ErrNoPath.New("")
|
|
|
|
}
|
|
|
|
|
|
|
|
info := storj.Object{
|
2019-11-15 10:06:17 +00:00
|
|
|
Bucket: bucket,
|
2018-11-30 13:50:52 +00:00
|
|
|
Path: path,
|
|
|
|
}
|
|
|
|
|
|
|
|
if createInfo != nil {
|
|
|
|
info.Metadata = createInfo.Metadata
|
|
|
|
info.ContentType = createInfo.ContentType
|
|
|
|
info.Expires = createInfo.Expires
|
|
|
|
info.RedundancyScheme = createInfo.RedundancyScheme
|
2019-07-03 19:07:44 +01:00
|
|
|
info.EncryptionParameters = createInfo.EncryptionParameters
|
2018-11-30 13:50:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: autodetect content type from the path extension
|
|
|
|
// if info.ContentType == "" {}
|
|
|
|
|
2019-07-03 19:07:44 +01:00
|
|
|
if info.EncryptionParameters.IsZero() {
|
|
|
|
info.EncryptionParameters = storj.EncryptionParameters{
|
|
|
|
CipherSuite: DefaultES.CipherSuite,
|
|
|
|
BlockSize: DefaultES.BlockSize,
|
2019-06-06 19:55:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if info.RedundancyScheme.IsZero() {
|
|
|
|
info.RedundancyScheme = DefaultRS
|
|
|
|
|
2019-07-03 19:07:44 +01:00
|
|
|
// If the provided EncryptionParameters.BlockSize isn't a multiple of the
|
|
|
|
// DefaultRS stripeSize, then overwrite the EncryptionParameters with the DefaultES values
|
|
|
|
if err := validateBlockSize(DefaultRS, info.EncryptionParameters.BlockSize); err != nil {
|
|
|
|
info.EncryptionParameters.BlockSize = DefaultES.BlockSize
|
2018-11-30 13:50:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &mutableObject{
|
|
|
|
db: db,
|
|
|
|
info: info,
|
|
|
|
}, nil
|
2018-11-06 11:40:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ModifyObject modifies a committed object
|
2019-11-15 10:06:17 +00:00
|
|
|
func (db *DB) ModifyObject(ctx context.Context, bucket storj.Bucket, path storj.Path) (object storj.MutableObject, err error) {
|
2018-11-16 13:59:27 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
2018-11-06 11:40:06 +00:00
|
|
|
return nil, errors.New("not implemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteObject deletes an object from database
|
2019-11-07 09:39:40 +00:00
|
|
|
func (db *DB) DeleteObject(ctx context.Context, bucket storj.Bucket, path storj.Path) (err error) {
|
2018-11-16 13:59:27 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
2019-11-07 09:39:40 +00:00
|
|
|
if bucket.Name == "" {
|
|
|
|
return storj.ErrNoBucket.New("")
|
2018-11-14 09:26:18 +00:00
|
|
|
}
|
2019-11-07 09:39:40 +00:00
|
|
|
|
2019-06-21 12:29:31 +01:00
|
|
|
prefixed := prefixedObjStore{
|
2019-11-07 09:39:40 +00:00
|
|
|
store: objects.NewStore(db.streams, bucket.PathCipher),
|
|
|
|
prefix: bucket.Name,
|
2019-06-21 12:29:31 +01:00
|
|
|
}
|
|
|
|
return prefixed.Delete(ctx, path)
|
2018-11-06 11:40:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ModifyPendingObject creates an interface for updating a partially uploaded object
|
2019-11-15 10:06:17 +00:00
|
|
|
func (db *DB) ModifyPendingObject(ctx context.Context, bucket storj.Bucket, path storj.Path) (object storj.MutableObject, err error) {
|
2018-11-16 13:59:27 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
2018-11-06 11:40:06 +00:00
|
|
|
return nil, errors.New("not implemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
// ListPendingObjects lists pending objects in bucket based on the ListOptions
|
2019-11-15 10:06:17 +00:00
|
|
|
func (db *DB) ListPendingObjects(ctx context.Context, bucket storj.Bucket, options storj.ListOptions) (list storj.ObjectList, err error) {
|
2018-11-16 13:59:27 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
2018-11-06 11:40:06 +00:00
|
|
|
return storj.ObjectList{}, errors.New("not implemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
// ListObjects lists objects in bucket based on the ListOptions
|
2019-11-15 10:06:17 +00:00
|
|
|
func (db *DB) ListObjects(ctx context.Context, bucket storj.Bucket, options storj.ListOptions) (list storj.ObjectList, err error) {
|
2018-11-16 13:59:27 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
2019-11-15 10:06:17 +00:00
|
|
|
if bucket.Name == "" {
|
|
|
|
return storj.ObjectList{}, storj.ErrNoBucket.New("")
|
2018-12-03 14:38:03 +00:00
|
|
|
}
|
|
|
|
|
2019-06-21 12:29:31 +01:00
|
|
|
objects := prefixedObjStore{
|
2019-11-15 10:06:17 +00:00
|
|
|
store: objects.NewStore(db.streams, bucket.PathCipher),
|
|
|
|
prefix: bucket.Name,
|
2018-11-14 09:26:18 +00:00
|
|
|
}
|
|
|
|
|
2019-09-25 22:30:41 +01:00
|
|
|
var startAfter string
|
2018-11-06 11:40:06 +00:00
|
|
|
switch options.Direction {
|
2019-09-10 16:39:47 +01:00
|
|
|
// TODO for now we are supporting only storj.After
|
|
|
|
// case storj.Forward:
|
|
|
|
// // forward lists forwards from cursor, including cursor
|
|
|
|
// startAfter = keyBefore(options.Cursor)
|
2018-11-06 11:40:06 +00:00
|
|
|
case storj.After:
|
|
|
|
// after lists forwards from cursor, without cursor
|
|
|
|
startAfter = options.Cursor
|
|
|
|
default:
|
|
|
|
return storj.ObjectList{}, errClass.New("invalid direction %d", options.Direction)
|
|
|
|
}
|
|
|
|
|
2019-09-25 22:30:41 +01:00
|
|
|
items, more, err := objects.List(ctx, options.Prefix, startAfter, options.Recursive, options.Limit, meta.All)
|
2018-11-06 11:40:06 +00:00
|
|
|
if err != nil {
|
|
|
|
return storj.ObjectList{}, err
|
|
|
|
}
|
|
|
|
|
2018-11-16 13:59:27 +00:00
|
|
|
list = storj.ObjectList{
|
2019-11-15 10:06:17 +00:00
|
|
|
Bucket: bucket.Name,
|
2018-11-06 11:40:06 +00:00
|
|
|
Prefix: options.Prefix,
|
|
|
|
More: more,
|
|
|
|
Items: make([]storj.Object, 0, len(items)),
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, item := range items {
|
2019-11-15 10:06:17 +00:00
|
|
|
list.Items = append(list.Items, objectFromMeta(bucket, item.Path, item.IsPrefix, item.Meta))
|
2018-11-06 11:40:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return list, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type object struct {
|
2019-06-24 20:23:07 +01:00
|
|
|
fullpath streams.Path
|
|
|
|
bucket string
|
|
|
|
encPath paths.Encrypted
|
2018-11-06 11:40:06 +00:00
|
|
|
lastSegmentMeta segments.Meta
|
|
|
|
streamInfo pb.StreamInfo
|
|
|
|
streamMeta pb.StreamMeta
|
|
|
|
}
|
|
|
|
|
2019-11-15 10:06:17 +00:00
|
|
|
func (db *DB) getInfo(ctx context.Context, bucket storj.Bucket, path storj.Path) (obj object, info storj.Object, err error) {
|
2018-11-16 13:59:27 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
2019-11-15 10:06:17 +00:00
|
|
|
if bucket.Name == "" {
|
|
|
|
return object{}, storj.Object{}, storj.ErrNoBucket.New("")
|
2018-11-14 09:26:18 +00:00
|
|
|
}
|
|
|
|
|
2018-11-15 15:31:33 +00:00
|
|
|
if path == "" {
|
2018-11-30 13:50:52 +00:00
|
|
|
return object{}, storj.Object{}, storj.ErrNoPath.New("")
|
2018-11-15 15:31:33 +00:00
|
|
|
}
|
|
|
|
|
2019-11-15 10:06:17 +00:00
|
|
|
fullpath := streams.CreatePath(bucket.Name, paths.NewUnencrypted(path))
|
2018-11-06 11:40:06 +00:00
|
|
|
|
2019-11-15 10:06:17 +00:00
|
|
|
encPath, err := encryption.EncryptPath(bucket.Name, paths.NewUnencrypted(path), bucket.PathCipher, db.encStore)
|
2018-11-06 11:40:06 +00:00
|
|
|
if err != nil {
|
|
|
|
return object{}, storj.Object{}, err
|
|
|
|
}
|
|
|
|
|
2019-09-10 16:39:47 +01:00
|
|
|
objectInfo, err := db.metainfo.GetObject(ctx, metainfo.GetObjectParams{
|
2019-11-15 10:06:17 +00:00
|
|
|
Bucket: []byte(bucket.Name),
|
2019-09-10 16:39:47 +01:00
|
|
|
EncryptedPath: []byte(encPath.Raw()),
|
|
|
|
})
|
2018-11-06 11:40:06 +00:00
|
|
|
if err != nil {
|
|
|
|
return object{}, storj.Object{}, err
|
|
|
|
}
|
|
|
|
|
2019-09-10 16:39:47 +01:00
|
|
|
redundancyScheme := objectInfo.Stream.RedundancyScheme
|
2018-11-08 08:45:48 +00:00
|
|
|
|
|
|
|
lastSegmentMeta := segments.Meta{
|
2019-09-10 16:39:47 +01:00
|
|
|
Modified: objectInfo.Created,
|
|
|
|
Expiration: objectInfo.Expires,
|
|
|
|
Size: objectInfo.Size,
|
|
|
|
Data: objectInfo.Metadata,
|
2018-11-08 08:45:48 +00:00
|
|
|
}
|
|
|
|
|
2019-06-24 20:23:07 +01:00
|
|
|
streamInfoData, streamMeta, err := streams.TypedDecryptStreamInfo(ctx, lastSegmentMeta.Data, fullpath, db.encStore)
|
2018-11-06 11:40:06 +00:00
|
|
|
if err != nil {
|
|
|
|
return object{}, storj.Object{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
streamInfo := pb.StreamInfo{}
|
|
|
|
err = proto.Unmarshal(streamInfoData, &streamInfo)
|
|
|
|
if err != nil {
|
|
|
|
return object{}, storj.Object{}, err
|
|
|
|
}
|
|
|
|
|
2019-11-24 18:26:18 +00:00
|
|
|
info, err = objectStreamFromMeta(bucket, path, objectInfo.StreamID, lastSegmentMeta, streamInfo, streamMeta, redundancyScheme)
|
2018-12-07 18:31:29 +00:00
|
|
|
if err != nil {
|
|
|
|
return object{}, storj.Object{}, err
|
|
|
|
}
|
2018-11-06 11:40:06 +00:00
|
|
|
|
|
|
|
return object{
|
|
|
|
fullpath: fullpath,
|
2019-11-15 10:06:17 +00:00
|
|
|
bucket: bucket.Name,
|
2019-06-24 20:23:07 +01:00
|
|
|
encPath: encPath,
|
2018-11-06 11:40:06 +00:00
|
|
|
lastSegmentMeta: lastSegmentMeta,
|
|
|
|
streamInfo: streamInfo,
|
|
|
|
streamMeta: streamMeta,
|
|
|
|
}, info, nil
|
|
|
|
}
|
|
|
|
|
2018-12-03 14:38:03 +00:00
|
|
|
func objectFromMeta(bucket storj.Bucket, path storj.Path, isPrefix bool, meta objects.Meta) storj.Object {
|
2018-11-06 11:40:06 +00:00
|
|
|
return storj.Object{
|
|
|
|
Version: 0, // TODO:
|
|
|
|
Bucket: bucket,
|
|
|
|
Path: path,
|
|
|
|
IsPrefix: isPrefix,
|
|
|
|
|
2018-12-07 18:31:29 +00:00
|
|
|
Metadata: meta.UserDefined,
|
2018-11-06 11:40:06 +00:00
|
|
|
|
|
|
|
ContentType: meta.ContentType,
|
|
|
|
Created: meta.Modified, // TODO: use correct field
|
|
|
|
Modified: meta.Modified, // TODO: use correct field
|
|
|
|
Expires: meta.Expiration,
|
|
|
|
|
|
|
|
Stream: storj.Stream{
|
|
|
|
Size: meta.Size,
|
|
|
|
Checksum: []byte(meta.Checksum),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-24 18:26:18 +00:00
|
|
|
func objectStreamFromMeta(bucket storj.Bucket, path storj.Path, streamID storj.StreamID, lastSegment segments.Meta, stream pb.StreamInfo, streamMeta pb.StreamMeta, redundancyScheme storj.RedundancyScheme) (storj.Object, error) {
|
2018-11-06 11:40:06 +00:00
|
|
|
var nonce storj.Nonce
|
2019-06-13 16:09:05 +01:00
|
|
|
var encryptedKey storj.EncryptedPrivateKey
|
|
|
|
if streamMeta.LastSegmentMeta != nil {
|
|
|
|
copy(nonce[:], streamMeta.LastSegmentMeta.KeyNonce)
|
|
|
|
encryptedKey = streamMeta.LastSegmentMeta.EncryptedKey
|
|
|
|
}
|
2018-12-07 18:31:29 +00:00
|
|
|
|
|
|
|
serMetaInfo := pb.SerializableMeta{}
|
|
|
|
err := proto.Unmarshal(stream.Metadata, &serMetaInfo)
|
|
|
|
if err != nil {
|
|
|
|
return storj.Object{}, err
|
|
|
|
}
|
|
|
|
|
2019-08-22 22:15:58 +01:00
|
|
|
numberOfSegments := streamMeta.NumberOfSegments
|
|
|
|
if streamMeta.NumberOfSegments == 0 {
|
|
|
|
numberOfSegments = stream.DeprecatedNumberOfSegments
|
|
|
|
}
|
|
|
|
|
2018-11-06 11:40:06 +00:00
|
|
|
return storj.Object{
|
|
|
|
Version: 0, // TODO:
|
|
|
|
Bucket: bucket,
|
|
|
|
Path: path,
|
|
|
|
IsPrefix: false,
|
|
|
|
|
2018-12-07 18:31:29 +00:00
|
|
|
Metadata: serMetaInfo.UserDefined,
|
2018-11-06 11:40:06 +00:00
|
|
|
|
2018-12-07 18:31:29 +00:00
|
|
|
ContentType: serMetaInfo.ContentType,
|
|
|
|
Created: lastSegment.Modified, // TODO: use correct field
|
|
|
|
Modified: lastSegment.Modified, // TODO: use correct field
|
|
|
|
Expires: lastSegment.Expiration, // TODO: use correct field
|
2018-11-06 11:40:06 +00:00
|
|
|
|
|
|
|
Stream: storj.Stream{
|
2019-11-19 12:58:26 +00:00
|
|
|
ID: streamID,
|
2019-08-22 22:15:58 +01:00
|
|
|
Size: stream.SegmentsSize*(numberOfSegments-1) + stream.LastSegmentSize,
|
2018-11-06 11:40:06 +00:00
|
|
|
// Checksum: []byte(object.Checksum),
|
|
|
|
|
2019-08-22 22:15:58 +01:00
|
|
|
SegmentCount: numberOfSegments,
|
2018-11-06 11:40:06 +00:00
|
|
|
FixedSegmentSize: stream.SegmentsSize,
|
|
|
|
|
2019-09-10 16:39:47 +01:00
|
|
|
RedundancyScheme: redundancyScheme,
|
2019-07-03 19:07:44 +01:00
|
|
|
EncryptionParameters: storj.EncryptionParameters{
|
|
|
|
CipherSuite: storj.CipherSuite(streamMeta.EncryptionType),
|
|
|
|
BlockSize: streamMeta.EncryptionBlockSize,
|
2018-11-06 11:40:06 +00:00
|
|
|
},
|
|
|
|
LastSegment: storj.LastSegment{
|
|
|
|
Size: stream.LastSegmentSize,
|
|
|
|
EncryptedKeyNonce: nonce,
|
2019-06-13 16:09:05 +01:00
|
|
|
EncryptedKey: encryptedKey,
|
2018-11-06 11:40:06 +00:00
|
|
|
},
|
|
|
|
},
|
2018-12-07 18:31:29 +00:00
|
|
|
}, nil
|
2018-11-06 11:40:06 +00:00
|
|
|
}
|
2018-11-08 08:45:48 +00:00
|
|
|
|
2018-11-30 13:50:52 +00:00
|
|
|
type mutableObject struct {
|
|
|
|
db *DB
|
|
|
|
info storj.Object
|
|
|
|
}
|
|
|
|
|
|
|
|
func (object *mutableObject) Info() storj.Object { return object.info }
|
|
|
|
|
2019-06-04 12:36:27 +01:00
|
|
|
func (object *mutableObject) CreateStream(ctx context.Context) (_ storj.MutableStream, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2018-11-30 13:50:52 +00:00
|
|
|
return &mutableStream{
|
|
|
|
db: object.db,
|
|
|
|
info: object.info,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2019-06-04 12:36:27 +01:00
|
|
|
func (object *mutableObject) ContinueStream(ctx context.Context) (_ storj.MutableStream, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2018-11-30 13:50:52 +00:00
|
|
|
return nil, errors.New("not implemented")
|
|
|
|
}
|
|
|
|
|
2019-06-04 12:36:27 +01:00
|
|
|
func (object *mutableObject) DeleteStream(ctx context.Context) (err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2018-11-30 13:50:52 +00:00
|
|
|
return errors.New("not implemented")
|
|
|
|
}
|
|
|
|
|
2019-06-04 12:36:27 +01:00
|
|
|
func (object *mutableObject) Commit(ctx context.Context) (err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-11-15 10:06:17 +00:00
|
|
|
_, info, err := object.db.getInfo(ctx, object.info.Bucket, object.info.Path)
|
2018-12-07 18:31:29 +00:00
|
|
|
object.info = info
|
|
|
|
return err
|
2018-11-30 13:50:52 +00:00
|
|
|
}
|