blob: a1d72a5fc04005bf663280d4ef95276411f3b428 [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 provides a Go binding for Apache OpenDAL (Open Data Access Layer).
//
// OpenDAL is a data access layer that allows users to easily interact with various
// storage services using a unified API. This Go binding enables Go developers to
// leverage OpenDAL's capabilities without the need for CGO.
//
// Key features:
// - Unified interface for multiple storage backends (e.g., S3, Azure Blob, local filesystem)
// - Native Go implementation using purego and libffi
// - No CGO dependency, ensuring better portability and easier cross-compilation
// - Supports common operations like read, write, delete, list, and metadata retrieval
//
// Basic usage:
//
// import (
// "github.com/apache/opendal/bindings/go
// "github.com/apache/opendal-go-services/memory
// )
//
// func main() {
// op, err := opendal.NewOperator(memory.Scheme, opendal.OperatorOptions{
// "root": "/path/to/root",
// })
// if err != nil {
// log.Fatal(err)
// }
// defer op.Close()
//
// // Perform operations using the operator
// err = op.Write("example.txt", []byte("Hello, OpenDAL!"))
// if err != nil {
// log.Fatal(err)
// }
// }
//
// This package aims to provide a seamless experience for Go developers working with
// various storage systems, combining the flexibility of OpenDAL with the performance
// and simplicity of native Go code.
package opendal
import (
"context"
)
// Scheme defines the interface for storage scheme implementations.
//
// A Scheme represents a specific storage backend (e.g., S3, filesystem, memory)
// and provides methods to identify and initialize the scheme.
//
// Implementations of this interface should be thread-safe, especially the LoadOnce method.
type Scheme interface {
// Name returns the unique identifier of the scheme.
Name() string
// Path returns the filesystem path where the scheme's shared library (.so) is located.
Path() string
// LoadOnce initializes the scheme. It ensures that initialization occurs only once,
// even if called multiple times. Subsequent calls after the first should be no-ops.
//
// Returns an error if initialization fails.
LoadOnce() error
}
// OperatorOptions contains configuration parameters for creating an Operator.
//
// This struct allows users to specify various settings and credentials
// required for connecting to and interacting with different storage backends.
//
// Fields in this struct vary depending on the storage scheme being used.
// Refer to the documentation of specific storage backends for details on
// required and optional fields.
//
// Example usage:
//
// options := opendal.OperatorOptions{
// "root": "/path/to/root",
// "endpoint": "https://example.com",
// "access_key_id": "your_access_key",
// "secret_access_key": "your_secret_key",
// }
type OperatorOptions map[string]string
// Operator is the entry point for all public APIs in OpenDAL.
//
// Operator provides a unified interface for interacting with various storage services.
// It encapsulates the underlying storage operations and presents a consistent API
// regardless of the storage backend.
//
// # Usage
//
// Create an Operator using NewOperator, perform operations, and always remember
// to Close the operator when finished to release resources.
//
// # Example
//
// func main() {
// // Create a new operator for the memory backend
// op, err := opendal.NewOperator(memory.Scheme, opendal.OperatorOptions{})
// if err != nil {
// log.Fatal(err)
// }
// defer op.Close() // Ensure the operator is closed when done
//
// // Perform operations using the operator
// err = op.Write("example.txt", []byte("Hello, OpenDAL!"))
// if err != nil {
// log.Fatal(err)
// }
//
// data, err := op.Read("example.txt")
// if err != nil {
// log.Fatal(err)
// }
// fmt.Println(string(data))
// }
//
// Note: Always use defer op.Close() to ensure proper resource cleanup.
//
// # Available Operations
//
// Operator provides methods for common storage operations including:
// - Read: Read data from a path
// - Write: Write data to a path
// - Stat: Get metadata for a path
// - Delete: Remove a file or directory
// - List: Enumerate entries in a directory
// - and more...
//
// Refer to the individual method documentation for detailed usage information.
type Operator struct {
ctx context.Context
cancel context.CancelFunc
inner *opendalOperator
}
// NewOperator creates and initializes a new Operator for the specified storage scheme.
//
// Parameters:
// - scheme: The storage scheme (e.g., "memory", "s3", "fs").
// - options: Configuration options for the operator.
//
// Returns:
// - *Operator: A new Operator instance.
// - error: An error if initialization fails, or nil if successful.
//
// Note: Remember to call Close() on the returned Operator when it's no longer needed.
func NewOperator(scheme Scheme, opts OperatorOptions) (op *Operator, err error) {
err = scheme.LoadOnce()
if err != nil {
return
}
ctx, cancel, err := newContext(scheme.Path())
if err != nil {
return
}
defer func() {
if err != nil {
// cancel must be called after all ffi calls
// to prevent `use after free` panics.
cancel()
}
}()
options := ffiOperatorOptionsNew.symbol(ctx)()
setOptions := ffiOperatorOptionsSet.symbol(ctx)
defer ffiOperatorOptionsFree.symbol(ctx)(options)
for key, value := range opts {
err = setOptions(options, key, value)
if err != nil {
return
}
}
inner, err := ffiOperatorNew.symbol(ctx)(scheme, options)
if err != nil {
return
}
op = &Operator{
inner: inner,
ctx: ctx,
cancel: cancel,
}
return
}
// Close releases all resources associated with the Operator.
//
// It's important to call this method when the Operator is no longer needed
// to ensure proper cleanup of underlying resources.
//
// Note: It's recommended to use defer op.Close() immediately after creating an Operator.
func (op *Operator) Close() {
ffiOperatorFree.symbol(op.ctx)(op.inner)
op.cancel()
}