Files
UA-.NETStandard/Libraries/Opc.Ua.Di.Server/Builders/SoftwareUpdateFacetWiring.cs
Marc Schier d8a95b4e9e dotnet format sweep (whitespace + IDE + RCS) (#3879)
# Description

Full-repo `dotnet format` sweep across all 73 projects in `UA.slnx` — no behavioral changes.
2026-06-16 06:07:17 +02:00

758 lines
33 KiB
C#

/* ========================================================================
* Copyright (c) 2005-2026 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;
using Microsoft.Extensions.Logging;
using Opc.Ua.Di.Server.SoftwareUpdate;
namespace Opc.Ua.Di.Server.Builders
{
/// <summary>
/// Materialises the OPC 10000-100 §10.3 <c>SoftwareUpdateType</c>
/// instance under a device and binds its method invocations to
/// user-supplied callbacks (with library-supplied default stubs
/// when callbacks are omitted).
/// </summary>
/// <remarks>
/// <para>
/// Address-space layout:
/// </para>
/// <code>
/// {device}
/// └─ SoftwareUpdate (SoftwareUpdateType)
/// ├─ Loading (PackageLoadingType | DirectLoadingType | CachedLoadingType)
/// ├─ PrepareForUpdate (PrepareForUpdateStateMachineType)
/// │ • Methods: Prepare, Abort, Resume
/// ├─ Installation (InstallationStateMachineType)
/// │ • Methods: InstallSoftwarePackage, Resume, Uninstall
/// ├─ PowerCycle (PowerCycleStateMachineType)
/// └─ Confirmation (ConfirmationStateMachineType)
/// • Methods: Confirm
/// </code>
/// <para>
/// The library ships "succeed immediately" defaults for every
/// method handler so callers can opt in to SU with one line and
/// later override individual hooks.
/// </para>
/// </remarks>
internal static class SoftwareUpdateFacetWiring
{
private const string SoftwareUpdateBrowseName = "SoftwareUpdate";
private const string LoadingBrowseName = "Loading";
private const string PrepareForUpdateBrowseName = "PrepareForUpdate";
private const string InstallationBrowseName = "Installation";
private const string PowerCycleBrowseName = "PowerCycle";
private const string ConfirmationBrowseName = "Confirmation";
public static SoftwareUpdateState BuildAndAttach(
DiNodeManager manager,
BaseInstanceState device,
ISoftwarePackageStore packageStore,
SoftwareUpdateBuilder config)
{
var context = manager.SystemContext;
ushort diNs = manager.DiNamespaceIndex;
ILogger logger = manager.Server.Telemetry.CreateLogger(
typeof(SoftwareUpdateFacetWiring));
// Per-device software folder (auto-registered MemorySoftwareFolder
// unless the caller supplied one via WithSoftwareFolder).
ISoftwareFolder folder = config.Folder
?? new MemorySoftwareFolder(device.NodeId);
var callbackContext = new SoftwareUpdateContext(
device.NodeId, context, packageStore, folder);
// Materialise the SoftwareUpdate node itself.
SoftwareUpdateState su = context.CreateInstanceOfSoftwareUpdateType(
parent: device,
browseName: new QualifiedName(SoftwareUpdateBrowseName, diNs));
su.SymbolicName = SoftwareUpdateBrowseName;
su.BrowseName = new QualifiedName(SoftwareUpdateBrowseName, diNs);
su.DisplayName = new LocalizedText(SoftwareUpdateBrowseName);
su.NodeId = context.NodeIdFactory.New(context, su);
su.ReferenceTypeId = Types.ReferenceTypeIds.HasComponent;
su.ModellingRuleId = NodeId.Null;
// Loading subtype (the address-space slot is non-abstract;
// pick the concrete type per the builder's LoadingMode).
su.Loading = CreateLoadingChild(
manager, context, su, config.LoadingMode, diNs, packageStore, logger);
// State machines — all four are present but only Installation
// and Confirmation have method handlers wired by default;
// PrepareForUpdate.Prepare/Abort/Resume also wired.
su.PrepareForUpdate = CreatePrepareForUpdate(
context, su, diNs, config, callbackContext, logger);
su.Installation = CreateInstallation(
context, su, diNs, config, callbackContext, logger);
su.PowerCycle = CreatePowerCycle(context, su, diNs);
su.Confirmation = CreateConfirmation(
context, su, diNs, config, callbackContext, logger);
// Link the SU node into the device subtree and register
// recursively with the manager.
device.AddChild(su);
manager.AddPredefinedNodeAsync(su, CancellationToken.None)
.AsTask().GetAwaiter().GetResult();
return su;
}
private const string FileTransferBrowseName = "FileTransfer";
private static SoftwareLoadingState CreateLoadingChild(
DiNodeManager manager,
ISystemContext context,
SoftwareUpdateState parent,
SoftwareLoadingMode mode,
ushort diNs,
ISoftwarePackageStore packageStore,
ILogger logger)
{
var browseName = new QualifiedName(LoadingBrowseName, diNs);
SoftwareLoadingState loading = mode switch
{
SoftwareLoadingMode.Package =>
context.CreateInstanceOfPackageLoadingType(parent, browseName),
SoftwareLoadingMode.Direct =>
context.CreateInstanceOfDirectLoadingType(parent, browseName),
SoftwareLoadingMode.Cached =>
context.CreateInstanceOfCachedLoadingType(parent, browseName),
_ => throw new ArgumentOutOfRangeException(nameof(mode))
};
FinaliseChild(context, loading, browseName);
// PackageLoadingType and all its subtypes (Direct, Cached) own
// an optional FileTransfer slot. The wiring is shared because
// the spec semantics are identical: upload a payload, commit
// it to the package store. Subtype-specific behaviour
// (Direct/Cached) is layered on top by the application's
// OnInstall handler.
AttachFileTransfer(manager, context, (PackageLoadingState)loading, diNs,
packageStore, logger);
return loading;
}
/// <summary>
/// Materialises a <see cref="TemporaryFileTransferState"/> under
/// the supplied <see cref="PackageLoadingState"/> and binds a
/// <see cref="SoftwareUpdateFileTransferManager"/> to it so
/// <c>GenerateFileForWrite</c> / <c>CloseAndCommit</c> commit
/// uploaded payloads to the package store. Idempotent — the
/// manager keeps a reference to the FileTransfer node for the
/// lifetime of the SU subtree.
/// </summary>
private static void AttachFileTransfer(
DiNodeManager manager,
ISystemContext context,
PackageLoadingState packageLoading,
ushort diNs,
ISoftwarePackageStore packageStore,
ILogger logger)
{
var browseName = new QualifiedName(FileTransferBrowseName, diNs);
TemporaryFileTransferState fileTransfer =
packageLoading.FileTransfer
?? context.CreateInstanceOfTemporaryFileTransferType(
packageLoading, browseName);
FinaliseChild(context, fileTransfer, browseName);
packageLoading.FileTransfer = fileTransfer;
// The TemporaryFileTransfer manager registers itself onto the
// GenerateFileForWrite / CloseAndCommit method handlers via
// OnCall delegates that capture the manager instance, so the
// GC will keep it alive for the address-space lifetime. The
// manager's only IDisposable concern is closing open upload
// streams on shutdown, which a per-device dispose chain would
// address — TODO: thread the manager through DiNodeManager's
// shutdown path when the SU facet adds a managed disposal API.
#pragma warning disable CA2000 // see comment above re: lifetime anchored by OnCall delegates
_ = new SoftwareUpdateFileTransferManager(
fileTransfer, manager, packageStore, logger);
#pragma warning restore CA2000
}
private static PrepareForUpdateStateMachineState CreatePrepareForUpdate(
ISystemContext context,
SoftwareUpdateState parent,
ushort diNs,
SoftwareUpdateBuilder config,
SoftwareUpdateContext callbackContext,
ILogger logger)
{
var browseName = new QualifiedName(PrepareForUpdateBrowseName, diNs);
PrepareForUpdateStateMachineState sm =
context.CreateInstanceOfPrepareForUpdateStateMachineType(parent, browseName);
FinaliseChild(context, sm, browseName);
// CreateInstanceOf already populated Prepare + Abort; only the
// Resume slot is left empty by the source-gen factory.
sm.Resume ??= CreateMethodChild(context, sm,
new QualifiedName("Resume", diNs));
if (sm.Prepare != null)
{
FinaliseChild(context, sm.Prepare,
new QualifiedName("Prepare", diNs));
sm.Prepare.OnCallMethod2Async = (ctx, m, oid, ins, outs, ct) =>
InvokePrepareAsync(config, callbackContext, sm, diNs, logger, ct);
}
if (sm.Abort != null)
{
FinaliseChild(context, sm.Abort,
new QualifiedName("Abort", diNs));
}
sm.Resume?.OnCallMethod2Async = (ctx, m, oid, ins, outs, ct) =>
InvokePrepareAsync(config, callbackContext, sm, diNs, logger, ct);
SoftwareUpdateStateMachineDispatcher.InitializeToInitialState(
sm,
SoftwareUpdateStateMachineDispatcher.PrepareForUpdate_Idle,
diNs,
context);
return sm;
}
private static InstallationStateMachineState CreateInstallation(
ISystemContext context,
SoftwareUpdateState parent,
ushort diNs,
SoftwareUpdateBuilder config,
SoftwareUpdateContext callbackContext,
ILogger logger)
{
var browseName = new QualifiedName(InstallationBrowseName, diNs);
InstallationStateMachineState sm =
context.CreateInstanceOfInstallationStateMachineType(parent, browseName);
FinaliseChild(context, sm, browseName);
// CreateInstanceOf only adds CurrentState + Resume. Materialise
// the optional InstallSoftwarePackage / InstallFiles / Uninstall
// method children explicitly.
var installPkgBn = new QualifiedName("InstallSoftwarePackage", diNs);
InstallSoftwarePackageMethodState installPkg =
context.CreateInstanceOfInstallSoftwarePackageMethodType(sm, installPkgBn);
FinaliseChild(context, installPkg, installPkgBn);
sm.AddChild(installPkg);
sm.InstallSoftwarePackage = installPkg;
installPkg.OnCallMethod2Async =
(ctx, m, oid, ins, outs, ct) =>
InvokeInstallSoftwarePackageAsync(
config, callbackContext, sm, diNs, logger, ins, ct);
var installFilesBn = new QualifiedName("InstallFiles", diNs);
InstallFilesMethodState installFiles =
context.CreateInstanceOfInstallFilesMethodType(sm, installFilesBn);
FinaliseChild(context, installFiles, installFilesBn);
sm.AddChild(installFiles);
sm.InstallFiles = installFiles;
installFiles.OnCallMethod2Async =
(ctx, m, oid, ins, outs, ct) =>
InvokeInstallFilesAsync(config, callbackContext, sm, diNs, logger, ct);
var uninstallBn = new QualifiedName("Uninstall", diNs);
MethodState uninstall = CreateMethodChild(context, sm, uninstallBn);
sm.Uninstall = uninstall;
uninstall.OnCallMethod2Async =
(ctx, m, oid, ins, outs, ct) =>
InvokeUninstallAsync(config, callbackContext, sm, diNs, logger, ct);
if (sm.Resume != null)
{
FinaliseChild(context, sm.Resume,
new QualifiedName("Resume", diNs));
}
SoftwareUpdateStateMachineDispatcher.InitializeToInitialState(
sm,
SoftwareUpdateStateMachineDispatcher.Installation_Idle,
diNs,
context);
return sm;
}
private static PowerCycleStateMachineState CreatePowerCycle(
ISystemContext context,
SoftwareUpdateState parent,
ushort diNs)
{
var browseName = new QualifiedName(PowerCycleBrowseName, diNs);
PowerCycleStateMachineState sm =
context.CreateInstanceOfPowerCycleStateMachineType(parent, browseName);
FinaliseChild(context, sm, browseName);
SoftwareUpdateStateMachineDispatcher.InitializeToInitialState(
sm,
SoftwareUpdateStateMachineDispatcher.PowerCycle_NotWaiting,
diNs,
context);
return sm;
}
private static ConfirmationStateMachineState CreateConfirmation(
ISystemContext context,
SoftwareUpdateState parent,
ushort diNs,
SoftwareUpdateBuilder config,
SoftwareUpdateContext callbackContext,
ILogger logger)
{
var browseName = new QualifiedName(ConfirmationBrowseName, diNs);
ConfirmationStateMachineState sm =
context.CreateInstanceOfConfirmationStateMachineType(parent, browseName);
FinaliseChild(context, sm, browseName);
if (sm.Confirm != null)
{
FinaliseChild(context, sm.Confirm,
new QualifiedName("Confirm", diNs));
sm.Confirm.OnCallMethod2Async =
(ctx, m, oid, ins, outs, ct) =>
InvokeConfirmAsync(config, callbackContext, sm, diNs, logger, ct);
}
SoftwareUpdateStateMachineDispatcher.InitializeToInitialState(
sm,
SoftwareUpdateStateMachineDispatcher.Confirmation_NotWaitingForConfirm,
diNs,
context);
return sm;
}
/// <summary>
/// Creates a bare <see cref="MethodState"/> child with a
/// per-instance NodeId and the supplied browse name. Used for
/// inherited methods (Resume / Uninstall) that aren't typed
/// in the source-generated model.
/// </summary>
private static MethodState CreateMethodChild(
ISystemContext context,
NodeState parent,
QualifiedName browseName)
{
var method = new MethodState(parent);
FinaliseChild(context, method, browseName);
method.Executable = true;
method.UserExecutable = true;
return method;
}
/// <summary>
/// Common finalisation step for an SU child: assign a per-instance
/// NodeId via the manager's <see cref="INodeIdFactory"/>, set the
/// HasComponent reference, and clear the type's modelling rule
/// so the instance isn't treated as a placeholder.
/// </summary>
private static void FinaliseChild(
ISystemContext context,
BaseInstanceState child,
QualifiedName browseName)
{
child.SymbolicName = browseName.Name ?? string.Empty;
child.BrowseName = browseName;
child.DisplayName = new LocalizedText(browseName.Name);
child.NodeId = context.NodeIdFactory.New(context, child);
child.ReferenceTypeId = Types.ReferenceTypeIds.HasComponent;
child.ModellingRuleId = NodeId.Null;
}
private static async ValueTask<ServiceResult> InvokePrepareAsync(
SoftwareUpdateBuilder config,
SoftwareUpdateContext context,
PrepareForUpdateStateMachineState sm,
ushort diNs,
ILogger logger,
CancellationToken cancellationToken)
{
ISystemContext sys = context.SystemContext;
SoftwareUpdateStateMachineDispatcher.Move(
sm,
SoftwareUpdateStateMachineDispatcher.PrepareForUpdate_Preparing,
SoftwareUpdateStateMachineDispatcher.PrepareForUpdate_IdleToPreparing,
diNs,
sys);
await SoftwareUpdateStateMachineDispatcher.FireAsync(
config.PrepareStateChanged, context,
new SoftwareUpdateStateChange(SoftwareUpdatePhase.Started, string.Empty, null),
logger,
cancellationToken).ConfigureAwait(false);
try
{
if (config.PrepareHandler != null)
{
await config.PrepareHandler(context, cancellationToken)
.ConfigureAwait(false);
}
SoftwareUpdateStateMachineDispatcher.Move(
sm,
SoftwareUpdateStateMachineDispatcher.PrepareForUpdate_PreparedForUpdate,
SoftwareUpdateStateMachineDispatcher.PrepareForUpdate_PreparingToPreparedForUpdate,
diNs,
sys);
await SoftwareUpdateStateMachineDispatcher.FireAsync(
config.PrepareStateChanged, context,
new SoftwareUpdateStateChange(SoftwareUpdatePhase.Completed, string.Empty, null),
logger,
cancellationToken).ConfigureAwait(false);
return ServiceResult.Good;
}
catch (Exception ex)
{
SoftwareUpdateStateMachineDispatcher.Move(
sm,
SoftwareUpdateStateMachineDispatcher.PrepareForUpdate_Idle,
SoftwareUpdateStateMachineDispatcher.PrepareForUpdate_PreparingToIdle,
diNs,
sys);
await SoftwareUpdateStateMachineDispatcher.FireAsync(
config.PrepareStateChanged, context,
new SoftwareUpdateStateChange(SoftwareUpdatePhase.Failed, ex.Message, null),
logger,
cancellationToken).ConfigureAwait(false);
return new ServiceResult(ex);
}
}
private static async ValueTask<ServiceResult> InvokeInstallSoftwarePackageAsync(
SoftwareUpdateBuilder config,
SoftwareUpdateContext context,
InstallationStateMachineState sm,
ushort diNs,
ILogger logger,
ArrayOf<Variant> inputs,
CancellationToken cancellationToken)
{
ISystemContext sys = context.SystemContext;
SoftwareUpdateStateMachineDispatcher.Move(
sm,
SoftwareUpdateStateMachineDispatcher.Installation_Installing,
SoftwareUpdateStateMachineDispatcher.Installation_IdleToInstalling,
diNs,
sys);
SoftwareUpdateStateMachineDispatcher.SetPercentComplete(sm, 0, sys);
await SoftwareUpdateStateMachineDispatcher.FireAsync(
config.InstallationStateChanged, context,
new SoftwareUpdateStateChange(SoftwareUpdatePhase.Started, string.Empty, 0),
logger,
cancellationToken).ConfigureAwait(false);
try
{
// InstallSoftwarePackage(ManufacturerUri, SoftwareRevision,
// PatchIdentifiers[], Hash)
string manufacturerUri = ExtractString(inputs, 0);
string softwareRevision = ExtractString(inputs, 1);
var package = new SoftwarePackage(
Id: BuildPackageId(manufacturerUri, softwareRevision),
Version: softwareRevision,
Vendor: manufacturerUri,
Description: string.Empty,
SizeBytes: 0,
CreatedAt: DateTimeOffset.UtcNow,
Hash: string.Empty);
if (config.InstallHandler != null)
{
await config.InstallHandler(context, package, cancellationToken)
.ConfigureAwait(false);
}
else
{
// Default stub: record the new version in the folder.
SoftwarePackage? existing = await context.SoftwareFolder
.GetVersionAsync(package.Version, cancellationToken)
.ConfigureAwait(false);
if (existing is null)
{
await context.SoftwareFolder
.AddVersionAsync(package, System.IO.Stream.Null, cancellationToken)
.ConfigureAwait(false);
}
await context.SoftwareFolder
.SetCurrentVersionAsync(package.Version, cancellationToken)
.ConfigureAwait(false);
}
SoftwareUpdateStateMachineDispatcher.SetPercentComplete(sm, 100, sys);
SoftwareUpdateStateMachineDispatcher.Move(
sm,
SoftwareUpdateStateMachineDispatcher.Installation_Idle,
SoftwareUpdateStateMachineDispatcher.Installation_InstallingToIdle,
diNs,
sys);
await SoftwareUpdateStateMachineDispatcher.FireAsync(
config.InstallationStateChanged, context,
new SoftwareUpdateStateChange(SoftwareUpdatePhase.Completed, string.Empty, 100),
logger,
cancellationToken).ConfigureAwait(false);
return ServiceResult.Good;
}
catch (Exception ex)
{
SoftwareUpdateStateMachineDispatcher.Move(
sm,
SoftwareUpdateStateMachineDispatcher.Installation_Error,
SoftwareUpdateStateMachineDispatcher.Installation_InstallingToError,
diNs,
sys);
await SoftwareUpdateStateMachineDispatcher.FireAsync(
config.InstallationStateChanged, context,
new SoftwareUpdateStateChange(SoftwareUpdatePhase.Failed, ex.Message, null),
logger,
cancellationToken).ConfigureAwait(false);
return new ServiceResult(ex);
}
}
private static async ValueTask<ServiceResult> InvokeInstallFilesAsync(
SoftwareUpdateBuilder config,
SoftwareUpdateContext context,
InstallationStateMachineState sm,
ushort diNs,
ILogger logger,
CancellationToken cancellationToken)
{
ISystemContext sys = context.SystemContext;
SoftwareUpdateStateMachineDispatcher.Move(
sm,
SoftwareUpdateStateMachineDispatcher.Installation_Installing,
SoftwareUpdateStateMachineDispatcher.Installation_IdleToInstalling,
diNs,
sys);
SoftwareUpdateStateMachineDispatcher.SetPercentComplete(sm, 0, sys);
await SoftwareUpdateStateMachineDispatcher.FireAsync(
config.InstallationStateChanged, context,
new SoftwareUpdateStateChange(SoftwareUpdatePhase.Started, string.Empty, 0),
logger,
cancellationToken).ConfigureAwait(false);
// InstallFiles flows the same way as InstallSoftwarePackage
// from the application's point of view, just with a NodeId[]
// input. The default callback synthesises a minimal package
// entry; production servers override OnInstall to carry the
// real file metadata.
try
{
if (config.InstallHandler != null)
{
var package = new SoftwarePackage(
Id: $"install-files-{DateTime.UtcNow:yyyyMMddHHmmss}",
Version: string.Empty,
Vendor: string.Empty,
Description: "Files-based install",
SizeBytes: 0,
CreatedAt: DateTimeOffset.UtcNow,
Hash: string.Empty);
await config.InstallHandler(context, package, cancellationToken)
.ConfigureAwait(false);
}
SoftwareUpdateStateMachineDispatcher.SetPercentComplete(sm, 100, sys);
SoftwareUpdateStateMachineDispatcher.Move(
sm,
SoftwareUpdateStateMachineDispatcher.Installation_Idle,
SoftwareUpdateStateMachineDispatcher.Installation_InstallingToIdle,
diNs,
sys);
await SoftwareUpdateStateMachineDispatcher.FireAsync(
config.InstallationStateChanged, context,
new SoftwareUpdateStateChange(SoftwareUpdatePhase.Completed, string.Empty, 100),
logger,
cancellationToken).ConfigureAwait(false);
return ServiceResult.Good;
}
catch (Exception ex)
{
SoftwareUpdateStateMachineDispatcher.Move(
sm,
SoftwareUpdateStateMachineDispatcher.Installation_Error,
SoftwareUpdateStateMachineDispatcher.Installation_InstallingToError,
diNs,
sys);
await SoftwareUpdateStateMachineDispatcher.FireAsync(
config.InstallationStateChanged, context,
new SoftwareUpdateStateChange(SoftwareUpdatePhase.Failed, ex.Message, null),
logger,
cancellationToken).ConfigureAwait(false);
return new ServiceResult(ex);
}
}
private static async ValueTask<ServiceResult> InvokeConfirmAsync(
SoftwareUpdateBuilder config,
SoftwareUpdateContext context,
ConfirmationStateMachineState sm,
ushort diNs,
ILogger logger,
CancellationToken cancellationToken)
{
ISystemContext sys = context.SystemContext;
await SoftwareUpdateStateMachineDispatcher.FireAsync(
config.ConfirmStateChanged, context,
new SoftwareUpdateStateChange(SoftwareUpdatePhase.Started, string.Empty, null),
logger,
cancellationToken).ConfigureAwait(false);
try
{
if (config.ConfirmHandler != null)
{
await config.ConfirmHandler(context, true, cancellationToken)
.ConfigureAwait(false);
}
SoftwareUpdateStateMachineDispatcher.Move(
sm,
SoftwareUpdateStateMachineDispatcher.Confirmation_NotWaitingForConfirm,
SoftwareUpdateStateMachineDispatcher.Confirmation_WaitingToNotWaiting,
diNs,
sys);
await SoftwareUpdateStateMachineDispatcher.FireAsync(
config.ConfirmStateChanged, context,
new SoftwareUpdateStateChange(SoftwareUpdatePhase.Completed, string.Empty, null),
logger,
cancellationToken).ConfigureAwait(false);
return ServiceResult.Good;
}
catch (Exception ex)
{
await SoftwareUpdateStateMachineDispatcher.FireAsync(
config.ConfirmStateChanged, context,
new SoftwareUpdateStateChange(SoftwareUpdatePhase.Failed, ex.Message, null),
logger,
cancellationToken).ConfigureAwait(false);
return new ServiceResult(ex);
}
}
private static async ValueTask<ServiceResult> InvokeUninstallAsync(
SoftwareUpdateBuilder config,
SoftwareUpdateContext context,
InstallationStateMachineState sm,
ushort diNs,
ILogger logger,
CancellationToken cancellationToken)
{
ISystemContext sys = context.SystemContext;
SoftwareUpdateStateMachineDispatcher.Move(
sm,
SoftwareUpdateStateMachineDispatcher.Installation_Installing,
SoftwareUpdateStateMachineDispatcher.Installation_IdleToInstalling,
diNs,
sys);
await SoftwareUpdateStateMachineDispatcher.FireAsync(
config.InstallationStateChanged, context,
new SoftwareUpdateStateChange(SoftwareUpdatePhase.Started, "Uninstall", null),
logger,
cancellationToken).ConfigureAwait(false);
try
{
if (config.UninstallHandler != null)
{
await config.UninstallHandler(context, cancellationToken)
.ConfigureAwait(false);
}
SoftwareUpdateStateMachineDispatcher.Move(
sm,
SoftwareUpdateStateMachineDispatcher.Installation_Idle,
SoftwareUpdateStateMachineDispatcher.Installation_InstallingToIdle,
diNs,
sys);
await SoftwareUpdateStateMachineDispatcher.FireAsync(
config.InstallationStateChanged, context,
new SoftwareUpdateStateChange(SoftwareUpdatePhase.Completed, "Uninstall", null),
logger,
cancellationToken).ConfigureAwait(false);
return ServiceResult.Good;
}
catch (Exception ex)
{
SoftwareUpdateStateMachineDispatcher.Move(
sm,
SoftwareUpdateStateMachineDispatcher.Installation_Error,
SoftwareUpdateStateMachineDispatcher.Installation_InstallingToError,
diNs,
sys);
await SoftwareUpdateStateMachineDispatcher.FireAsync(
config.InstallationStateChanged, context,
new SoftwareUpdateStateChange(SoftwareUpdatePhase.Failed, ex.Message, null),
logger,
cancellationToken).ConfigureAwait(false);
return new ServiceResult(ex);
}
}
private static string ExtractString(ArrayOf<Variant> inputs, int index)
{
if (inputs.Count <= index)
{
return string.Empty;
}
return inputs[index].TryGetValue(out string s) ? s ?? string.Empty : string.Empty;
}
private static string BuildPackageId(string manufacturerUri, string version)
{
string m = string.IsNullOrEmpty(manufacturerUri) ? "unknown" : manufacturerUri;
string v = string.IsNullOrEmpty(version) ? "0.0.0" : version;
return $"{m}:{v}";
}
}
}