/* ======================================================================== * 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); } } }