blob: 287e4fda5a18fe7771c5b0fbf8f7e9994125205c [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"
"runtime"
"unsafe"
"github.com/jupiterrider/ffi"
)
// Copier drives a long-running copy operation from a source to a destination.
//
// Unlike Copy, which performs the whole copy in a single call, a Copier lets the
// caller advance the copy step by step via Next, which is useful for reporting
// progress on large copies. The copy is complete once Next reports that no more
// progress is available. Always call Close to release the resources held by the
// Copier.
//
// # Example
//
// func exampleCopier(op *opendal.Operator) {
// copier, err := op.Copier("path/from/file", "path/to/file")
// if err != nil {
// log.Fatal(err)
// }
// defer copier.Close()
// for {
// n, ok, err := copier.Next()
// if err != nil {
// log.Fatal(err)
// }
// if !ok {
// break
// }
// log.Printf("copied %d bytes", n)
// }
// }
type Copier struct {
inner *opendalCopier
ctx context.Context
}
// Copier creates a Copier to copy a file from src to dest.
//
// Copier is a wrapper around the C-binding function `opendal_operator_copier`.
// When options are provided, it uses `opendal_operator_copier_with`.
//
// # Parameters
//
// - src: The source file path.
// - dest: The destination file path.
// - opts: Optional copy options (shared with Copy).
//
// # Returns
//
// - *Copier: A Copier used to drive the copy, or an error if the operation fails.
//
// # Behavior
//
// - Both src and dest must be file paths, not directories.
// - The returned Copier must be released with Close.
func (op *Operator) Copier(src, dest string, opts ...WithCopyFn) (*Copier, error) {
if len(opts) == 0 {
inner, err := ffiOperatorCopier.symbol(op.ctx)(op.inner, src, dest)
if err != nil {
return nil, err
}
return &Copier{inner: inner, ctx: op.ctx}, nil
}
o := parseCopyOptions(opts...)
cOpts, keepAlive, err := newOpendalCopyOptions(op.ctx, o)
if err != nil {
return nil, err
}
defer ffiCopyOptionsFree.symbol(op.ctx)(cOpts)
inner, err := ffiOperatorCopierWith.symbol(op.ctx)(op.inner, src, dest, cOpts)
// cOpts holds raw pointers into the Go buffers tracked by keepAlive.
// Keep them reachable until the native call above has returned.
runtime.KeepAlive(keepAlive)
if err != nil {
return nil, err
}
return &Copier{inner: inner, ctx: op.ctx}, nil
}
// Next drives the copy forward by one step.
//
// It returns the number of bytes copied in this step. ok is false when the copy
// has completed; once that happens, further calls keep returning (0, false, nil).
// A non-nil error reports a failed copy. Calling Next on a closed Copier also
// returns (0, false, nil).
func (c *Copier) Next() (size uint, ok bool, err error) {
if c.inner == nil {
return 0, false, nil
}
return ffiCopierNext.symbol(c.ctx)(c.inner)
}
// Abort aborts the pending copy operation.
//
// After Abort, the Copier should still be released with Close. Calling Abort
// on a closed Copier is a no-op.
func (c *Copier) Abort() error {
if c.inner == nil {
return nil
}
return ffiCopierAbort.symbol(c.ctx)(c.inner)
}
// Close releases the resources held by the Copier. It must be called once the
// Copier is no longer needed. Close is safe to call more than once; subsequent
// calls are no-ops.
func (c *Copier) Close() error {
if c.inner == nil {
return nil
}
ffiCopierFree.symbol(c.ctx)(c.inner)
c.inner = nil
return nil
}
var ffiOperatorCopier = newFFI(ffiOpts{
sym: "opendal_operator_copier",
rType: &typeResultOperatorCopier,
aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer},
}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, src, dest string) (*opendalCopier, error) {
return func(op *opendalOperator, src, dest string) (*opendalCopier, error) {
byteSrc, err := BytePtrFromString(src)
if err != nil {
return nil, err
}
byteDest, err := BytePtrFromString(dest)
if err != nil {
return nil, err
}
var result resultOperatorCopier
ffiCall(
unsafe.Pointer(&result),
unsafe.Pointer(&op),
unsafe.Pointer(&byteSrc),
unsafe.Pointer(&byteDest),
)
if result.error != nil {
return nil, parseError(ctx, result.error)
}
return result.copier, nil
}
})
var ffiOperatorCopierWith = newFFI(ffiOpts{
sym: "opendal_operator_copier_with",
rType: &typeResultOperatorCopier,
aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer},
}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, src, dest string, opts *opendalCopyOptions) (*opendalCopier, error) {
return func(op *opendalOperator, src, dest string, opts *opendalCopyOptions) (*opendalCopier, error) {
byteSrc, err := BytePtrFromString(src)
if err != nil {
return nil, err
}
byteDest, err := BytePtrFromString(dest)
if err != nil {
return nil, err
}
var result resultOperatorCopier
ffiCall(
unsafe.Pointer(&result),
unsafe.Pointer(&op),
unsafe.Pointer(&byteSrc),
unsafe.Pointer(&byteDest),
unsafe.Pointer(&opts),
)
if result.error != nil {
return nil, parseError(ctx, result.error)
}
return result.copier, nil
}
})
var ffiCopierNext = newFFI(ffiOpts{
sym: "opendal_copier_next",
rType: &typeResultCopierNext,
aTypes: []*ffi.Type{&ffi.TypePointer},
}, func(ctx context.Context, ffiCall ffiCall) func(c *opendalCopier) (uint, bool, error) {
return func(c *opendalCopier) (uint, bool, error) {
var result resultCopierNext
ffiCall(
unsafe.Pointer(&result),
unsafe.Pointer(&c),
)
if result.error != nil {
return 0, false, parseError(ctx, result.error)
}
return result.size, result.has_next == 1, nil
}
})
var ffiCopierAbort = newFFI(ffiOpts{
sym: "opendal_copier_abort",
rType: &ffi.TypePointer,
aTypes: []*ffi.Type{&ffi.TypePointer},
}, func(ctx context.Context, ffiCall ffiCall) func(c *opendalCopier) error {
return func(c *opendalCopier) error {
var e *opendalError
ffiCall(
unsafe.Pointer(&e),
unsafe.Pointer(&c),
)
return parseError(ctx, e)
}
})
var ffiCopierFree = newFFI(ffiOpts{
sym: "opendal_copier_free",
rType: &ffi.TypeVoid,
aTypes: []*ffi.Type{&ffi.TypePointer},
}, func(_ context.Context, ffiCall ffiCall) func(c *opendalCopier) {
return func(c *opendalCopier) {
ffiCall(
nil,
unsafe.Pointer(&c),
)
}
})