blob: 346bce199abd016d1e7489f10458c6dc1144dc54 [file]
/*
* 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) {
bytes, err := ffiOperatorRead.symbol(op.ctx)(op.inner, path)
if err != nil {
return nil, err
}
data := parseBytes(bytes)
if len(data) > 0 {
ffiBytesFree.symbol(op.ctx)(&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) {
inner, err := ffiOperatorReader.symbol(op.ctx)(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.ReadSeekCloser = (*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 := ffiReaderRead.symbol(r.ctx)
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
}
// Seek sets the offset for the next Read operation on the reader.
//
// This method implements the io.Seeker interface for Reader.
//
// # Parameters
//
// - offset: The offset from the origin (specified by whence).
// - whence: The reference point for offset. Can be:
// - io.SeekStart (0): Relative to the start of the file
// - io.SeekCurrent (1): Relative to the current position
// - io.SeekEnd (2): Relative to the end of the file
//
// # Returns
//
// - int64: The new absolute position in the file after the seek operation.
// - error: An error if the seek operation fails, or nil if successful.
//
// # Example
//
// reader, err := op.Reader("path/to/file")
// if err != nil {
// log.Fatal(err)
// }
// defer reader.Close()
//
// // Seek to the middle of the file
// pos, err := reader.Seek(1000, io.SeekStart)
// if err != nil {
// log.Fatal(err)
// }
// fmt.Printf("New position: %d\n", pos)
//
// // Seek relative to current position
// pos, err = reader.Seek(100, io.SeekCurrent)
// if err != nil {
// log.Fatal(err)
// }
// fmt.Printf("New position: %d\n", pos)
//
// Note: The actual new position may differ from the requested position
// if the underlying storage system has restrictions on seeking.
func (r *Reader) Seek(offset int64, whence int) (int64, error) {
return ffiReaderSeek.symbol(r.ctx)(r.inner, offset, whence)
}
// Close releases resources associated with the OperatorReader.
func (r *Reader) Close() error {
ffiReaderFree.symbol(r.ctx)(r.inner)
return nil
}
var ffiOperatorRead = newFFI(ffiOpts{
sym: "opendal_operator_read",
rType: &typeResultRead,
aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer},
}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string) (opendalBytes, error) {
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)
}
})
var ffiOperatorReader = newFFI(ffiOpts{
sym: "opendal_operator_reader",
rType: &typeResultOperatorReader,
aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer},
}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string) (*opendalReader, error) {
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
}
})
var ffiReaderFree = newFFI(ffiOpts{
sym: "opendal_reader_free",
rType: &ffi.TypeVoid,
aTypes: []*ffi.Type{&ffi.TypePointer},
}, func(ctx context.Context, ffiCall ffiCall) func(r *opendalReader) {
return func(r *opendalReader) {
ffiCall(
nil,
unsafe.Pointer(&r),
)
}
})
var ffiReaderRead = newFFI(ffiOpts{
sym: "opendal_reader_read",
rType: &typeResultReaderRead,
aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer},
}, func(ctx context.Context, ffiCall ffiCall) func(r *opendalReader, buf []byte) (size uint, err error) {
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
}
})
var ffiReaderSeek = newFFI(ffiOpts{
sym: "opendal_reader_seek",
rType: &typeResultReaderSeek,
aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer},
}, func(ctx context.Context, ffiCall ffiCall) func(r *opendalReader, offset int64, whence int) (int64, error) {
return func(r *opendalReader, offset int64, whence int) (int64, error) {
var result resultReaderSeek
ffiCall(
unsafe.Pointer(&result),
unsafe.Pointer(&r),
unsafe.Pointer(&offset),
unsafe.Pointer(&whence),
)
if result.error != nil {
return 0, parseError(ctx, result.error)
}
return int64(result.pos), nil
}
})