bindings/go/reader.go (150 lines of code) (raw):
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package opendal
import (
"context"
"io"
"unsafe"
"github.com/jupiterrider/ffi"
)
// Read reads the entire contents of the file at the specified path into a byte slice.
//
// This function is a wrapper around the C-binding function `opendal_operator_read`.
//
// # Parameters
//
// - path: The path of the file to read.
//
// # Returns
//
// - []byte: The contents of the file as a byte slice.
// - error: An error if the read operation fails, or nil if successful.
//
// # Notes
//
// - This implementation does not support the `read_with` functionality.
// - Read allocates a new byte slice internally. For more precise memory control
// or lazy reading, consider using the Reader() method instead.
//
// # Example
//
// func exampleRead(op *opendal.Operator) {
// data, err := op.Read("test")
// if err != nil {
// log.Fatal(err)
// }
// fmt.Printf("Read: %s\n", data)
// }
//
// Note: This example assumes proper error handling and import statements.
func (op *Operator) Read(path string) ([]byte, error) {
read := getFFI[operatorRead](op.ctx, symOperatorRead)
bytes, err := read(op.inner, path)
if err != nil {
return nil, err
}
data := parseBytes(bytes)
if len(data) > 0 {
free := getFFI[bytesFree](op.ctx, symBytesFree)
free(&bytes)
}
return data, nil
}
// Reader creates a new Reader for reading the contents of a file at the specified path.
//
// This function is a wrapper around the C-binding function `opendal_operator_reader`.
//
// # Parameters
//
// - path: The path of the file to read.
//
// # Returns
//
// - *Reader: A reader for accessing the file's contents. It implements `io.ReadCloser`.
// - error: An error if the reader creation fails, or nil if successful.
//
// # Notes
//
// - This implementation does not support the `reader_with` functionality.
// - The returned reader allows for more controlled and efficient reading of large files.
//
// # Example
//
// func exampleReader(op *opendal.Operator) {
// r, err := op.Reader("path/to/file")
// if err != nil {
// log.Fatal(err)
// }
// defer r.Close()
//
// size := 1024 // Read 1KB at a time
// buffer := make([]byte, size)
//
// for {
// n, err := r.Read(buffer)
// if err != nil {
// log.Fatal(err)
// }
// fmt.Printf("Read %d bytes: %s\n", n, buffer[:n])
// }
// }
//
// Note: This example assumes proper error handling and import statements.
func (op *Operator) Reader(path string) (*Reader, error) {
getReader := getFFI[operatorReader](op.ctx, symOperatorReader)
inner, err := getReader(op.inner, path)
if err != nil {
return nil, err
}
reader := &Reader{
inner: inner,
ctx: op.ctx,
}
return reader, nil
}
type Reader struct {
inner *opendalReader
ctx context.Context
}
var _ io.ReadCloser = (*Reader)(nil)
// Read reads data from the underlying storage into the provided buffer.
//
// This method implements the io.Reader interface for OperatorReader.
//
// # Parameters
//
// - buf: A pre-allocated byte slice where the read data will be stored.
// The length of buf determines the maximum number of bytes to read.
//
// # Returns
//
// - int: The number of bytes read. Returns 0 if no data is available or the end of the file is reached.
// - error: An error if the read operation fails, or nil if successful.
// Note that this method does not return io.EOF; it returns nil at the end of the file.
//
// # Notes
//
// - The caller is responsible for pre-allocating the buffer and determining its size.
//
// # Example
//
// reader, err := op.Reader("path/to/file")
// if err != nil {
// log.Fatal(err)
// }
// defer reader.Close()
//
// buf := make([]byte, 1024)
// for {
// n, err := reader.Read(buf)
// if err != nil {
// log.Fatal(err)
// }
// if n == 0 {
// break // End of file
// }
// // Process buf[:n]
// }
//
// Note: Always check the number of bytes read (n) as it may be less than len(buf).
func (r *Reader) Read(buf []byte) (int, error) {
length := uint(len(buf))
if length == 0 {
return 0, nil
}
read := getFFI[readerRead](r.ctx, symReaderRead)
var (
totalSize uint
size uint
err error
)
for {
size, err = read(r.inner, buf[totalSize:])
totalSize += size
if size == 0 || err != nil || totalSize >= length {
break
}
}
if totalSize == 0 && err == nil {
err = io.EOF
}
return int(totalSize), err
}
// Close releases resources associated with the OperatorReader.
func (r *Reader) Close() error {
free := getFFI[readerFree](r.ctx, symReaderFree)
free(r.inner)
return nil
}
const symOperatorRead = "opendal_operator_read"
type operatorRead func(op *opendalOperator, path string) (opendalBytes, error)
var withOperatorRead = withFFI(ffiOpts{
sym: symOperatorRead,
rType: &typeResultRead,
aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer},
}, func(ctx context.Context, ffiCall func(rValue unsafe.Pointer, aValues ...unsafe.Pointer)) operatorRead {
return func(op *opendalOperator, path string) (opendalBytes, error) {
bytePath, err := BytePtrFromString(path)
if err != nil {
return opendalBytes{}, err
}
var result resultRead
ffiCall(
unsafe.Pointer(&result),
unsafe.Pointer(&op),
unsafe.Pointer(&bytePath),
)
return result.data, parseError(ctx, result.error)
}
})
const symOperatorReader = "opendal_operator_reader"
type operatorReader func(op *opendalOperator, path string) (*opendalReader, error)
var withOperatorReader = withFFI(ffiOpts{
sym: symOperatorReader,
rType: &typeResultOperatorReader,
aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer},
}, func(ctx context.Context, ffiCall func(rValue unsafe.Pointer, aValues ...unsafe.Pointer)) operatorReader {
return func(op *opendalOperator, path string) (*opendalReader, error) {
bytePath, err := BytePtrFromString(path)
if err != nil {
return nil, err
}
var result resultOperatorReader
ffiCall(
unsafe.Pointer(&result),
unsafe.Pointer(&op),
unsafe.Pointer(&bytePath),
)
if result.error != nil {
return nil, parseError(ctx, result.error)
}
return result.reader, nil
}
})
const symReaderFree = "opendal_reader_free"
type readerFree func(r *opendalReader)
var withReaderFree = withFFI(ffiOpts{
sym: symReaderFree,
rType: &ffi.TypeVoid,
aTypes: []*ffi.Type{&ffi.TypePointer},
}, func(ctx context.Context, ffiCall func(rValue unsafe.Pointer, aValues ...unsafe.Pointer)) readerFree {
return func(r *opendalReader) {
ffiCall(
nil,
unsafe.Pointer(&r),
)
}
})
const symReaderRead = "opendal_reader_read"
type readerRead func(r *opendalReader, buf []byte) (size uint, err error)
var withReaderRead = withFFI(ffiOpts{
sym: symReaderRead,
rType: &typeResultReaderRead,
aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer},
}, func(ctx context.Context, ffiCall func(rValue unsafe.Pointer, aValues ...unsafe.Pointer)) readerRead {
return func(r *opendalReader, buf []byte) (size uint, err error) {
var length = len(buf)
if length == 0 {
return 0, nil
}
bytePtr := &buf[0]
var result resultReaderRead
ffiCall(
unsafe.Pointer(&result),
unsafe.Pointer(&r),
unsafe.Pointer(&bytePtr),
unsafe.Pointer(&length),
)
if result.error != nil {
return 0, parseError(ctx, result.error)
}
return result.size, nil
}
})