/* ========================================================================
* Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved.
*
* OPC Foundation MIT License 1.00
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* The complete license agreement can be found here:
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Opc.Ua.Client.FileSystem
{
///
/// Client for the OPC UA TemporaryFileTransferType
/// (Part 5 §C.5). Wraps the
/// proxy and exposes
/// the read/commit lifecycles as /
/// values.
///
///
///
/// The temporary-file-transfer pattern is intentionally separate
/// from because its lifecycle does
/// not fit the abstraction: the server
/// allocates a transient file, the client streams data through it,
/// and a final commit (or rollback) signals the server to either
/// publish or discard the result.
///
///
/// owns the close lifecycle for
/// the underlying handle: exactly one terminal call is made,
/// either (which
/// invokes CloseAndCommit) or
/// (which falls
/// back to Close). The wrapped
/// itself does NOT close the server handle on disposal.
///
///
public sealed class TemporaryFileTransferClient
{
///
/// Creates a new client rooted at the supplied
/// TemporaryFileTransferType object.
///
/// The OPC UA session.
/// NodeId of the
/// TemporaryFileTransferType instance.
/// Optional configuration; defaults are
/// applied when null.
public TemporaryFileTransferClient(
ISession session,
NodeId temporaryFileTransferObjectId,
FileSystemClientOptions? options = null)
{
Session = session ?? throw new ArgumentNullException(nameof(session));
if (temporaryFileTransferObjectId.IsNull)
{
throw new ArgumentNullException(nameof(temporaryFileTransferObjectId));
}
ObjectId = temporaryFileTransferObjectId;
Options = (options ?? new FileSystemClientOptions()).Clone();
Options.Validate();
Proxy = new TemporaryFileTransferTypeClient(
session,
temporaryFileTransferObjectId,
session.MessageContext.Telemetry);
}
/// The session.
public ISession Session { get; }
/// The NodeId of the underlying
/// TemporaryFileTransferType object.
public NodeId ObjectId { get; }
/// The (cloned, immutable) configuration.
public FileSystemClientOptions Options { get; }
/// The underlying generated proxy.
public TemporaryFileTransferTypeClient Proxy { get; }
///
/// Asks the server to generate a temporary file for reading.
/// Returns a wrapping the server
/// handle; disposing the stream issues Close per the
/// regular FileType lifecycle.
///
/// Server-defined generation
/// options; pass default for "no options".
/// Cancellation token.
public async ValueTask GenerateFileForReadAsync(
Variant generateOptions = default,
CancellationToken ct = default)
{
(NodeId fileNodeId, uint handle, _) = await Proxy
.GenerateFileForReadAsync(generateOptions, ct)
.ConfigureAwait(false);
var fileProxy = new FileTypeClient(
Session,
fileNodeId,
Session.MessageContext.Telemetry);
try
{
return new UaFileStream(
fileProxy,
handle,
UaFileMode.Read,
initialLength: 0,
initialPosition: 0,
chunkSize: Options.ChunkSize);
}
catch
{
try
{
await fileProxy.CloseAsync(handle, CancellationToken.None)
.ConfigureAwait(false);
}
catch
{
// Best-effort cleanup.
}
throw;
}
}
///
/// Asks the server to allocate a temporary file for writing.
/// The returned owns the
/// close lifecycle: call
/// to publish
/// the file or rely on
/// to roll back.
///
/// Server-defined generation
/// options.
/// Cancellation token.
public async ValueTask GenerateFileForWriteAsync(
Variant generateOptions = default,
CancellationToken ct = default)
{
(NodeId fileNodeId, uint handle) = await Proxy
.GenerateFileForWriteAsync(generateOptions, ct)
.ConfigureAwait(false);
var fileProxy = new FileTypeClient(
Session,
fileNodeId,
Session.MessageContext.Telemetry);
return new UaTemporaryWriteFile(
Proxy,
fileProxy,
fileNodeId,
handle,
Options.ChunkSize);
}
}
}