Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions bindings/dotnet/OpenDAL.Tests/Behavior/ListBehaviorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,28 @@ public ListBehaviorTest(BehaviorOperatorFixture fixture)
{
}

[Fact]
public void ListBehavior_Check_Succeeds()
{
if (!Supports(c => c.Read && c.Write && c.List))
{
return;
}

Op.Check();
}

[Fact]
public async Task ListBehavior_Check_SucceedsAsync()
{
if (!Supports(c => c.Read && c.Write && c.List))
{
return;
}

await Op.CheckAsync(CT);
}

[Fact]
public void ListBehavior_ListsEntriesUnderPrefix()
{
Expand Down
34 changes: 34 additions & 0 deletions bindings/dotnet/OpenDAL.Tests/Behavior/StatBehaviorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,40 @@ public async Task StatBehavior_MissingPath_ReturnsNotFoundAsync()
Assert.True(IsMissingError(ex));
}

[Fact]
public void StatBehavior_Exists_ReportsPresence()
{
if (!Supports(c => c.Stat && c.Write))
{
return;
}

var path = NewPath("exists");

Assert.False(Op.Exists(path));

Op.Write(path, RandomBytes(16));

Assert.True(Op.Exists(path));
}

[Fact]
public async Task StatBehavior_Exists_ReportsPresenceAsync()
{
if (!Supports(c => c.Stat && c.Write))
{
return;
}

var path = NewPath("exists-async");

Assert.False(await Op.ExistsAsync(path, CT));

await Op.WriteAsync(path, RandomBytes(16), cancellationToken: CT);

Assert.True(await Op.ExistsAsync(path, CT));
}

[Fact]
public void StatBehavior_WithIfModifiedSince_AppliesCondition()
{
Expand Down
46 changes: 46 additions & 0 deletions bindings/dotnet/OpenDAL/Interop/Result/OpenDALBoolResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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 OpenDAL.Interop.Result.Abstractions;

namespace OpenDAL.Interop.Result;

[StructLayout(LayoutKind.Sequential)]
internal struct OpenDALBoolResult : INativeValueResult<bool>
{
public byte Value;

public OpenDALError Error;

public readonly void Release()
{
NativeMethods.opendal_error_release(Error);
}

public readonly OpenDALError GetError()
{
return Error;
}

public readonly bool ToValue()
{
return Value != 0;
}
}
36 changes: 36 additions & 0 deletions bindings/dotnet/OpenDAL/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,42 @@ long context

#endregion

#region Exists

[LibraryImport(__DllName, EntryPoint = "operator_exists", StringMarshalling = StringMarshalling.Utf8)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static partial OpenDALBoolResult operator_exists(
Operator op,
string path
);

[LibraryImport(__DllName, EntryPoint = "operator_exists_async", StringMarshalling = StringMarshalling.Utf8)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static unsafe partial OpenDALResult operator_exists_async(
Operator op,
string path,
delegate* unmanaged[Cdecl]<long, OpenDALBoolResult, void> callback,
long context
);

#endregion

#region Check

[LibraryImport(__DllName, EntryPoint = "operator_check")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static partial OpenDALResult operator_check(Operator op);

[LibraryImport(__DllName, EntryPoint = "operator_check_async")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static unsafe partial OpenDALResult operator_check_async(
Operator op,
delegate* unmanaged[Cdecl]<long, OpenDALResult, void> callback,
long context
);

#endregion

#region List

[LibraryImport(__DllName, EntryPoint = "operator_list_with_options", StringMarshalling = StringMarshalling.Utf8)]
Expand Down
110 changes: 110 additions & 0 deletions bindings/dotnet/OpenDAL/Operator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,94 @@ OpenDALResult SubmitStatAsync(long context, IntPtr optionsHandle)
}
}

/// <summary>
/// Checks whether the specified path exists.
/// </summary>
/// <remarks>
/// A <c>NotFound</c> error from the backend yields <see langword="false"/>;
/// any other error is thrown.
/// </remarks>
/// <param name="path">Target path in the configured backend.</param>
/// <returns><see langword="true"/> when the path exists.</returns>
public bool Exists(string path)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var result = NativeMethods.operator_exists(this, path);
return ToValueOrThrowAndRelease<bool, OpenDALBoolResult>(result);
}

/// <summary>
/// Checks whether the specified path exists asynchronously.
/// </summary>
/// <remarks>
/// A <c>NotFound</c> error from the backend yields <see langword="false"/>;
/// any other error is thrown.
/// </remarks>
/// <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 <see langword="true"/> when the path exists.</returns>
public Task<bool> ExistsAsync(string path, CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);

return SubmitAsyncOperation<bool>(SubmitExistsAsync, cancellationToken);

OpenDALResult SubmitExistsAsync(long context)
{
unsafe
{
return NativeMethods.operator_exists_async(
this,
path,
&OnExistsCompleted,
context
);
}
}
}

/// <summary>
/// Checks whether the operator can reach its service.
/// </summary>
/// <remarks>
/// Lists the root with a limit of one entry. A <c>NotFound</c> error is
/// treated as success; any other error is thrown.
/// </remarks>
public void Check()
{
ObjectDisposedException.ThrowIf(IsInvalid, this);
var result = NativeMethods.operator_check(this);
ThrowIfErrorAndRelease(result);
}

/// <summary>
/// Checks whether the operator can reach its service asynchronously.
/// </summary>
/// <remarks>
/// Lists the root with a limit of one entry. A <c>NotFound</c> error is
/// treated as success; any other error is thrown.
/// </remarks>
/// <param name="cancellationToken">Cancellation token for the managed task.</param>
/// <returns>A task that completes when the native callback reports completion.</returns>
public Task CheckAsync(CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(IsInvalid, this);

return SubmitAsyncOperation(SubmitCheckAsync, cancellationToken);

OpenDALResult SubmitCheckAsync(long context)
{
unsafe
{
return NativeMethods.operator_check_async(
this,
&OnCheckCompleted,
context
);
}
}
}

/// <summary>
/// Lists entries under the specified path.
/// </summary>
Expand Down Expand Up @@ -1470,6 +1558,28 @@ private static void OnStatCompleted(long context, OpenDALMetadataResult result)
CompleteAsyncCallback<Metadata, OpenDALMetadataResult>(context, result);
}

/// <summary>
/// Native callback invoked when an asynchronous exists operation finishes.
/// </summary>
/// <param name="context">Opaque async state context previously registered by <see cref="AsyncStateRegistry"/>.</param>
/// <param name="result">Exists completion result returned by the native layer.</param>
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void OnExistsCompleted(long context, OpenDALBoolResult result)
{
CompleteAsyncCallback<bool, OpenDALBoolResult>(context, result);
}

/// <summary>
/// Native callback invoked when an asynchronous check operation finishes.
/// </summary>
/// <param name="context">Opaque async state context previously registered by <see cref="AsyncStateRegistry"/>.</param>
/// <param name="result">Check completion result returned by the native layer.</param>
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void OnCheckCompleted(long context, OpenDALResult result)
{
CompleteAsyncCallback(context, result);
}

/// <summary>
/// Native callback invoked when an asynchronous list operation finishes.
/// </summary>
Expand Down
Loading
Loading