blob: 93dd0ce2511031975511f64328526f4414ac6c30 [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.
*/
using System.Buffers;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using OpenDAL.Interop.Buffers;
using OpenDAL.Interop.NativeObject;
using OpenDAL.Interop.Result;
using OpenDAL.Interop.Result.Abstractions;
using OpenDAL.Layer.Abstractions;
using OpenDAL.Options;
using OpenDAL.Options.Abstractions;
using OpenDAL.ServiceConfig.Abstractions;
using System.Diagnostics.CodeAnalysis;
namespace OpenDAL;
/// <summary>
/// Managed wrapper over an OpenDAL native operator handle.
/// </summary>
public partial class Operator : SafeHandle
{
private Lazy<OperatorInfo> info;
private Operator() : base(IntPtr.Zero, true)
{
info = CreateInfoLazy();
}
private Operator(IntPtr nativeHandle) : this()
{
if (nativeHandle == IntPtr.Zero)
{
throw new ArgumentException("Native operator handle must not be zero.", nameof(nativeHandle));
}
SetHandle(nativeHandle);
}
/// <summary>
/// Gets metadata of this operator.
/// </summary>
/// <exception cref="ObjectDisposedException">The operator has been disposed.</exception>
/// <exception cref="OpenDALException">Native operator info retrieval fails.</exception>
public OperatorInfo Info
{
get
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
return info.Value;
}
}
/// <summary>
/// Gets whether the native handle is invalid.
/// </summary>
public override bool IsInvalid => handle == IntPtr.Zero;
/// <summary>
/// Creates an operator for the specified backend scheme and options.
/// </summary>
/// <remarks>
/// Available scheme names are defined by OpenDAL.
/// See <see href="https://docs.rs/opendal/latest/opendal/enum.Scheme.html">OpenDAL Scheme documentation</see>
/// for supported backends and their related configuration options.
/// </remarks>
/// <param name="scheme">Name of the backend service, such as <c>fs</c> or <c>memory</c>.</param>
/// <param name="options">Key/value options used to configure the selected backend service.</param>
/// <exception cref="ArgumentException"><paramref name="scheme"/> is null, empty, or whitespace.</exception>
/// <exception cref="ObjectDisposedException"><paramref name="executor"/> has been disposed.</exception>
/// <exception cref="OpenDALException">Native operator construction fails.</exception>
public Operator(
string scheme,
IReadOnlyDictionary<string, string>? options = null,
Executor? executor = null) : base(IntPtr.Zero, true)
{
ArgumentException.ThrowIfNullOrWhiteSpace(scheme);
info = CreateInfoLazy();
using var nativeOptionsHandle = CreateConstructorOptionsHandle(options);
var executorAddRefed = false;
try
{
executor?.DangerousAddRef(ref executorAddRefed);
var result = NativeMethods.operator_construct(
scheme,
GetOptionsHandle(nativeOptionsHandle),
executor?.DangerousGetHandle() ?? IntPtr.Zero);
SetHandle(ToValueOrThrowAndRelease<IntPtr, OpenDALOperatorResult>(result));
}
finally
{
if (executorAddRefed)
{
executor!.DangerousRelease();
}
}
}
/// <summary>
/// Creates an operator from a typed service configuration.
/// </summary>
/// <remarks>
/// This overload converts <paramref name="config"/> into backend key/value options internally,
/// then creates the same native operator as <see cref="Operator(string, IReadOnlyDictionary{string, string}?)"/>.
/// </remarks>
/// <param name="config">Typed service configuration for the target backend service.</param>
/// <exception cref="ArgumentNullException"><paramref name="config"/> is null.</exception>
/// <exception cref="OpenDALException">Native operator construction fails.</exception>
public Operator(IServiceConfig config, Executor? executor = null) : this(
config?.Scheme ?? throw new ArgumentNullException(nameof(config)),
config.ToOptions(),
executor)
{
}
/// <summary>
/// Applies the specified layer and returns a new operator instance.
/// </summary>
/// <param name="layer">Layer to apply.</param>
/// <returns>A new operator with the layer applied.</returns>
/// <exception cref="ArgumentNullException"><paramref name="layer"/> is null.</exception>
/// <exception cref="ObjectDisposedException">The operator has been disposed.</exception>
/// <exception cref="OpenDALException">Native layer application fails.</exception>
public Operator WithLayer(ILayer layer)
{
ArgumentNullException.ThrowIfNull(layer);
ObjectDisposedException.ThrowIf(IsInvalid, this);
return layer.Apply(this);
}
/// <summary>
/// Writes the specified content to a path with write options.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="content">Bytes to write.</param>
/// <param name="options">Additional write options.</param>
public void Write(string path, byte[] content, WriteOptions? options = null)
{
ArgumentNullException.ThrowIfNull(content);
ObjectDisposedException.ThrowIf(IsInvalid, this);
using var nativeOptionsHandle = options?.BuildNativeOptionsHandle();
var result = NativeMethods.operator_write_bytes_with_options(
this,
path,
content,
(nuint)content.Length,
GetOptionsHandle(nativeOptionsHandle)
);
ThrowIfErrorAndRelease(result);
}
/// <summary>
/// Writes a payload produced directly into native memory, without a
/// managed copy.
/// </summary>
/// <remarks>
/// <paramref name="fill"/> runs synchronously against a native-backed
/// <see cref="IBufferWriter{T}"/>, so standard producers such as
/// <c>System.Text.Json.Utf8JsonWriter</c> serialize straight into the
/// write payload. Everything committed when it returns becomes the
/// payload of a single write. The buffer never leaves this call: it
/// grows on demand while filling and is released once the backend
/// finishes with the payload.
/// </remarks>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="fill">Producer invoked once to fill the payload.</param>
/// <param name="sizeHint">
/// Expected payload size in bytes, or 0 for the default. A positive hint
/// sizes the first native segment so a well-estimated payload fills it
/// without growing; it is an estimate, not a limit.
/// </param>
/// <param name="options">Additional write options, or <see langword="null"/> for default behavior.</param>
/// <exception cref="ArgumentNullException"><paramref name="fill"/> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="sizeHint"/> is negative.</exception>
/// <exception cref="ObjectDisposedException">The operator or executor has been disposed.</exception>
/// <exception cref="OpenDALException">Native write fails.</exception>
public void Write(
string path,
Action<IBufferWriter<byte>> fill,
int sizeHint = 0,
WriteOptions? options = null)
{
ArgumentNullException.ThrowIfNull(fill);
ArgumentOutOfRangeException.ThrowIfNegative(sizeHint);
using var buffer = AllocateWriteBuffer(
sizeHint > 0 ? sizeHint : WriteBuffer.DefaultInitialCapacity);
fill(buffer);
Write(path, buffer, options);
}
/// <summary>
/// Writes the bytes committed to an allocated native buffer to a path
/// synchronously, transferring ownership of its contents to the native
/// layer without copying.
/// </summary>
/// <remarks>
/// On success the write consumes the buffer contents: every later access
/// throws, and the memory is released once the buffer is disposed and
/// the backend finishes with the payload. An error raised before the
/// payload is taken leaves the buffer usable, but a backend failure
/// after that point has already consumed it, so treat a failed buffer
/// as spent.
/// </remarks>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="content">Allocated buffer holding the bytes to write.</param>
/// <param name="options">Additional write options, or <see langword="null"/> for default behavior.</param>
/// <exception cref="ObjectDisposedException">The operator or buffer has been disposed.</exception>
/// <exception cref="InvalidOperationException">The buffer contents were already consumed by a write.</exception>
/// <exception cref="OpenDALException">Native write fails.</exception>
internal void Write(string path, WriteBuffer content, WriteOptions? options = null)
{
ArgumentNullException.ThrowIfNull(content);
ObjectDisposedException.ThrowIf(IsInvalid, this);
var bufferHandle = content.Handle;
var committedInTail = content.TailWritten;
using var nativeOptionsHandle = options?.BuildNativeOptionsHandle();
var result = NativeMethods.operator_write_with_options(
this,
path,
bufferHandle,
(nuint)committedInTail,
GetOptionsHandle(nativeOptionsHandle)
);
if (!result.Error.IsError)
{
content.MarkConsumed();
}
ThrowIfErrorAndRelease(result);
}
/// <summary>
/// Writes the specified content to a path asynchronously.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="content">Bytes to write.</param>
/// <param name="cancellationToken">Cancellation token for the managed task.</param>
public Task WriteAsync(string path, byte[] content, CancellationToken cancellationToken)
{
return WriteAsync(path, content, options: null, cancellationToken);
}
/// <summary>
/// Writes the specified content to a path asynchronously with optional write options and executor.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="content">Bytes to write.</param>
/// <param name="options">Additional write options, or <see langword="null"/> for default behavior.</param>
/// <param name="cancellationToken">Cancellation token for the managed task.</param>
/// <returns>A task that completes when the native callback reports completion.</returns>
public Task WriteAsync(
string path,
byte[] content,
WriteOptions? options = null,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(content);
ObjectDisposedException.ThrowIf(IsInvalid, this);
return SubmitAsyncOperation<bool, WriteOptions>(options, DispatchWriteBytesAsync, cancellationToken);
OpenDALResult DispatchWriteBytesAsync(long context, IntPtr optionsHandle)
{
unsafe
{
return NativeMethods.operator_write_bytes_with_options_async(
this,
path,
content,
(nuint)content.Length,
optionsHandle,
&OnWriteCompleted,
context
);
}
}
}
/// <summary>
/// Allocates a native <see cref="System.Buffers.IBufferWriter{T}"/> for a
/// zero-copy write.
/// </summary>
/// <remarks>
/// Produce into the buffer through the <see cref="System.Buffers.IBufferWriter{T}"/>
/// contract — for example with <c>System.Text.Json.Utf8JsonWriter</c> —
/// then hand it to <c>Write(path, buffer)</c> or
/// <c>WriteAsync(path, buffer)</c>. The buffer grows on demand, so
/// <paramref name="initialCapacity"/> is a hint, not a limit. The write
/// consumes the buffer; allocate a new one for the next write. Dispose
/// an unconsumed buffer to return the memory.
/// </remarks>
/// <param name="initialCapacity">Size of the first segment in bytes.</param>
/// <returns>An empty native buffer writer.</returns>
/// <exception cref="ObjectDisposedException">The operator has been disposed.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="initialCapacity"/> is not positive.</exception>
/// <exception cref="OpenDALException">Native buffer allocation fails.</exception>
internal WriteBuffer AllocateWriteBuffer(int initialCapacity = WriteBuffer.DefaultInitialCapacity)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(initialCapacity);
var result = NativeMethods.write_buffer_create((nuint)initialCapacity);
var buffer = ToValueOrThrowAndRelease<OpenDALWriteBuffer, OpenDALWriteBufferResult>(result);
return new WriteBuffer(buffer.Handle, buffer.Data, checked((int)buffer.Capacity));
}
/// <summary>
/// Writes the bytes committed to an allocated native buffer to a path
/// asynchronously, transferring ownership of its contents to the native
/// layer without copying.
/// </summary>
/// <remarks>
/// Everything committed with <see cref="WriteBuffer.Advance"/> is
/// written, in order. On success the buffer is consumed: every later
/// access to it throws, and its memory is released once the buffer is
/// disposed and the backend finishes with the payload. When dispatch
/// fails immediately, ownership does not transfer and the buffer stays
/// usable.
/// </remarks>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="content">Allocated buffer holding the bytes to write.</param>
/// <param name="options">Additional write options, or <see langword="null"/> for default behavior.</param>
/// <param name="cancellationToken">Cancellation token for the managed task.</param>
/// <returns>A task that completes when the native callback reports completion.</returns>
/// <exception cref="ObjectDisposedException">The operator or buffer has been disposed.</exception>
/// <exception cref="InvalidOperationException">The buffer contents were already consumed by a write.</exception>
/// <exception cref="OperationCanceledException"><paramref name="cancellationToken"/> is already canceled.</exception>
/// <exception cref="OpenDALException">Native write submission fails immediately.</exception>
internal Task WriteAsync(
string path,
WriteBuffer content,
WriteOptions? options = null,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(content);
ObjectDisposedException.ThrowIf(IsInvalid, this);
// Surfaces disposed/already-consumed as managed exceptions before the
// native call; the native slot enforces the same contract again.
var bufferHandle = content.Handle;
var committedInTail = content.TailWritten;
return SubmitAsyncOperation<bool, WriteOptions>(options, DispatchWriteBufferAsync, cancellationToken);
OpenDALResult DispatchWriteBufferAsync(long context, IntPtr optionsHandle)
{
unsafe
{
var result = NativeMethods.operator_write_with_options_async(
this,
path,
bufferHandle,
(nuint)committedInTail,
optionsHandle,
&OnWriteCompleted,
context
);
if (!result.Error.IsError)
{
content.MarkConsumed();
}
return result;
}
}
}
/// <summary>
/// Writes a payload produced directly into native memory, without a
/// managed copy.
/// </summary>
/// <remarks>
/// <paramref name="fill"/> runs synchronously against a native-backed
/// <see cref="IBufferWriter{T}"/>, so standard producers such as
/// <c>System.Text.Json.Utf8JsonWriter</c> serialize straight into the
/// write payload. Everything committed when it returns becomes the
/// payload of a single write. The buffer never leaves this call: it
/// grows on demand while filling and is released once the backend
/// finishes with the payload.
/// </remarks>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="fill">Producer invoked once to fill the payload.</param>
/// <param name="sizeHint">
/// Expected payload size in bytes, or 0 for the default. A positive hint
/// sizes the first native segment so a well-estimated payload fills it
/// without growing; it is an estimate, not a limit.
/// </param>
/// <param name="options">Additional write options, or <see langword="null"/> for default behavior.</param>
/// <param name="cancellationToken">Cancellation token for the managed task.</param>
/// <returns>A task that completes when the native callback reports completion.</returns>
/// <exception cref="ArgumentNullException"><paramref name="fill"/> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="sizeHint"/> is negative.</exception>
/// <exception cref="ObjectDisposedException">The operator or executor has been disposed.</exception>
/// <exception cref="OperationCanceledException"><paramref name="cancellationToken"/> is already canceled.</exception>
/// <exception cref="OpenDALException">Native write submission fails immediately.</exception>
public async Task WriteAsync(
string path,
Action<IBufferWriter<byte>> fill,
int sizeHint = 0,
WriteOptions? options = null,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(fill);
ArgumentOutOfRangeException.ThrowIfNegative(sizeHint);
using var buffer = AllocateWriteBuffer(
sizeHint > 0 ? sizeHint : WriteBuffer.DefaultInitialCapacity);
fill(buffer);
await WriteAsync(path, buffer, options, cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Reads bytes from a path with read options.
/// </summary>
/// <param name="path">Source path in the configured backend.</param>
/// <param name="options">Additional read options.</param>
/// <returns>The content bytes.</returns>
public byte[] Read(string path, ReadOptions? options = null)
{
return Read(path, static sequence => sequence.ToArray(), options);
}
/// <summary>
/// Reads all bytes from a path asynchronously.
/// </summary>
/// <param name="path">Source path in the configured backend.</param>
/// <param name="cancellationToken">Cancellation token for the managed task.</param>
/// <returns>A task that resolves with the read content.</returns>
public Task<byte[]> ReadAsync(string path, CancellationToken cancellationToken)
{
return ReadAsync(path, options: null, cancellationToken);
}
/// <summary>
/// Reads bytes from a path asynchronously with optional read options and executor.
/// </summary>
/// <param name="path">Source path in the configured backend.</param>
/// <param name="options">Additional read options, or <see langword="null"/> for default behavior.</param>
/// <param name="cancellationToken">Cancellation token for the managed task.</param>
/// <returns>A task that resolves with the read content.</returns>
public Task<byte[]> ReadAsync(
string path,
ReadOptions? options = null,
CancellationToken cancellationToken = default)
{
return ReadAsync(path, static sequence => sequence.ToArray(), options, cancellationToken);
}
/// <summary>
/// Reads a path into a native buffer exposed as a
/// <see cref="System.Buffers.ReadOnlySequence{T}"/>, without copying into
/// a managed array.
/// </summary>
/// <remarks>
/// Dispose the returned buffer to release the native memory; the sequence
/// must not be used afterwards.
/// </remarks>
/// <param name="path">Source path in the configured backend.</param>
/// <param name="options">Additional read options, or <see langword="null"/> for default behavior.</param>
/// <returns>The content as a disposable native buffer.</returns>
/// <exception cref="ObjectDisposedException">The operator or executor has been disposed.</exception>
/// <exception cref="OpenDALException">Native read fails.</exception>
internal ReadBuffer ReadBuffer(string path, ReadOptions? options = null)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
using var nativeOptionsHandle = options?.BuildNativeOptionsHandle();
var result = NativeMethods.operator_read_with_options(
this, path, GetOptionsHandle(nativeOptionsHandle));
return Interop.Buffers.ReadBuffer.FromResult(result);
}
/// <summary>
/// Reads a path asynchronously into a native buffer exposed as a
/// <see cref="System.Buffers.ReadOnlySequence{T}"/>, without copying into
/// a managed array.
/// </summary>
/// <remarks>
/// Dispose the returned buffer to release the native memory; the sequence
/// must not be used afterwards.
/// </remarks>
/// <param name="path">Source path in the configured backend.</param>
/// <param name="options">Additional read options, or <see langword="null"/> for default behavior.</param>
/// <param name="cancellationToken">Cancellation token for the managed task.</param>
/// <returns>A task that resolves with the content as a disposable native buffer.</returns>
/// <exception cref="ObjectDisposedException">The operator or executor has been disposed.</exception>
/// <exception cref="OperationCanceledException"><paramref name="cancellationToken"/> is already canceled.</exception>
/// <exception cref="OpenDALException">Native read submission fails immediately.</exception>
internal async Task<ReadBuffer> ReadBufferAsync(
string path,
ReadOptions? options = null,
CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
cancellationToken.ThrowIfCancellationRequested();
var context = AsyncStateRegistry.Register<OpenDALReadResult>(out var state);
OpenDALResult submit;
using (var nativeOptionsHandle = options?.BuildNativeOptionsHandle())
{
unsafe
{
submit = NativeMethods.operator_read_with_options_async(
this,
path,
GetOptionsHandle(nativeOptionsHandle),
&OnReadResultRetained,
context
);
}
}
try
{
ThrowIfErrorAndRelease(submit);
}
catch
{
AsyncStateRegistry.Unregister(context);
throw;
}
state.BindCancellation(cancellationToken);
var result = await state.Completion.Task.ConfigureAwait(false);
return Interop.Buffers.ReadBuffer.FromResult(result);
}
/// <summary>
/// Reads a path and consumes the payload in place as a
/// <see cref="ReadOnlySequence{T}"/> over native memory, without copying
/// into a managed array.
/// </summary>
/// <remarks>
/// <paramref name="consume"/> runs synchronously, so standard consumers
/// such as <c>System.Text.Json.Utf8JsonReader</c> parse straight from
/// native memory. The memory is released when it returns: the sequence,
/// and anything pointing into it, must not escape the callback.
/// </remarks>
/// <typeparam name="T">Result produced by the consumer.</typeparam>
/// <param name="path">Source path in the configured backend.</param>
/// <param name="consume">Consumer invoked once with the payload.</param>
/// <param name="options">Additional read options, or <see langword="null"/> for default behavior.</param>
/// <returns>The value returned by <paramref name="consume"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="consume"/> is null.</exception>
/// <exception cref="ObjectDisposedException">The operator or executor has been disposed.</exception>
/// <exception cref="OpenDALException">Native read fails.</exception>
public T Read<T>(
string path,
Func<ReadOnlySequence<byte>, T> consume,
ReadOptions? options = null)
{
ArgumentNullException.ThrowIfNull(consume);
using var buffer = ReadBuffer(path, options);
return consume(buffer.Sequence);
}
/// <inheritdoc cref="Read{T}(string, Func{ReadOnlySequence{byte}, T}, ReadOptions?, Executor?)" />
/// <summary>
/// Reads a path asynchronously and consumes the payload in place as a
/// <see cref="ReadOnlySequence{T}"/> over native memory, without copying
/// into a managed array.
/// </summary>
/// <param name="cancellationToken">Cancellation token for the managed task.</param>
/// <exception cref="OperationCanceledException"><paramref name="cancellationToken"/> is already canceled.</exception>
public async Task<T> ReadAsync<T>(
string path,
Func<ReadOnlySequence<byte>, T> consume,
ReadOptions? options = null,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(consume);
using var buffer = await ReadBufferAsync(path, options, cancellationToken).ConfigureAwait(false);
return consume(buffer.Sequence);
}
/// <summary>
/// Native read callback that keeps ownership of a successful payload with
/// the awaiter instead of materializing it. Without an awaiter, for
/// example after cancellation, the payload is released here.
/// </summary>
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
internal static void OnReadResultRetained(long context, OpenDALReadResult result)
{
if (!AsyncStateRegistry.TryTake<AsyncState<OpenDALReadResult>>(context, out var state))
{
result.Release();
return;
}
state.CancellationRegistration.Dispose();
if (!state.Completion.TrySetResult(result))
{
// Cancellation won the completion race, so no awaiter will ever
// take ownership of this payload.
result.Release();
}
}
/// <summary>
/// Gets metadata for the specified path.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="options">Additional stat options.</param>
/// <returns>Metadata of the target path.</returns>
public Metadata Stat(string path, StatOptions? options = null)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
OpenDALMetadataResult result;
using var nativeOptionsHandle = options?.BuildNativeOptionsHandle();
result = NativeMethods.operator_stat_with_options(this, path, GetOptionsHandle(nativeOptionsHandle));
return ToValueOrThrowAndRelease<Metadata, OpenDALMetadataResult>(result);
}
/// <summary>
/// Gets metadata of a path asynchronously.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="cancellationToken">Cancellation token for the managed task.</param>
/// <returns>A task that resolves with the path metadata.</returns>
public Task<Metadata> StatAsync(string path, CancellationToken cancellationToken)
{
return StatAsync(path, options: null, cancellationToken);
}
/// <summary>
/// Gets metadata for the specified path asynchronously.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="options">Additional stat options.</param>
/// <param name="cancellationToken">Cancellation token for the managed task.</param>
/// <returns>A task that resolves with metadata.</returns>
public Task<Metadata> StatAsync(
string path,
StatOptions? options = null,
CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
return SubmitAsyncOperation<Metadata, StatOptions>(options, SubmitStatAsync, cancellationToken);
OpenDALResult SubmitStatAsync(long context, IntPtr optionsHandle)
{
unsafe
{
return NativeMethods.operator_stat_with_options_async(
this,
path,
optionsHandle,
&OnStatCompleted,
context
);
}
}
}
/// <summary>
/// Lists entries under the specified path.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="options">Additional list options.</param>
/// <returns>Listed entries.</returns>
public IReadOnlyList<Entry> List(string path, ListOptions? options = null)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
OpenDALEntryListResult result;
using var nativeOptionsHandle = options?.BuildNativeOptionsHandle();
result = NativeMethods.operator_list_with_options(this, path, GetOptionsHandle(nativeOptionsHandle));
return ToValueOrThrowAndRelease<IReadOnlyList<Entry>, OpenDALEntryListResult>(result);
}
/// <summary>
/// Lists entries under a path asynchronously.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="cancellationToken">Cancellation token for the managed task.</param>
/// <returns>A task that resolves with the listed entries.</returns>
public Task<IReadOnlyList<Entry>> ListAsync(string path, CancellationToken cancellationToken)
{
return ListAsync(path, options: null, cancellationToken);
}
/// <summary>
/// Lists entries under the specified path asynchronously.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="options">Additional list options.</param>
/// <param name="cancellationToken">Cancellation token for the managed task.</param>
/// <returns>A task that resolves with listed entries.</returns>
public Task<IReadOnlyList<Entry>> ListAsync(
string path,
ListOptions? options = null,
CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
return SubmitAsyncOperation<IReadOnlyList<Entry>, ListOptions>(options, SubmitListAsync, cancellationToken);
OpenDALResult SubmitListAsync(long context, IntPtr optionsHandle)
{
unsafe
{
return NativeMethods.operator_list_with_options_async(
this,
path,
optionsHandle,
&OnListCompleted,
context
);
}
}
}
/// <summary>
/// Duplicates this operator and returns a new managed instance.
/// </summary>
/// <returns>A new operator handle that shares the same backend configuration.</returns>
/// <exception cref="ObjectDisposedException">The operator has been disposed.</exception>
/// <exception cref="OpenDALException">Native operator duplication fails.</exception>
public Operator Duplicate()
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var result = NativeMethods.operator_duplicate(this);
var newHandle = ToValueOrThrowAndRelease<IntPtr, OpenDALOperatorResult>(result);
if (newHandle == IntPtr.Zero)
{
throw new InvalidOperationException("Duplicate returned null operator pointer");
}
return new Operator(newHandle);
}
/// <summary>
/// Deletes the file at the specified path.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="options">Additional delete options.</param>
public void Delete(string path, DeleteOptions? options = null)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
using var nativeOptionsHandle = options?.BuildNativeOptionsHandle();
var result = NativeMethods.operator_delete_with_options(this, path, GetOptionsHandle(nativeOptionsHandle));
ThrowIfErrorAndRelease(result);
}
/// <summary>
/// Deletes the file at the specified path asynchronously.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="options">Additional delete options.</param>
/// <param name="cancellationToken">Cancellation token for the managed task.</param>
/// <returns>A task that completes when the native callback reports completion.</returns>
public Task DeleteAsync(string path, DeleteOptions? options = null, CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
return SubmitAsyncOperation(options, SubmitDeleteAsync, cancellationToken);
OpenDALResult SubmitDeleteAsync(long context, IntPtr optionsHandle)
{
unsafe
{
return NativeMethods.operator_delete_with_options_async(
this,
path,
optionsHandle,
&OnDeleteCompleted,
context
);
}
}
}
/// <summary>
/// Creates a directory at the specified path.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
public void CreateDir(string path)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var result = NativeMethods.operator_create_dir(this, path);
ThrowIfErrorAndRelease(result);
}
/// <summary>
/// Creates a directory at the specified path asynchronously.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="cancellationToken">Cancellation token for the managed task.</param>
/// <returns>A task that completes when the native callback reports completion.</returns>
public Task CreateDirAsync(string path, CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
return SubmitAsyncOperation(SubmitCreateDirAsync, cancellationToken);
OpenDALResult SubmitCreateDirAsync(long context)
{
unsafe
{
return NativeMethods.operator_create_dir_async(
this,
path,
&OnCreateDirCompleted,
context
);
}
}
}
/// <summary>
/// Copies a file from source path to target path.
/// </summary>
/// <param name="sourcePath">Source path in the configured backend.</param>
/// <param name="targetPath">Target path in the configured backend.</param>
public void Copy(string sourcePath, string targetPath)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var result = NativeMethods.operator_copy(this, sourcePath, targetPath);
ThrowIfErrorAndRelease(result);
}
/// <summary>
/// Copies a file from source path to target path asynchronously.
/// </summary>
/// <param name="sourcePath">Source path in the configured backend.</param>
/// <param name="targetPath">Target path in the configured backend.</param>
/// <param name="cancellationToken">Cancellation token for the managed task.</param>
/// <returns>A task that completes when the native callback reports completion.</returns>
public Task CopyAsync(
string sourcePath,
string targetPath,
CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
return SubmitAsyncOperation(SubmitCopyAsync, cancellationToken);
OpenDALResult SubmitCopyAsync(long context)
{
unsafe
{
return NativeMethods.operator_copy_async(
this,
sourcePath,
targetPath,
&OnCopyCompleted,
context
);
}
}
}
/// <summary>
/// Renames a file from source path to target path.
/// </summary>
/// <param name="sourcePath">Source path in the configured backend.</param>
/// <param name="targetPath">Target path in the configured backend.</param>
public void Rename(string sourcePath, string targetPath)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var result = NativeMethods.operator_rename(this, sourcePath, targetPath);
ThrowIfErrorAndRelease(result);
}
/// <summary>
/// Renames a file from source path to target path asynchronously.
/// </summary>
/// <param name="sourcePath">Source path in the configured backend.</param>
/// <param name="targetPath">Target path in the configured backend.</param>
/// <param name="cancellationToken">Cancellation token for the managed task.</param>
/// <returns>A task that completes when the native callback reports completion.</returns>
public Task RenameAsync(
string sourcePath,
string targetPath,
CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
return SubmitAsyncOperation(SubmitRenameAsync, cancellationToken);
OpenDALResult SubmitRenameAsync(long context)
{
unsafe
{
return NativeMethods.operator_rename_async(
this,
sourcePath,
targetPath,
&OnRenameCompleted,
context
);
}
}
}
/// <summary>
/// Removes all entries under the specified path recursively.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
public void RemoveAll(string path)
{
var options = new DeleteOptions { Recursive = true };
Delete(path, options);
}
/// <summary>
/// Removes all entries under the specified path recursively asynchronously.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="cancellationToken">Cancellation token for the managed task.</param>
/// <returns>A task that completes when the native callback reports completion.</returns>
public Task RemoveAllAsync(string path, CancellationToken cancellationToken = default)
{
var options = new DeleteOptions { Recursive = true };
return DeleteAsync(path, options, cancellationToken);
}
/// <summary>
/// Creates a presigned read request asynchronously.
/// </summary>
public Task<PresignedRequest> PresignReadAsync(
string path,
TimeSpan expiration,
CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var expireNanos = Utilities.ToNanoseconds(expiration, nameof(expiration));
return SubmitAsyncOperation<PresignedRequest>(SubmitPresignReadAsync, cancellationToken);
OpenDALResult SubmitPresignReadAsync(long context)
{
unsafe
{
return NativeMethods.operator_presign_read_async(
this,
path,
expireNanos,
&OnPresignReadCompleted,
context
);
}
}
}
/// <summary>
/// Creates a presigned write request asynchronously.
/// </summary>
public Task<PresignedRequest> PresignWriteAsync(
string path,
TimeSpan expiration,
CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var expireNanos = Utilities.ToNanoseconds(expiration, nameof(expiration));
return SubmitAsyncOperation<PresignedRequest>(SubmitPresignWriteAsync, cancellationToken);
OpenDALResult SubmitPresignWriteAsync(long context)
{
unsafe
{
return NativeMethods.operator_presign_write_async(
this,
path,
expireNanos,
&OnPresignWriteCompleted,
context
);
}
}
}
/// <summary>
/// Creates a presigned stat request asynchronously.
/// </summary>
public Task<PresignedRequest> PresignStatAsync(
string path,
TimeSpan expiration,
CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var expireNanos = Utilities.ToNanoseconds(expiration, nameof(expiration));
return SubmitAsyncOperation<PresignedRequest>(SubmitPresignStatAsync, cancellationToken);
OpenDALResult SubmitPresignStatAsync(long context)
{
unsafe
{
return NativeMethods.operator_presign_stat_async(
this,
path,
expireNanos,
&OnPresignStatCompleted,
context
);
}
}
}
/// <summary>
/// Creates a presigned delete request asynchronously.
/// </summary>
public Task<PresignedRequest> PresignDeleteAsync(
string path,
TimeSpan expiration,
CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var expireNanos = Utilities.ToNanoseconds(expiration, nameof(expiration));
return SubmitAsyncOperation<PresignedRequest>(SubmitPresignDeleteAsync, cancellationToken);
OpenDALResult SubmitPresignDeleteAsync(long context)
{
unsafe
{
return NativeMethods.operator_presign_delete_async(
this,
path,
expireNanos,
&OnPresignDeleteCompleted,
context
);
}
}
}
/// <summary>
/// Opens a read stream for the specified path.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="options">Optional read options.</param>
/// <returns>A readable stream over the given path.</returns>
public OperatorInputStream OpenReadStream(
string path,
ReadOptions? options = null)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
using var nativeOptions = options?.BuildNativeOptionsHandle();
var result = NativeMethods.operator_input_stream_create(
this,
path,
GetOptionsHandle(nativeOptions)
);
var streamHandle = ToValueOrThrowAndRelease<IntPtr, OpenDALOperatorResult>(result);
if (streamHandle == IntPtr.Zero)
{
throw new InvalidOperationException("OpenReadStream returned null stream pointer");
}
return new OperatorInputStream(streamHandle);
}
/// <summary>
/// Opens a write stream for the specified path.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="options">Optional write options.</param>
/// <param name="bufferSize">Buffer size used by the managed write stream.</param>
/// <returns>A writable stream over the given path.</returns>
public OperatorOutputStream OpenWriteStream(
string path,
WriteOptions? options = null,
int bufferSize = OperatorOutputStream.DefaultBufferSize)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
using var nativeOptions = options?.BuildNativeOptionsHandle();
var result = NativeMethods.operator_output_stream_create(
this,
path,
GetOptionsHandle(nativeOptions)
);
var streamHandle = ToValueOrThrowAndRelease<IntPtr, OpenDALOperatorResult>(result);
if (streamHandle == IntPtr.Zero)
{
throw new InvalidOperationException("OpenWriteStream returned null stream pointer");
}
return new OperatorOutputStream(streamHandle, bufferSize);
}
/// <summary>
/// Releases the native operator handle.
/// </summary>
/// <returns><see langword="true"/> after the handle has been released.</returns>
protected override bool ReleaseHandle()
{
NativeMethods.operator_free(handle);
return true;
}
/// <summary>
/// Applies a native layer result by creating a new operator from the returned handle.
/// </summary>
/// <param name="result">Native result that contains a new operator pointer.</param>
/// <returns>A new operator instance.</returns>
/// <exception cref="InvalidOperationException">Returned operator pointer is null.</exception>
/// <exception cref="OpenDALException">Native layer application fails.</exception>
internal Operator ApplyLayerResult(OpenDALOperatorResult result)
{
var newHandle = ToValueOrThrowAndRelease<IntPtr, OpenDALOperatorResult>(result);
if (newHandle == IntPtr.Zero)
{
throw new InvalidOperationException("Layer application returned null operator pointer");
}
return new Operator(newHandle);
}
/// <summary>
/// Gets the native options pointer from an optional native options handle.
/// </summary>
/// <param name="options">Native options handle or <see langword="null"/>.</param>
/// <returns>Native options pointer, or <see cref="IntPtr.Zero"/> when options are not provided.</returns>
private static IntPtr GetOptionsHandle(NativeOptionsHandle? options)
{
return options is null ? IntPtr.Zero : options.DangerousGetHandle();
}
/// <summary>
/// Creates the lazily-evaluated operator info loader.
/// </summary>
/// <returns>A thread-safe lazy loader for <see cref="OperatorInfo"/>.</returns>
private Lazy<OperatorInfo> CreateInfoLazy()
{
return new Lazy<OperatorInfo>(CreateOperatorInfo, LazyThreadSafetyMode.ExecutionAndPublication);
}
/// <summary>
/// Retrieves operator info from the native layer.
/// </summary>
/// <returns>Managed operator info value.</returns>
/// <exception cref="OpenDALException">Native operator info retrieval fails.</exception>
private OperatorInfo CreateOperatorInfo()
{
var result = NativeMethods.operator_info_get(this);
return ToValueOrThrowAndRelease<OperatorInfo, OpenDALOperatorInfoResult>(result);
}
/// <summary>
/// Builds constructor options for operator creation when key/value options are provided.
/// </summary>
/// <param name="options">Backend options dictionary.</param>
/// <returns>Native options handle, or <see langword="null"/> when options are empty.</returns>
private static NativeOptionsHandle? CreateConstructorOptionsHandle(IReadOnlyDictionary<string, string>? options)
{
if (options is null || options.Count == 0)
{
return null;
}
return NativeOptionsBuilder.BuildNativeOptionsHandle(
options,
NativeMethods.constructor_option_build,
NativeMethods.constructor_option_free
);
}
/// <summary>
/// Converts a native result into a managed value, throwing on native error and always releasing native resources.
/// </summary>
/// <typeparam name="TOutput">Managed output type.</typeparam>
/// <typeparam name="TResult">Native result type.</typeparam>
/// <param name="result">Native result payload.</param>
/// <returns>Managed value converted from <paramref name="result"/>.</returns>
/// <exception cref="OpenDALException">Native operation returns an error.</exception>
internal static TOutput ToValueOrThrowAndRelease<TOutput, TResult>(TResult result)
where TResult : struct, INativeValueResult<TOutput>
{
try
{
var error = result.GetError();
if (error.IsError)
{
throw new OpenDALException(error);
}
return result.ToValue();
}
finally
{
result.Release();
}
}
/// <summary>
/// Throws when a native result reports an error and always releases native resources.
/// </summary>
/// <typeparam name="TResult">Native result type.</typeparam>
/// <param name="result">Native result payload.</param>
/// <exception cref="OpenDALException">Native operation returns an error.</exception>
internal static void ThrowIfErrorAndRelease<TResult>(TResult result)
where TResult : struct, INativeResult
{
try
{
var error = result.GetError();
if (error.IsError)
{
throw new OpenDALException(error);
}
}
finally
{
result.Release();
}
}
/// <summary>
/// Submits a native async operation and binds it to a managed task completion source.
/// </summary>
/// <typeparam name="TOutput">Managed task result type.</typeparam>
/// <typeparam name="TOptions">Managed options type.</typeparam>
/// <param name="options">Optional managed options for this operation.</param>
/// <param name="submit">Submission delegate that invokes the native async API.</param>
/// <param name="cancellationToken">Cancellation token for managed task observation.</param>
/// <returns>A task completed by the corresponding native callback.</returns>
/// <exception cref="OperationCanceledException"><paramref name="cancellationToken"/> is already canceled.</exception>
/// <exception cref="OpenDALException">Native submission returns an immediate error.</exception>
internal static Task<TOutput> SubmitAsyncOperation<TOutput, TOptions>(
TOptions? options,
Func<long, IntPtr, OpenDALResult> submit,
CancellationToken cancellationToken)
where TOptions : class, IOptions
{
cancellationToken.ThrowIfCancellationRequested();
var context = AsyncStateRegistry.Register<TOutput>(out var asyncState);
try
{
using var nativeOptionsHandle = options?.BuildNativeOptionsHandle();
var submitResult = submit(context, GetOptionsHandle(nativeOptionsHandle));
ThrowIfErrorAndRelease(submitResult);
asyncState.BindCancellation(cancellationToken);
return asyncState.Completion.Task;
}
catch
{
AsyncStateRegistry.Unregister(context);
throw;
}
}
/// <summary>
/// Submits a native async operation and binds it to a managed task completion source.
/// </summary>
/// <typeparam name="TOptions">Managed options type.</typeparam>
/// <param name="options">Optional managed options for this operation.</param>
/// <param name="submit">Submission delegate that invokes the native async API.</param>
/// <param name="cancellationToken">Cancellation token for managed task observation.</param>
/// <returns>A task completed by the corresponding native callback.</returns>
/// <exception cref="OperationCanceledException"><paramref name="cancellationToken"/> is already canceled.</exception>
/// <exception cref="OpenDALException">Native submission returns an immediate error.</exception>
internal static Task SubmitAsyncOperation<TOptions>(
TOptions? options,
Func<long, IntPtr, OpenDALResult> submit,
CancellationToken cancellationToken)
where TOptions : class, IOptions
{
cancellationToken.ThrowIfCancellationRequested();
var context = AsyncStateRegistry.Register<bool>(out var asyncState);
try
{
using var nativeOptionsHandle = options?.BuildNativeOptionsHandle();
var submitResult = submit(context, GetOptionsHandle(nativeOptionsHandle));
ThrowIfErrorAndRelease(submitResult);
asyncState.BindCancellation(cancellationToken);
return asyncState.Completion.Task;
}
catch
{
AsyncStateRegistry.Unregister(context);
throw;
}
}
/// <summary>
/// Submits a native async operation without options and binds it to a managed task completion source.
/// </summary>
/// <typeparam name="TOutput">Managed task result type.</typeparam>
/// <param name="submit">Submission delegate that invokes the native async API.</param>
/// <param name="cancellationToken">Cancellation token for managed task observation.</param>
/// <returns>A task completed by the corresponding native callback.</returns>
internal static Task<TOutput> SubmitAsyncOperation<TOutput>(
Func<long, OpenDALResult> submit,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var context = AsyncStateRegistry.Register<TOutput>(out var asyncState);
try
{
var submitResult = submit(context);
ThrowIfErrorAndRelease(submitResult);
asyncState.BindCancellation(cancellationToken);
return asyncState.Completion.Task;
}
catch
{
AsyncStateRegistry.Unregister(context);
throw;
}
}
/// <summary>
/// Submits a native async operation without options and binds it to a managed task completion source.
/// </summary>
/// <param name="submit">Submission delegate that invokes the native async API.</param>
/// <param name="cancellationToken">Cancellation token for managed task observation.</param>
/// <returns>A task completed by the corresponding native callback.</returns>
/// <exception cref="OperationCanceledException"><paramref name="cancellationToken"/> is already canceled.</exception>
/// <exception cref="OpenDALException">Native submission returns an immediate error.</exception>
internal static Task SubmitAsyncOperation(
Func<long, OpenDALResult> submit,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var context = AsyncStateRegistry.Register<bool>(out var asyncState);
try
{
var submitResult = submit(context);
ThrowIfErrorAndRelease(submitResult);
asyncState.BindCancellation(cancellationToken);
return asyncState.Completion.Task;
}
catch
{
AsyncStateRegistry.Unregister(context);
throw;
}
}
/// <summary>
/// Attempts to retrieve and remove async state for a callback context.
/// </summary>
/// <typeparam name="T">Async state result type.</typeparam>
/// <param name="context">Native callback context id.</param>
/// <param name="state">Resolved async state when found.</param>
/// <returns><see langword="true"/> if an async state is found; otherwise <see langword="false"/>.</returns>
private static bool TryTakeAsyncState<T>(long context, [NotNullWhen(true)] out AsyncState<T>? state)
{
if (AsyncStateRegistry.TryTake<AsyncState<T>>(context, out var current))
{
state = current;
return true;
}
state = null;
return false;
}
/// <summary>
/// Completes a value-producing async state from a native callback result.
/// </summary>
/// <typeparam name="TOutput">Managed output type.</typeparam>
/// <typeparam name="TResult">Native result type.</typeparam>
/// <param name="context">Native callback context id.</param>
/// <param name="result">Native callback result payload.</param>
private static void CompleteAsyncState<TOutput, TResult>(long context, TResult result)
where TResult : struct, INativeValueResult<TOutput>
{
if (!TryTakeAsyncState(context, out AsyncState<TOutput>? state))
{
return;
}
try
{
state.CancellationRegistration.Dispose();
if (result.GetError().IsError)
{
state.Completion.TrySetException(new OpenDALException(result.GetError()));
return;
}
state.Completion.TrySetResult(result.ToValue());
}
catch (Exception ex)
{
state.Completion.TrySetException(ex);
}
}
/// <summary>
/// Completes a non-value async state from a native callback result.
/// </summary>
/// <typeparam name="TResult">Native result type.</typeparam>
/// <param name="context">Native callback context id.</param>
/// <param name="result">Native callback result payload.</param>
private static void CompleteAsyncState<TResult>(long context, TResult result)
where TResult : struct, INativeResult
{
if (!TryTakeAsyncState(context, out AsyncState<bool>? state))
{
return;
}
try
{
state.CancellationRegistration.Dispose();
var error = result.GetError();
if (error.IsError)
{
state.Completion.TrySetException(new OpenDALException(error));
return;
}
state.Completion.TrySetResult(true);
}
catch (Exception ex)
{
state.Completion.TrySetException(ex);
}
}
/// <summary>
/// Finalizes a value-producing native callback by completing managed state and releasing native resources.
/// </summary>
/// <typeparam name="TOutput">Managed output type.</typeparam>
/// <typeparam name="TResult">Native result type.</typeparam>
/// <param name="context">Native callback context id.</param>
/// <param name="result">Native callback result payload.</param>
internal static void CompleteAsyncCallback<TOutput, TResult>(long context, TResult result)
where TResult : struct, INativeValueResult<TOutput>
{
try
{
CompleteAsyncState<TOutput, TResult>(context, result);
}
finally
{
result.Release();
}
}
/// <summary>
/// Finalizes a non-value native callback by completing managed state and releasing native resources.
/// </summary>
/// <typeparam name="TResult">Native result type.</typeparam>
/// <param name="context">Native callback context id.</param>
/// <param name="result">Native callback result payload.</param>
internal static void CompleteAsyncCallback<TResult>(long context, TResult result)
where TResult : struct, INativeResult
{
try
{
CompleteAsyncState(context, result);
}
finally
{
result.Release();
}
}
#region Async Callbacks
/// <summary>
/// Native callback invoked when an asynchronous write operation finishes.
/// </summary>
/// <param name="context">Opaque async state context previously registered by <see cref="AsyncStateRegistry"/>.</param>
/// <param name="result">Write completion result returned by the native layer.</param>
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void OnWriteCompleted(long context, OpenDALResult result)
{
CompleteAsyncCallback(context, result);
}
/// <summary>
/// Native callback invoked when an asynchronous stat operation finishes.
/// </summary>
/// <param name="context">Opaque async state context previously registered by <see cref="AsyncStateRegistry"/>.</param>
/// <param name="result">Stat completion result returned by the native layer.</param>
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void OnStatCompleted(long context, OpenDALMetadataResult result)
{
CompleteAsyncCallback<Metadata, OpenDALMetadataResult>(context, result);
}
/// <summary>
/// Native callback invoked when an asynchronous list operation finishes.
/// </summary>
/// <param name="context">Opaque async state context previously registered by <see cref="AsyncStateRegistry"/>.</param>
/// <param name="result">List completion result returned by the native layer.</param>
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void OnListCompleted(long context, OpenDALEntryListResult result)
{
CompleteAsyncCallback<IReadOnlyList<Entry>, OpenDALEntryListResult>(context, result);
}
/// <summary>
/// Native callback invoked when an asynchronous delete operation finishes.
/// </summary>
/// <param name="context">Opaque async state context previously registered by <see cref="AsyncStateRegistry"/>.</param>
/// <param name="result">Delete completion result returned by the native layer.</param>
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void OnDeleteCompleted(long context, OpenDALResult result)
{
CompleteAsyncCallback(context, result);
}
/// <summary>
/// Native callback invoked when an asynchronous create-dir operation finishes.
/// </summary>
/// <param name="context">Opaque async state context previously registered by <see cref="AsyncStateRegistry"/>.</param>
/// <param name="result">Create-dir completion result returned by the native layer.</param>
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void OnCreateDirCompleted(long context, OpenDALResult result)
{
CompleteAsyncCallback(context, result);
}
/// <summary>
/// Native callback invoked when an asynchronous copy operation finishes.
/// </summary>
/// <param name="context">Opaque async state context previously registered by <see cref="AsyncStateRegistry"/>.</param>
/// <param name="result">Copy completion result returned by the native layer.</param>
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void OnCopyCompleted(long context, OpenDALResult result)
{
CompleteAsyncCallback(context, result);
}
/// <summary>
/// Native callback invoked when an asynchronous rename operation finishes.
/// </summary>
/// <param name="context">Opaque async state context previously registered by <see cref="AsyncStateRegistry"/>.</param>
/// <param name="result">Rename completion result returned by the native layer.</param>
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void OnRenameCompleted(long context, OpenDALResult result)
{
CompleteAsyncCallback(context, result);
}
/// <summary>
/// Native callback invoked when an asynchronous presign-read operation finishes.
/// </summary>
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void OnPresignReadCompleted(long context, OpenDALPresignedRequestResult result)
{
CompleteAsyncCallback<PresignedRequest, OpenDALPresignedRequestResult>(context, result);
}
/// <summary>
/// Native callback invoked when an asynchronous presign-write operation finishes.
/// </summary>
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void OnPresignWriteCompleted(long context, OpenDALPresignedRequestResult result)
{
CompleteAsyncCallback<PresignedRequest, OpenDALPresignedRequestResult>(context, result);
}
/// <summary>
/// Native callback invoked when an asynchronous presign-stat operation finishes.
/// </summary>
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void OnPresignStatCompleted(long context, OpenDALPresignedRequestResult result)
{
CompleteAsyncCallback<PresignedRequest, OpenDALPresignedRequestResult>(context, result);
}
/// <summary>
/// Native callback invoked when an asynchronous presign-delete operation finishes.
/// </summary>
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void OnPresignDeleteCompleted(long context, OpenDALPresignedRequestResult result)
{
CompleteAsyncCallback<PresignedRequest, OpenDALPresignedRequestResult>(context, result);
}
#endregion
}