Skip to content

Commit

Permalink
feat(bindings/go): add writer operation
Browse files Browse the repository at this point in the history
Signed-off-by: Hanchin Hsieh <[email protected]>
  • Loading branch information
yuchanns committed Sep 25, 2024
1 parent 3235893 commit bff09cd
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 0 deletions.
4 changes: 4 additions & 0 deletions bindings/go/ffi.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,8 @@ var withFFIs = []contextWithFFI{
withOperatorReader,
withReaderRead,
withReaderFree,

withOperatorWriter,
withWriterWrite,
withWriterFree,
}
28 changes: 28 additions & 0 deletions bindings/go/tests/behavior_tests/write_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func testsWrite(cap *opendal.Capability) []behaviorTest {
testWriteWithDirPath,
testWriteWithSpecialChars,
testWriteOverwrite,
testWriterWrite,
}
}

Expand Down Expand Up @@ -99,3 +100,30 @@ func testWriteOverwrite(assert *require.Assertions, op *opendal.Operator, fixtur
assert.NotEqual(contentOne, bs, "content_one must be overwrote")
assert.Equal(contentTwo, bs, "read content_two")
}

func testWriterWrite(assert *require.Assertions, op *opendal.Operator, fixture *fixture) {
if !op.Info().GetFullCapability().WriteCanMulti() {
return
}

path := fixture.NewFilePath()
size := uint(5 * 1024 * 1024)
contentA := genFixedBytes(size)
contentB := genFixedBytes(size)

w, err := op.Writer(path)
assert.Nil(err)
assert.Nil(w.Write(contentA))
assert.Nil(w.Write(contentB))
assert.Nil(w.Close())

meta, err := op.Stat(path)
assert.Nil(err, "stat must succeed")
assert.Equal(uint64(size*2), meta.ContentLength())

bs, err := op.Read(path)
assert.Nil(err, "read must succeed")
assert.Equal(uint64(size*2), uint64(len(bs)), "read size")
assert.Equal(contentA, bs[:size], "read contentA")
assert.Equal(contentB, bs[size:], "read contentB")
}
21 changes: 21 additions & 0 deletions bindings/go/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,15 @@ var (
}[0],
}

typeResultWriterWrite = ffi.Type{
Type: ffi.Struct,
Elements: &[]*ffi.Type{
&ffi.TypePointer,
&ffi.TypePointer,
nil,
}[0],
}

typeResultReaderRead = ffi.Type{
Type: ffi.Struct,
Elements: &[]*ffi.Type{
Expand Down Expand Up @@ -209,6 +218,18 @@ type resultOperatorReader struct {
error *opendalError
}

type opendalWriter struct{}

type resultOperatorWriter struct {
writer *opendalWriter
error *opendalError
}

type resultWriterWrite struct {
size uint
error *opendalError
}

type resultReaderRead struct {
size uint
error *opendalError
Expand Down
160 changes: 160 additions & 0 deletions bindings/go/write.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package opendal

import (
"context"
"io"
"unsafe"

"github.com/jupiterrider/ffi"
Expand Down Expand Up @@ -98,6 +99,94 @@ func (op *Operator) CreateDir(path string) error {
return createDir(op.inner, path)
}

// Writer returns a new Writer for the specified path.
//
// Writer is a wrapper around the C-binding function `opendal_operator_writer`.
// It provides a way to obtain a writer for writing data to the storage system.
//
// # Parameters
//
// - path: The destination path where data will be written.
//
// # Returns
//
// - *Writer: A pointer to a Writer instance, or an error if the operation fails.
//
// # Example
//
// func exampleWriter(op *opendal.Operator) {
// writer, err := op.Writer("test/")
// if err != nil {
// log.Fatal(err)
// }
// defer writer.Close()
// _, err = writer.Write([]byte("Hello opendal writer!"))
// if err != nil {
// log.Fatal(err)
// }
// }
//
// Note: This example assumes proper error handling and import statements.
func (op *Operator) Writer(path string) (*Writer, error) {
getWriter := getFFI[operatorWriter](op.ctx,
symOperatorWriter)
inner, err := getWriter(op.inner, path)
if err != nil {
return nil, err
}
writer := &Writer{
inner: inner,
ctx: op.ctx,
}
return writer, nil
}

type Writer struct {
inner *opendalWriter
ctx context.Context
}

// Write writes the given bytes to the specified path.
//
// Write is a wrapper around the C-binding function `opendal_operator_write`. It provides a simplified
// interface for writing data to the storage. Write can be called multiple times to write
// additional data to the same path.
//
// # Parameters
//
// - path: The destination path where the bytes will be written.
// - data: The byte slice containing the data to be written.
//
// # Returns
//
// - error: An error if the write operation fails, or nil if successful.
//
// # Example
//
// func exampleWrite(op *opendal.Operator) {
// err = op.Write("test", []byte("Hello opendal go binding!"))
// if err != nil {
// log.Fatal(err)
// }
// }
//
// Note: This example assumes proper error handling and import statements.
func (w *Writer) Write(p []byte) (n int, err error) {
write := getFFI[writerWrite](w.ctx, symWriterWrite)
return write(w.inner, p)
}

// Close finishes the write and releases the resources associated with the Writer.
// It is important to call Close after writing all the data to ensure that the data is
// properly flushed and written to the storage.
func (w *Writer) Close() error {
free := getFFI[writerFree](w.ctx, symWriterFree)
free(w.inner)
return nil
}

var _ io.WriteCloser = (*Writer)(nil)

const symOperatorWrite = "opendal_operator_write"

type operatorWrite func(op *opendalOperator, path string, data []byte) error
Expand Down Expand Up @@ -150,3 +239,74 @@ var withOperatorCreateDir = withFFI(ffiOpts{
return parseError(ctx, e)
}
})

const symOperatorWriter = "opendal_operator_writer"

type operatorWriter func(op *opendalOperator, path string) (*opendalWriter, error)

var withOperatorWriter = withFFI(ffiOpts{
sym: symOperatorWriter,
rType: &ffi.TypePointer,
aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer},
}, func(ctx context.Context, ffiCall func(rValue unsafe.Pointer, aValues ...unsafe.Pointer)) operatorWriter {
return func(op *opendalOperator, path string) (*opendalWriter, error) {
bytePath, err := unix.BytePtrFromString(path)
if err != nil {
return nil, err
}
var result resultOperatorWriter
ffiCall(
unsafe.Pointer(&result),
unsafe.Pointer(&op),
unsafe.Pointer(&bytePath),
)
if result.error != nil {
return nil, parseError(ctx, result.error)
}
return result.writer, nil
}
})

const symWriterFree = "opendal_writer_free"

type writerFree func(w *opendalWriter)

var withWriterFree = withFFI(ffiOpts{
sym: symWriterFree,
rType: &ffi.TypeVoid,
aTypes: []*ffi.Type{&ffi.TypePointer},
}, func(ctx context.Context, ffiCall func(rValue unsafe.Pointer, aValues ...unsafe.Pointer)) writerFree {
return func(r *opendalWriter) {
ffiCall(
nil,
unsafe.Pointer(&r),
)
}
})

const symWriterWrite = "opendal_writer_write"

type writerWrite func(r *opendalWriter, buf []byte) (size int, err error)

var withWriterWrite = withFFI(ffiOpts{
sym: symWriterWrite,
rType: &typeResultWriterWrite,
aTypes: []*ffi.Type{&ffi.TypePointer, &typeBytes},
}, func(ctx context.Context, ffiCall func(rValue unsafe.Pointer, aValues ...unsafe.Pointer)) writerWrite {
return func(r *opendalWriter, data []byte) (size int, err error) {
bytes := toOpendalBytes(data)
if len(data) > 0 {
bytes.data = &data[0]
}
var result resultWriterWrite
ffiCall(
unsafe.Pointer(&result),
unsafe.Pointer(&r),
unsafe.Pointer(&bytes),
)
if result.error != nil {
return 0, parseError(ctx, result.error)
}
return int(result.size), nil
}
})

0 comments on commit bff09cd

Please sign in to comment.