blob: 8bb04631e92f23af53319725290767c0e92ef3e0 [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.Runtime.InteropServices;
using System.Runtime.CompilerServices;
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 the underlying native operator pointer.
/// </summary>
public IntPtr Op => DangerousGetHandle();
/// <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="OpenDALException">Native operator construction fails.</exception>
public Operator(string scheme, IReadOnlyDictionary<string, string>? options = null) : base(IntPtr.Zero, true)
{
ArgumentException.ThrowIfNullOrWhiteSpace(scheme);
info = CreateInfoLazy();
using var nativeOptionsHandle = CreateConstructorOptionsHandle(options);
var result = NativeMethods.operator_construct(scheme, GetOptionsHandle(nativeOptionsHandle));
SetHandle(ToValueOrThrowAndRelease<IntPtr, OpenDALOperatorResult>(result));
}
/// <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) : this(
config?.Scheme ?? throw new ArgumentNullException(nameof(config)),
config.ToOptions())
{
}
/// <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.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="content">Bytes to write.</param>
/// <exception cref="ObjectDisposedException">The operator has been disposed.</exception>
/// <exception cref="OpenDALException">Native write fails.</exception>
public void Write(string path, byte[] content)
{
Write(path, content, options: null, executor: null);
}
/// <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)
{
Write(path, content, (WriteOptions?)options, executor: null);
}
/// <summary>
/// Writes the specified content to a path using the provided executor.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="content">Bytes to write.</param>
/// <param name="executor">Executor used for this operation, or <see langword="null"/> to use default executor.</param>
/// <exception cref="ObjectDisposedException">The operator or executor has been disposed.</exception>
/// <exception cref="OpenDALException">Native write fails.</exception>
public void Write(string path, byte[] content, Executor? executor)
{
Write(path, content, options: null, executor);
}
/// <summary>
/// Writes the specified content to a path with write options using the provided 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.</param>
/// <param name="executor">Executor used for this operation, or <see langword="null"/> to use default executor.</param>
public void Write(string path, byte[] content, WriteOptions? options, Executor? executor)
{
ArgumentNullException.ThrowIfNull(content);
ObjectDisposedException.ThrowIf(IsInvalid, this);
var executorHandle = GetExecutorHandle(executor);
using var nativeOptionsHandle = options?.BuildNativeOptionsHandle();
OpenDALResult result = NativeMethods.operator_write_with_options(
this,
executorHandle,
path,
content,
(nuint)content.Length,
GetOptionsHandle(nativeOptionsHandle)
);
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>
/// <returns>A task that completes when the native callback reports completion.</returns>
/// <exception cref="ObjectDisposedException">The operator has been disposed.</exception>
/// <exception cref="OperationCanceledException"><paramref name="cancellationToken"/> is already canceled.</exception>
/// <exception cref="OpenDALException">Native write submission fails immediately.</exception>
public Task WriteAsync(string path, byte[] content, CancellationToken cancellationToken = default)
{
return WriteAsync(path, content, options: null, executor: null, cancellationToken);
}
/// <summary>
/// Writes the specified content to a path asynchronously 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>
/// <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,
CancellationToken cancellationToken = default)
{
return WriteAsync(path, content, (WriteOptions?)options, executor: null, cancellationToken);
}
/// <summary>
/// Writes the specified content to a path asynchronously using the provided executor.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="content">Bytes to write.</param>
/// <param name="executor">Executor used for this operation, or <see langword="null"/> to use default executor.</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 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 Task WriteAsync(
string path,
byte[] content,
Executor? executor,
CancellationToken cancellationToken = default)
{
return WriteAsync(path, content, options: null, executor, 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="executor">Executor used for this operation, or <see langword="null"/> to use default executor.</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,
Executor? executor,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(content);
ObjectDisposedException.ThrowIf(IsInvalid, this);
var executorHandle = GetExecutorHandle(executor);
return SubmitAsyncOperation<bool, WriteOptions>(options, SubmitWriteAsync, cancellationToken);
OpenDALResult SubmitWriteAsync(long context, IntPtr optionsHandle)
{
unsafe
{
fixed (byte* ptr = content)
{
var buffer = new ByteBuffer
{
Data = (IntPtr)ptr,
Len = (nuint)content.Length,
Capacity = (nuint)content.Length,
};
return NativeMethods.operator_write_with_options_async(
this,
executorHandle,
path,
buffer,
optionsHandle,
&OnWriteCompleted,
context
);
}
}
}
}
/// <summary>
/// Reads all bytes from a path.
/// </summary>
/// <param name="path">Source path in the configured backend.</param>
/// <returns>The content bytes.</returns>
/// <exception cref="ObjectDisposedException">The operator has been disposed.</exception>
/// <exception cref="OpenDALException">Native read fails.</exception>
public byte[] Read(string path)
{
return Read(path, options: null, executor: null);
}
/// <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)
{
return Read(path, options, executor: null);
}
/// <summary>
/// Reads all bytes from a path using the provided executor.
/// </summary>
/// <param name="path">Source path in the configured backend.</param>
/// <param name="executor">Executor used for this operation, or <see langword="null"/> to use default executor.</param>
/// <returns>The content bytes.</returns>
/// <exception cref="ObjectDisposedException">The operator or executor has been disposed.</exception>
/// <exception cref="OpenDALException">Native read fails.</exception>
public byte[] Read(string path, Executor? executor)
{
return Read(path, options: null, executor);
}
/// <summary>
/// Reads bytes from a path with read options using the provided executor.
/// </summary>
/// <param name="path">Source path in the configured backend.</param>
/// <param name="options">Additional read options.</param>
/// <param name="executor">Executor used for this operation, or <see langword="null"/> to use default executor.</param>
/// <returns>The content bytes.</returns>
public byte[] Read(string path, ReadOptions? options, Executor? executor)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var executorHandle = GetExecutorHandle(executor);
OpenDALReadResult result;
using var nativeOptionsHandle = options?.BuildNativeOptionsHandle();
result = NativeMethods.operator_read_with_options(this, executorHandle, path, GetOptionsHandle(nativeOptionsHandle));
return ToValueOrThrowAndRelease<byte[], OpenDALReadResult>(result);
}
/// <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>
/// <exception cref="ObjectDisposedException">The operator has been disposed.</exception>
/// <exception cref="OperationCanceledException"><paramref name="cancellationToken"/> is already canceled.</exception>
/// <exception cref="OpenDALException">Native read submission fails immediately.</exception>
public Task<byte[]> ReadAsync(string path, CancellationToken cancellationToken = default)
{
return ReadAsync(path, options: null, executor: null, cancellationToken);
}
/// <summary>
/// Reads bytes from a path asynchronously with read options.
/// </summary>
/// <param name="path">Source path in the configured backend.</param>
/// <param name="options">Additional read options.</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, CancellationToken cancellationToken = default)
{
return ReadAsync(path, options, executor: null, cancellationToken);
}
/// <summary>
/// Reads all bytes from a path asynchronously using the provided executor.
/// </summary>
/// <param name="path">Source path in the configured backend.</param>
/// <param name="executor">Executor used for this operation, or <see langword="null"/> to use default executor.</param>
/// <param name="cancellationToken">Cancellation token for the managed task.</param>
/// <returns>A task that resolves with the read content.</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>
public Task<byte[]> ReadAsync(string path, Executor? executor, CancellationToken cancellationToken = default)
{
return ReadAsync(path, options: null, executor, 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="executor">Executor used for this operation, or <see langword="null"/> to use default executor.</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,
Executor? executor,
CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var executorHandle = GetExecutorHandle(executor);
return SubmitAsyncOperation<byte[], ReadOptions>(options, SubmitReadAsync, cancellationToken);
OpenDALResult SubmitReadAsync(long context, IntPtr optionsHandle)
{
unsafe
{
return NativeMethods.operator_read_with_options_async(
this,
executorHandle,
path,
optionsHandle,
&OnReadCompleted,
context
);
}
}
}
/// <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)
{
return Stat(path, options, executor: null);
}
/// <summary>
/// Gets metadata for the specified path using the provided executor.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="options">Additional stat options.</param>
/// <param name="executor">Executor used for this operation, or <see langword="null"/> to use default executor.</param>
/// <returns>Metadata of the target path.</returns>
public Metadata Stat(string path, StatOptions? options, Executor? executor)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var executorHandle = GetExecutorHandle(executor);
OpenDALMetadataResult result;
using var nativeOptionsHandle = options?.BuildNativeOptionsHandle();
result = NativeMethods.operator_stat_with_options(this, executorHandle, path, GetOptionsHandle(nativeOptionsHandle));
return ToValueOrThrowAndRelease<Metadata, OpenDALMetadataResult>(result);
}
/// <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)
{
return StatAsync(path, options, executor: null, cancellationToken);
}
/// <summary>
/// Gets metadata for the specified path asynchronously using the provided executor.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="options">Additional stat options.</param>
/// <param name="executor">Executor used for this operation, or <see langword="null"/> to use default executor.</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,
Executor? executor,
CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var executorHandle = GetExecutorHandle(executor);
return SubmitAsyncOperation<Metadata, StatOptions>(options, SubmitStatAsync, cancellationToken);
OpenDALResult SubmitStatAsync(long context, IntPtr optionsHandle)
{
unsafe
{
return NativeMethods.operator_stat_with_options_async(
this,
executorHandle,
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)
{
return List(path, options, executor: null);
}
/// <summary>
/// Lists entries under the specified path using the provided executor.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="options">Additional list options.</param>
/// <param name="executor">Executor used for this operation, or <see langword="null"/> to use default executor.</param>
/// <returns>Listed entries.</returns>
public IReadOnlyList<Entry> List(string path, ListOptions? options, Executor? executor)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var executorHandle = GetExecutorHandle(executor);
OpenDALEntryListResult result;
using var nativeOptionsHandle = options?.BuildNativeOptionsHandle();
result = NativeMethods.operator_list_with_options(this, executorHandle, path, GetOptionsHandle(nativeOptionsHandle));
return ToValueOrThrowAndRelease<IReadOnlyList<Entry>, OpenDALEntryListResult>(result);
}
/// <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)
{
return ListAsync(path, options, executor: null, cancellationToken);
}
/// <summary>
/// Lists entries under the specified path asynchronously using the provided executor.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="options">Additional list options.</param>
/// <param name="executor">Executor used for this operation, or <see langword="null"/> to use default executor.</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,
Executor? executor,
CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var executorHandle = GetExecutorHandle(executor);
return SubmitAsyncOperation<IReadOnlyList<Entry>, ListOptions>(options, SubmitListAsync, cancellationToken);
OpenDALResult SubmitListAsync(long context, IntPtr optionsHandle)
{
unsafe
{
return NativeMethods.operator_list_with_options_async(
this,
executorHandle,
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>
public void Delete(string path)
{
Delete(path, executor: null);
}
/// <summary>
/// Deletes the file at the specified path using the provided executor.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="executor">Executor used for this operation, or <see langword="null"/> to use default executor.</param>
public void Delete(string path, Executor? executor)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var executorHandle = GetExecutorHandle(executor);
var result = NativeMethods.operator_delete(this, executorHandle, path);
ThrowIfErrorAndRelease(result);
}
/// <summary>
/// Deletes the file 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 DeleteAsync(string path, CancellationToken cancellationToken = default)
{
return DeleteAsync(path, executor: null, cancellationToken);
}
/// <summary>
/// Deletes the file at the specified path asynchronously using the provided executor.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="executor">Executor used for this operation, or <see langword="null"/> to use default executor.</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, Executor? executor, CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var executorHandle = GetExecutorHandle(executor);
return SubmitAsyncOperation(SubmitDeleteAsync, cancellationToken);
OpenDALResult SubmitDeleteAsync(long context)
{
unsafe
{
return NativeMethods.operator_delete_async(
this,
executorHandle,
path,
&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)
{
CreateDir(path, executor: null);
}
/// <summary>
/// Creates a directory at the specified path using the provided executor.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="executor">Executor used for this operation, or <see langword="null"/> to use default executor.</param>
public void CreateDir(string path, Executor? executor)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var executorHandle = GetExecutorHandle(executor);
var result = NativeMethods.operator_create_dir(this, executorHandle, 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)
{
return CreateDirAsync(path, executor: null, cancellationToken);
}
/// <summary>
/// Creates a directory at the specified path asynchronously using the provided executor.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="executor">Executor used for this operation, or <see langword="null"/> to use default executor.</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, Executor? executor, CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var executorHandle = GetExecutorHandle(executor);
return SubmitAsyncOperation(SubmitCreateDirAsync, cancellationToken);
OpenDALResult SubmitCreateDirAsync(long context)
{
unsafe
{
return NativeMethods.operator_create_dir_async(
this,
executorHandle,
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)
{
Copy(sourcePath, targetPath, executor: null);
}
/// <summary>
/// Copies a file from source path to target path using the provided executor.
/// </summary>
/// <param name="sourcePath">Source path in the configured backend.</param>
/// <param name="targetPath">Target path in the configured backend.</param>
/// <param name="executor">Executor used for this operation, or <see langword="null"/> to use default executor.</param>
public void Copy(string sourcePath, string targetPath, Executor? executor)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var executorHandle = GetExecutorHandle(executor);
var result = NativeMethods.operator_copy(this, executorHandle, 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)
{
return CopyAsync(sourcePath, targetPath, executor: null, cancellationToken);
}
/// <summary>
/// Copies a file from source path to target path asynchronously using the provided executor.
/// </summary>
/// <param name="sourcePath">Source path in the configured backend.</param>
/// <param name="targetPath">Target path in the configured backend.</param>
/// <param name="executor">Executor used for this operation, or <see langword="null"/> to use default executor.</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,
Executor? executor,
CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var executorHandle = GetExecutorHandle(executor);
return SubmitAsyncOperation(SubmitCopyAsync, cancellationToken);
OpenDALResult SubmitCopyAsync(long context)
{
unsafe
{
return NativeMethods.operator_copy_async(
this,
executorHandle,
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)
{
Rename(sourcePath, targetPath, executor: null);
}
/// <summary>
/// Renames a file from source path to target path using the provided executor.
/// </summary>
/// <param name="sourcePath">Source path in the configured backend.</param>
/// <param name="targetPath">Target path in the configured backend.</param>
/// <param name="executor">Executor used for this operation, or <see langword="null"/> to use default executor.</param>
public void Rename(string sourcePath, string targetPath, Executor? executor)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var executorHandle = GetExecutorHandle(executor);
var result = NativeMethods.operator_rename(this, executorHandle, 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)
{
return RenameAsync(sourcePath, targetPath, executor: null, cancellationToken);
}
/// <summary>
/// Renames a file from source path to target path asynchronously using the provided executor.
/// </summary>
/// <param name="sourcePath">Source path in the configured backend.</param>
/// <param name="targetPath">Target path in the configured backend.</param>
/// <param name="executor">Executor used for this operation, or <see langword="null"/> to use default executor.</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,
Executor? executor,
CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var executorHandle = GetExecutorHandle(executor);
return SubmitAsyncOperation(SubmitRenameAsync, cancellationToken);
OpenDALResult SubmitRenameAsync(long context)
{
unsafe
{
return NativeMethods.operator_rename_async(
this,
executorHandle,
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)
{
RemoveAll(path, executor: null);
}
/// <summary>
/// Removes all entries under the specified path recursively using the provided executor.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="executor">Executor used for this operation, or <see langword="null"/> to use default executor.</param>
public void RemoveAll(string path, Executor? executor)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var executorHandle = GetExecutorHandle(executor);
var result = NativeMethods.operator_remove_all(this, executorHandle, path);
ThrowIfErrorAndRelease(result);
}
/// <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)
{
return RemoveAllAsync(path, executor: null, cancellationToken);
}
/// <summary>
/// Removes all entries under the specified path recursively asynchronously using the provided executor.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="executor">Executor used for this operation, or <see langword="null"/> to use default executor.</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, Executor? executor, CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var executorHandle = GetExecutorHandle(executor);
return SubmitAsyncOperation(SubmitRemoveAllAsync, cancellationToken);
OpenDALResult SubmitRemoveAllAsync(long context)
{
unsafe
{
return NativeMethods.operator_remove_all_async(
this,
executorHandle,
path,
&OnRemoveAllCompleted,
context
);
}
}
}
/// <summary>
/// Creates a presigned read request asynchronously.
/// </summary>
/// <param name="path">Target path in the configured backend.</param>
/// <param name="expiration">Presigned request expiration duration.</param>
/// <param name="cancellationToken">Cancellation token for the managed task.</param>
/// <returns>A task that resolves with a presigned request.</returns>
public Task<PresignedRequest> PresignReadAsync(
string path,
TimeSpan expiration,
CancellationToken cancellationToken = default)
{
return PresignReadAsync(path, expiration, executor: null, cancellationToken);
}
/// <summary>
/// Creates a presigned read request asynchronously using the provided executor.
/// </summary>
public Task<PresignedRequest> PresignReadAsync(
string path,
TimeSpan expiration,
Executor? executor,
CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var executorHandle = GetExecutorHandle(executor);
var expireNanos = Utilities.ToNanoseconds(expiration, nameof(expiration));
return SubmitAsyncOperation<PresignedRequest>(SubmitPresignReadAsync, cancellationToken);
OpenDALResult SubmitPresignReadAsync(long context)
{
unsafe
{
return NativeMethods.operator_presign_read_async(
this,
executorHandle,
path,
expireNanos,
&OnPresignReadCompleted,
context
);
}
}
}
/// <summary>
/// Creates a presigned write request asynchronously.
/// </summary>
public Task<PresignedRequest> PresignWriteAsync(
string path,
TimeSpan expiration,
CancellationToken cancellationToken = default)
{
return PresignWriteAsync(path, expiration, executor: null, cancellationToken);
}
/// <summary>
/// Creates a presigned write request asynchronously using the provided executor.
/// </summary>
public Task<PresignedRequest> PresignWriteAsync(
string path,
TimeSpan expiration,
Executor? executor,
CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var executorHandle = GetExecutorHandle(executor);
var expireNanos = Utilities.ToNanoseconds(expiration, nameof(expiration));
return SubmitAsyncOperation<PresignedRequest>(SubmitPresignWriteAsync, cancellationToken);
OpenDALResult SubmitPresignWriteAsync(long context)
{
unsafe
{
return NativeMethods.operator_presign_write_async(
this,
executorHandle,
path,
expireNanos,
&OnPresignWriteCompleted,
context
);
}
}
}
/// <summary>
/// Creates a presigned stat request asynchronously.
/// </summary>
public Task<PresignedRequest> PresignStatAsync(
string path,
TimeSpan expiration,
CancellationToken cancellationToken = default)
{
return PresignStatAsync(path, expiration, executor: null, cancellationToken);
}
/// <summary>
/// Creates a presigned stat request asynchronously using the provided executor.
/// </summary>
public Task<PresignedRequest> PresignStatAsync(
string path,
TimeSpan expiration,
Executor? executor,
CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var executorHandle = GetExecutorHandle(executor);
var expireNanos = Utilities.ToNanoseconds(expiration, nameof(expiration));
return SubmitAsyncOperation<PresignedRequest>(SubmitPresignStatAsync, cancellationToken);
OpenDALResult SubmitPresignStatAsync(long context)
{
unsafe
{
return NativeMethods.operator_presign_stat_async(
this,
executorHandle,
path,
expireNanos,
&OnPresignStatCompleted,
context
);
}
}
}
/// <summary>
/// Creates a presigned delete request asynchronously.
/// </summary>
public Task<PresignedRequest> PresignDeleteAsync(
string path,
TimeSpan expiration,
CancellationToken cancellationToken = default)
{
return PresignDeleteAsync(path, expiration, executor: null, cancellationToken);
}
/// <summary>
/// Creates a presigned delete request asynchronously using the provided executor.
/// </summary>
public Task<PresignedRequest> PresignDeleteAsync(
string path,
TimeSpan expiration,
Executor? executor,
CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var executorHandle = GetExecutorHandle(executor);
var expireNanos = Utilities.ToNanoseconds(expiration, nameof(expiration));
return SubmitAsyncOperation<PresignedRequest>(SubmitPresignDeleteAsync, cancellationToken);
OpenDALResult SubmitPresignDeleteAsync(long context)
{
unsafe
{
return NativeMethods.operator_presign_delete_async(
this,
executorHandle,
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>
/// <param name="executor">Executor used for stream creation, or <see langword="null"/> to use default executor.</param>
/// <returns>A readable stream over the given path.</returns>
public OperatorInputStream OpenReadStream(
string path,
ReadOptions? options = null,
Executor? executor = null)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var executorHandle = GetExecutorHandle(executor);
using var nativeOptions = options?.BuildNativeOptionsHandle();
var result = NativeMethods.operator_input_stream_create(
this,
executorHandle,
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>
/// <param name="executor">Executor used for stream creation, or <see langword="null"/> to use default executor.</param>
/// <returns>A writable stream over the given path.</returns>
public OperatorOutputStream OpenWriteStream(
string path,
WriteOptions? options = null,
int bufferSize = OperatorOutputStream.DefaultBufferSize,
Executor? executor = null)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var executorHandle = GetExecutorHandle(executor);
using var nativeOptions = options?.BuildNativeOptionsHandle();
var result = NativeMethods.operator_output_stream_create(
this,
executorHandle,
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 executor handle for operation submission.
/// </summary>
/// <param name="executor">Executor instance or <see langword="null"/> to use default executor.</param>
/// <returns>Native executor pointer, or <see cref="IntPtr.Zero"/> when no executor is specified.</returns>
/// <exception cref="ObjectDisposedException">The executor has already been disposed.</exception>
private static IntPtr GetExecutorHandle(Executor? executor)
{
if (executor is null)
{
return IntPtr.Zero;
}
ObjectDisposedException.ThrowIf(executor.IsClosed || executor.IsInvalid, executor);
return executor.DangerousGetHandle();
}
/// <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 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 read operation finishes.
/// </summary>
/// <param name="context">Opaque async state context previously registered by <see cref="AsyncStateRegistry"/>.</param>
/// <param name="result">Read completion result returned by the native layer, including byte buffer payload.</param>
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void OnReadCompleted(long context, OpenDALReadResult result)
{
CompleteAsyncCallback<byte[], OpenDALReadResult>(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 remove-all operation finishes.
/// </summary>
/// <param name="context">Opaque async state context previously registered by <see cref="AsyncStateRegistry"/>.</param>
/// <param name="result">Remove-all completion result returned by the native layer.</param>
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void OnRemoveAllCompleted(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
}