Add bulk MXAccess subscription commands
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -80,6 +80,12 @@ message MxCommand {
|
||||
WriteSecured2Command write_secured2 = 25;
|
||||
AuthenticateUserCommand authenticate_user = 26;
|
||||
ArchestrAUserToIdCommand archestra_user_to_id = 27;
|
||||
AddItemBulkCommand add_item_bulk = 28;
|
||||
AdviseItemBulkCommand advise_item_bulk = 29;
|
||||
RemoveItemBulkCommand remove_item_bulk = 30;
|
||||
UnAdviseItemBulkCommand un_advise_item_bulk = 31;
|
||||
SubscribeBulkCommand subscribe_bulk = 32;
|
||||
UnsubscribeBulkCommand unsubscribe_bulk = 33;
|
||||
PingCommand ping = 100;
|
||||
GetSessionStateCommand get_session_state = 101;
|
||||
GetWorkerInfoCommand get_worker_info = 102;
|
||||
@@ -108,6 +114,12 @@ enum MxCommandKind {
|
||||
MX_COMMAND_KIND_WRITE_SECURED2 = 16;
|
||||
MX_COMMAND_KIND_AUTHENTICATE_USER = 17;
|
||||
MX_COMMAND_KIND_ARCHESTRA_USER_TO_ID = 18;
|
||||
MX_COMMAND_KIND_ADD_ITEM_BULK = 19;
|
||||
MX_COMMAND_KIND_ADVISE_ITEM_BULK = 20;
|
||||
MX_COMMAND_KIND_REMOVE_ITEM_BULK = 21;
|
||||
MX_COMMAND_KIND_UN_ADVISE_ITEM_BULK = 22;
|
||||
MX_COMMAND_KIND_SUBSCRIBE_BULK = 23;
|
||||
MX_COMMAND_KIND_UNSUBSCRIBE_BULK = 24;
|
||||
MX_COMMAND_KIND_PING = 100;
|
||||
MX_COMMAND_KIND_GET_SESSION_STATE = 101;
|
||||
MX_COMMAND_KIND_GET_WORKER_INFO = 102;
|
||||
@@ -224,6 +236,36 @@ message ArchestrAUserToIdCommand {
|
||||
string user_id_guid = 2;
|
||||
}
|
||||
|
||||
message AddItemBulkCommand {
|
||||
int32 server_handle = 1;
|
||||
repeated string tag_addresses = 2;
|
||||
}
|
||||
|
||||
message AdviseItemBulkCommand {
|
||||
int32 server_handle = 1;
|
||||
repeated int32 item_handles = 2;
|
||||
}
|
||||
|
||||
message RemoveItemBulkCommand {
|
||||
int32 server_handle = 1;
|
||||
repeated int32 item_handles = 2;
|
||||
}
|
||||
|
||||
message UnAdviseItemBulkCommand {
|
||||
int32 server_handle = 1;
|
||||
repeated int32 item_handles = 2;
|
||||
}
|
||||
|
||||
message SubscribeBulkCommand {
|
||||
int32 server_handle = 1;
|
||||
repeated string tag_addresses = 2;
|
||||
}
|
||||
|
||||
message UnsubscribeBulkCommand {
|
||||
int32 server_handle = 1;
|
||||
repeated int32 item_handles = 2;
|
||||
}
|
||||
|
||||
message PingCommand {
|
||||
string message = 1;
|
||||
}
|
||||
@@ -264,6 +306,12 @@ message MxCommandReply {
|
||||
ActivateReply activate = 25;
|
||||
AuthenticateUserReply authenticate_user = 26;
|
||||
ArchestrAUserToIdReply archestra_user_to_id = 27;
|
||||
BulkSubscribeReply add_item_bulk = 28;
|
||||
BulkSubscribeReply advise_item_bulk = 29;
|
||||
BulkSubscribeReply remove_item_bulk = 30;
|
||||
BulkSubscribeReply un_advise_item_bulk = 31;
|
||||
BulkSubscribeReply subscribe_bulk = 32;
|
||||
BulkSubscribeReply unsubscribe_bulk = 33;
|
||||
SessionStateReply session_state = 100;
|
||||
WorkerInfoReply worker_info = 101;
|
||||
DrainEventsReply drain_events = 102;
|
||||
@@ -302,6 +350,18 @@ message ArchestrAUserToIdReply {
|
||||
int32 user_id = 1;
|
||||
}
|
||||
|
||||
message SubscribeResult {
|
||||
int32 server_handle = 1;
|
||||
string tag_address = 2;
|
||||
int32 item_handle = 3;
|
||||
bool was_successful = 4;
|
||||
string error_message = 5;
|
||||
}
|
||||
|
||||
message BulkSubscribeReply {
|
||||
repeated SubscribeResult results = 1;
|
||||
}
|
||||
|
||||
message SessionStateReply {
|
||||
SessionState state = 1;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ public sealed class MxAccessGatewayService(
|
||||
reply.Capabilities.Add("unary-close-session");
|
||||
reply.Capabilities.Add("unary-invoke");
|
||||
reply.Capabilities.Add("server-stream-events");
|
||||
reply.Capabilities.Add("bulk-subscribe-commands");
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
@@ -85,6 +85,12 @@ public sealed class MxAccessGrpcRequestValidator
|
||||
MxCommandKind.WriteSecured2 => MxCommand.PayloadOneofCase.WriteSecured2,
|
||||
MxCommandKind.AuthenticateUser => MxCommand.PayloadOneofCase.AuthenticateUser,
|
||||
MxCommandKind.ArchestraUserToId => MxCommand.PayloadOneofCase.ArchestraUserToId,
|
||||
MxCommandKind.AddItemBulk => MxCommand.PayloadOneofCase.AddItemBulk,
|
||||
MxCommandKind.AdviseItemBulk => MxCommand.PayloadOneofCase.AdviseItemBulk,
|
||||
MxCommandKind.RemoveItemBulk => MxCommand.PayloadOneofCase.RemoveItemBulk,
|
||||
MxCommandKind.UnAdviseItemBulk => MxCommand.PayloadOneofCase.UnAdviseItemBulk,
|
||||
MxCommandKind.SubscribeBulk => MxCommand.PayloadOneofCase.SubscribeBulk,
|
||||
MxCommandKind.UnsubscribeBulk => MxCommand.PayloadOneofCase.UnsubscribeBulk,
|
||||
MxCommandKind.Ping => MxCommand.PayloadOneofCase.Ping,
|
||||
MxCommandKind.GetSessionState => MxCommand.PayloadOneofCase.GetSessionState,
|
||||
MxCommandKind.GetWorkerInfo => MxCommand.PayloadOneofCase.GetWorkerInfo,
|
||||
|
||||
@@ -247,6 +247,120 @@ public sealed class GatewaySession
|
||||
return await workerClient.InvokeAsync(command, CommandTimeout, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<SubscribeResult>> AddItemBulkAsync(
|
||||
int serverHandle,
|
||||
IReadOnlyList<string> tagAddresses,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(tagAddresses);
|
||||
|
||||
AddItemBulkCommand bulkCommand = new() { ServerHandle = serverHandle };
|
||||
bulkCommand.TagAddresses.Add(tagAddresses);
|
||||
return InvokeBulkAsync(
|
||||
new MxCommand
|
||||
{
|
||||
Kind = MxCommandKind.AddItemBulk,
|
||||
AddItemBulk = bulkCommand,
|
||||
},
|
||||
reply => reply.AddItemBulk,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<SubscribeResult>> AdviseItemBulkAsync(
|
||||
int serverHandle,
|
||||
IReadOnlyList<int> itemHandles,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(itemHandles);
|
||||
|
||||
AdviseItemBulkCommand bulkCommand = new() { ServerHandle = serverHandle };
|
||||
bulkCommand.ItemHandles.Add(itemHandles);
|
||||
return InvokeBulkAsync(
|
||||
new MxCommand
|
||||
{
|
||||
Kind = MxCommandKind.AdviseItemBulk,
|
||||
AdviseItemBulk = bulkCommand,
|
||||
},
|
||||
reply => reply.AdviseItemBulk,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<SubscribeResult>> RemoveItemBulkAsync(
|
||||
int serverHandle,
|
||||
IReadOnlyList<int> itemHandles,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(itemHandles);
|
||||
|
||||
RemoveItemBulkCommand bulkCommand = new() { ServerHandle = serverHandle };
|
||||
bulkCommand.ItemHandles.Add(itemHandles);
|
||||
return InvokeBulkAsync(
|
||||
new MxCommand
|
||||
{
|
||||
Kind = MxCommandKind.RemoveItemBulk,
|
||||
RemoveItemBulk = bulkCommand,
|
||||
},
|
||||
reply => reply.RemoveItemBulk,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<SubscribeResult>> UnAdviseItemBulkAsync(
|
||||
int serverHandle,
|
||||
IReadOnlyList<int> itemHandles,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(itemHandles);
|
||||
|
||||
UnAdviseItemBulkCommand bulkCommand = new() { ServerHandle = serverHandle };
|
||||
bulkCommand.ItemHandles.Add(itemHandles);
|
||||
return InvokeBulkAsync(
|
||||
new MxCommand
|
||||
{
|
||||
Kind = MxCommandKind.UnAdviseItemBulk,
|
||||
UnAdviseItemBulk = bulkCommand,
|
||||
},
|
||||
reply => reply.UnAdviseItemBulk,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<SubscribeResult>> SubscribeBulkAsync(
|
||||
int serverHandle,
|
||||
IReadOnlyList<string> tagAddresses,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(tagAddresses);
|
||||
|
||||
SubscribeBulkCommand bulkCommand = new() { ServerHandle = serverHandle };
|
||||
bulkCommand.TagAddresses.Add(tagAddresses);
|
||||
return InvokeBulkAsync(
|
||||
new MxCommand
|
||||
{
|
||||
Kind = MxCommandKind.SubscribeBulk,
|
||||
SubscribeBulk = bulkCommand,
|
||||
},
|
||||
reply => reply.SubscribeBulk,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<SubscribeResult>> UnsubscribeBulkAsync(
|
||||
int serverHandle,
|
||||
IReadOnlyList<int> itemHandles,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(itemHandles);
|
||||
|
||||
UnsubscribeBulkCommand bulkCommand = new() { ServerHandle = serverHandle };
|
||||
bulkCommand.ItemHandles.Add(itemHandles);
|
||||
return InvokeBulkAsync(
|
||||
new MxCommand
|
||||
{
|
||||
Kind = MxCommandKind.UnsubscribeBulk,
|
||||
UnsubscribeBulk = bulkCommand,
|
||||
},
|
||||
reply => reply.UnsubscribeBulk,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
public IAsyncEnumerable<WorkerEvent> ReadEventsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
IWorkerClient workerClient = GetReadyWorkerClient();
|
||||
@@ -308,6 +422,35 @@ public sealed class GatewaySession
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IReadOnlyList<SubscribeResult>> InvokeBulkAsync(
|
||||
MxCommand command,
|
||||
Func<MxCommandReply, BulkSubscribeReply?> payloadAccessor,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
WorkerCommandReply workerReply = await InvokeAsync(
|
||||
new WorkerCommand { Command = command },
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
MxCommandReply reply = workerReply.Reply ?? new MxCommandReply
|
||||
{
|
||||
ProtocolStatus = new ProtocolStatus
|
||||
{
|
||||
Code = ProtocolStatusCode.ProtocolViolation,
|
||||
Message = "Worker command reply did not contain a public reply payload.",
|
||||
},
|
||||
};
|
||||
|
||||
if (reply.ProtocolStatus?.Code is not ProtocolStatusCode.Ok)
|
||||
{
|
||||
string message = reply.ProtocolStatus?.Message ?? reply.DiagnosticMessage;
|
||||
throw new SessionManagerException(
|
||||
SessionManagerErrorCode.SessionNotReady,
|
||||
string.IsNullOrWhiteSpace(message) ? "Bulk MXAccess command failed." : message);
|
||||
}
|
||||
|
||||
return payloadAccessor(reply)?.Results.ToArray() ?? [];
|
||||
}
|
||||
|
||||
private IWorkerClient GetReadyWorkerClient()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
|
||||
@@ -48,6 +48,50 @@ public sealed class SessionManagerTests
|
||||
Assert.Equal(MxCommandKind.Ping, reply.Reply.Kind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GatewaySessionSubscribeBulkAsync_ForwardsOneBulkCommandAndReturnsResults()
|
||||
{
|
||||
FakeWorkerClient workerClient = new()
|
||||
{
|
||||
InvokeReply = new WorkerCommandReply
|
||||
{
|
||||
Reply = new MxCommandReply
|
||||
{
|
||||
SessionId = "session-1",
|
||||
CorrelationId = "correlation-1",
|
||||
Kind = MxCommandKind.SubscribeBulk,
|
||||
ProtocolStatus = new ProtocolStatus { Code = ProtocolStatusCode.Ok },
|
||||
SubscribeBulk = new BulkSubscribeReply
|
||||
{
|
||||
Results =
|
||||
{
|
||||
new SubscribeResult
|
||||
{
|
||||
ServerHandle = 12,
|
||||
TagAddress = "Galaxy.Tag.Value",
|
||||
ItemHandle = 512,
|
||||
WasSuccessful = true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
SessionManager manager = CreateManager(new FakeSessionWorkerClientFactory(workerClient));
|
||||
GatewaySession session = await manager.OpenSessionAsync(CreateOpenRequest(), "client-1", CancellationToken.None);
|
||||
|
||||
IReadOnlyList<SubscribeResult> results = await session.SubscribeBulkAsync(
|
||||
12,
|
||||
["Galaxy.Tag.Value"],
|
||||
CancellationToken.None);
|
||||
|
||||
SubscribeResult result = Assert.Single(results);
|
||||
Assert.Equal(512, result.ItemHandle);
|
||||
Assert.Equal(1, workerClient.InvokeCount);
|
||||
Assert.Equal(MxCommandKind.SubscribeBulk, workerClient.LastCommand?.Command.Kind);
|
||||
Assert.Equal(["Galaxy.Tag.Value"], workerClient.LastCommand?.Command.SubscribeBulk.TagAddresses);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAsync_WhenSessionFaulted_RejectsCommand()
|
||||
{
|
||||
@@ -288,6 +332,10 @@ public sealed class SessionManagerTests
|
||||
|
||||
public Exception? ShutdownException { get; init; }
|
||||
|
||||
public WorkerCommand? LastCommand { get; private set; }
|
||||
|
||||
public WorkerCommandReply? InvokeReply { get; init; }
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
@@ -299,6 +347,12 @@ public sealed class SessionManagerTests
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
InvokeCount++;
|
||||
LastCommand = command;
|
||||
if (InvokeReply is not null)
|
||||
{
|
||||
return Task.FromResult(InvokeReply);
|
||||
}
|
||||
|
||||
MxCommandKind kind = command.Command?.Kind ?? MxCommandKind.Unspecified;
|
||||
|
||||
return Task.FromResult(new WorkerCommandReply
|
||||
|
||||
@@ -416,6 +416,74 @@ public sealed class MxAccessCommandExecutorTests
|
||||
Assert.Equal(MxAccessAdviceKind.Plain, adviceHandle.AdviceKind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DispatchAsync_SubscribeBulk_RunsSequentialMxAccessCallsAndReturnsPerItemResults()
|
||||
{
|
||||
FakeMxAccessComObject fakeComObject = new(
|
||||
registerHandle: 60,
|
||||
addItemHandle: 512);
|
||||
FakeMxAccessComObjectFactory factory = new(fakeComObject);
|
||||
using StaRuntime runtime = CreateRuntime();
|
||||
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
||||
await session.StartAsync(workerProcessId: 1234);
|
||||
|
||||
MxCommandReply reply = await session.DispatchAsync(CreateSubscribeBulkCommand(
|
||||
"subscribe-bulk",
|
||||
60,
|
||||
["", "Galaxy.Tag.Value"]));
|
||||
|
||||
Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
|
||||
Assert.Equal(MxCommandKind.SubscribeBulk, reply.Kind);
|
||||
Assert.Collection(
|
||||
reply.SubscribeBulk.Results,
|
||||
result =>
|
||||
{
|
||||
Assert.False(result.WasSuccessful);
|
||||
Assert.Equal(string.Empty, result.TagAddress);
|
||||
Assert.Equal(0, result.ItemHandle);
|
||||
Assert.Contains("required", result.ErrorMessage, StringComparison.OrdinalIgnoreCase);
|
||||
},
|
||||
result =>
|
||||
{
|
||||
Assert.True(result.WasSuccessful);
|
||||
Assert.Equal("Galaxy.Tag.Value", result.TagAddress);
|
||||
Assert.Equal(512, result.ItemHandle);
|
||||
});
|
||||
Assert.Equal(
|
||||
["AddItem:60:Galaxy.Tag.Value", "Advise:60:512"],
|
||||
fakeComObject.OperationNames);
|
||||
Assert.Equal(runtime.StaThreadId, fakeComObject.AddItemThreadId);
|
||||
Assert.Equal(runtime.StaThreadId, fakeComObject.AdviseThreadId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DispatchAsync_UnsubscribeBulk_RemovesItemAfterUnAdviseFailure()
|
||||
{
|
||||
const int hresult = unchecked((int)0x80070057);
|
||||
FakeMxAccessComObject fakeComObject = new(
|
||||
registerHandle: 61,
|
||||
unAdviseException: new COMException("Invalid item handle.", hresult));
|
||||
FakeMxAccessComObjectFactory factory = new(fakeComObject);
|
||||
using StaRuntime runtime = CreateRuntime();
|
||||
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
||||
await session.StartAsync(workerProcessId: 1234);
|
||||
|
||||
MxCommandReply reply = await session.DispatchAsync(CreateUnsubscribeBulkCommand(
|
||||
"unsubscribe-bulk",
|
||||
61,
|
||||
[513]));
|
||||
|
||||
Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
|
||||
SubscribeResult result = Assert.Single(reply.UnsubscribeBulk.Results);
|
||||
Assert.False(result.WasSuccessful);
|
||||
Assert.Equal(513, result.ItemHandle);
|
||||
Assert.Equal(string.Empty, result.TagAddress);
|
||||
Assert.Contains("UnAdvise failed", result.ErrorMessage);
|
||||
Assert.Equal(
|
||||
["UnAdvise:61:513", "RemoveItem:61:513"],
|
||||
fakeComObject.OperationNames);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ShutdownGracefullyAsync_CleansHandlesInAdviceItemServerOrder()
|
||||
{
|
||||
@@ -658,6 +726,48 @@ public sealed class MxAccessCommandExecutorTests
|
||||
});
|
||||
}
|
||||
|
||||
private static StaCommand CreateSubscribeBulkCommand(
|
||||
string correlationId,
|
||||
int serverHandle,
|
||||
IEnumerable<string> tagAddresses)
|
||||
{
|
||||
SubscribeBulkCommand command = new()
|
||||
{
|
||||
ServerHandle = serverHandle,
|
||||
};
|
||||
command.TagAddresses.Add(tagAddresses);
|
||||
|
||||
return new StaCommand(
|
||||
"session-1",
|
||||
correlationId,
|
||||
new MxCommand
|
||||
{
|
||||
Kind = MxCommandKind.SubscribeBulk,
|
||||
SubscribeBulk = command,
|
||||
});
|
||||
}
|
||||
|
||||
private static StaCommand CreateUnsubscribeBulkCommand(
|
||||
string correlationId,
|
||||
int serverHandle,
|
||||
IEnumerable<int> itemHandles)
|
||||
{
|
||||
UnsubscribeBulkCommand command = new()
|
||||
{
|
||||
ServerHandle = serverHandle,
|
||||
};
|
||||
command.ItemHandles.Add(itemHandles);
|
||||
|
||||
return new StaCommand(
|
||||
"session-1",
|
||||
correlationId,
|
||||
new MxCommand
|
||||
{
|
||||
Kind = MxCommandKind.UnsubscribeBulk,
|
||||
UnsubscribeBulk = command,
|
||||
});
|
||||
}
|
||||
|
||||
private static StaCommand CreateAdviseSupervisoryCommand(
|
||||
string correlationId,
|
||||
int serverHandle,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MxGateway.Contracts.Proto;
|
||||
using MxGateway.Worker.Conversion;
|
||||
using MxGateway.Worker.Sta;
|
||||
@@ -40,6 +41,12 @@ public sealed class MxAccessCommandExecutor : IStaCommandExecutor
|
||||
MxCommandKind.Advise => ExecuteAdvise(command),
|
||||
MxCommandKind.UnAdvise => ExecuteUnAdvise(command),
|
||||
MxCommandKind.AdviseSupervisory => ExecuteAdviseSupervisory(command),
|
||||
MxCommandKind.AddItemBulk => ExecuteAddItemBulk(command),
|
||||
MxCommandKind.AdviseItemBulk => ExecuteAdviseItemBulk(command),
|
||||
MxCommandKind.RemoveItemBulk => ExecuteRemoveItemBulk(command),
|
||||
MxCommandKind.UnAdviseItemBulk => ExecuteUnAdviseItemBulk(command),
|
||||
MxCommandKind.SubscribeBulk => ExecuteSubscribeBulk(command),
|
||||
MxCommandKind.UnsubscribeBulk => ExecuteUnsubscribeBulk(command),
|
||||
_ => CreateInvalidRequestReply(command, $"Unsupported MXAccess command kind {command.Kind}."),
|
||||
};
|
||||
}
|
||||
@@ -178,6 +185,84 @@ public sealed class MxAccessCommandExecutor : IStaCommandExecutor
|
||||
return CreateOkReply(command);
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteAddItemBulk(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.AddItemBulk)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "AddItemBulk command payload is required.");
|
||||
}
|
||||
|
||||
AddItemBulkCommand addItemBulkCommand = command.Command.AddItemBulk;
|
||||
return CreateBulkReply(
|
||||
command,
|
||||
session.AddItemBulk(addItemBulkCommand.ServerHandle, addItemBulkCommand.TagAddresses));
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteAdviseItemBulk(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.AdviseItemBulk)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "AdviseItemBulk command payload is required.");
|
||||
}
|
||||
|
||||
AdviseItemBulkCommand adviseItemBulkCommand = command.Command.AdviseItemBulk;
|
||||
return CreateBulkReply(
|
||||
command,
|
||||
session.AdviseItemBulk(adviseItemBulkCommand.ServerHandle, adviseItemBulkCommand.ItemHandles));
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteRemoveItemBulk(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.RemoveItemBulk)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "RemoveItemBulk command payload is required.");
|
||||
}
|
||||
|
||||
RemoveItemBulkCommand removeItemBulkCommand = command.Command.RemoveItemBulk;
|
||||
return CreateBulkReply(
|
||||
command,
|
||||
session.RemoveItemBulk(removeItemBulkCommand.ServerHandle, removeItemBulkCommand.ItemHandles));
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteUnAdviseItemBulk(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.UnAdviseItemBulk)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "UnAdviseItemBulk command payload is required.");
|
||||
}
|
||||
|
||||
UnAdviseItemBulkCommand unAdviseItemBulkCommand = command.Command.UnAdviseItemBulk;
|
||||
return CreateBulkReply(
|
||||
command,
|
||||
session.UnAdviseItemBulk(unAdviseItemBulkCommand.ServerHandle, unAdviseItemBulkCommand.ItemHandles));
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteSubscribeBulk(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.SubscribeBulk)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "SubscribeBulk command payload is required.");
|
||||
}
|
||||
|
||||
SubscribeBulkCommand subscribeBulkCommand = command.Command.SubscribeBulk;
|
||||
return CreateBulkReply(
|
||||
command,
|
||||
session.SubscribeBulk(subscribeBulkCommand.ServerHandle, subscribeBulkCommand.TagAddresses));
|
||||
}
|
||||
|
||||
private MxCommandReply ExecuteUnsubscribeBulk(StaCommand command)
|
||||
{
|
||||
if (command.Command.PayloadCase != MxCommand.PayloadOneofCase.UnsubscribeBulk)
|
||||
{
|
||||
return CreateInvalidRequestReply(command, "UnsubscribeBulk command payload is required.");
|
||||
}
|
||||
|
||||
UnsubscribeBulkCommand unsubscribeBulkCommand = command.Command.UnsubscribeBulk;
|
||||
return CreateBulkReply(
|
||||
command,
|
||||
session.UnsubscribeBulk(unsubscribeBulkCommand.ServerHandle, unsubscribeBulkCommand.ItemHandles));
|
||||
}
|
||||
|
||||
private static MxCommandReply CreateOkReply(StaCommand command)
|
||||
{
|
||||
return new MxCommandReply
|
||||
@@ -194,6 +279,41 @@ public sealed class MxAccessCommandExecutor : IStaCommandExecutor
|
||||
};
|
||||
}
|
||||
|
||||
private static MxCommandReply CreateBulkReply(
|
||||
StaCommand command,
|
||||
IEnumerable<SubscribeResult> results)
|
||||
{
|
||||
MxCommandReply reply = CreateOkReply(command);
|
||||
BulkSubscribeReply bulkReply = new();
|
||||
bulkReply.Results.Add(results);
|
||||
|
||||
switch (command.Kind)
|
||||
{
|
||||
case MxCommandKind.AddItemBulk:
|
||||
reply.AddItemBulk = bulkReply;
|
||||
break;
|
||||
case MxCommandKind.AdviseItemBulk:
|
||||
reply.AdviseItemBulk = bulkReply;
|
||||
break;
|
||||
case MxCommandKind.RemoveItemBulk:
|
||||
reply.RemoveItemBulk = bulkReply;
|
||||
break;
|
||||
case MxCommandKind.UnAdviseItemBulk:
|
||||
reply.UnAdviseItemBulk = bulkReply;
|
||||
break;
|
||||
case MxCommandKind.SubscribeBulk:
|
||||
reply.SubscribeBulk = bulkReply;
|
||||
break;
|
||||
case MxCommandKind.UnsubscribeBulk:
|
||||
reply.UnsubscribeBulk = bulkReply;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Unsupported bulk command kind {command.Kind}.");
|
||||
}
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
private static MxCommandReply CreateInvalidRequestReply(
|
||||
StaCommand command,
|
||||
string message)
|
||||
|
||||
@@ -189,6 +189,202 @@ public sealed class MxAccessSession : IDisposable
|
||||
MxAccessAdviceKind.Supervisory);
|
||||
}
|
||||
|
||||
public IReadOnlyList<SubscribeResult> AddItemBulk(
|
||||
int serverHandle,
|
||||
IEnumerable<string> tagAddresses)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (tagAddresses is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagAddresses));
|
||||
}
|
||||
|
||||
List<SubscribeResult> results = new();
|
||||
foreach (string? tagAddress in tagAddresses)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(tagAddress))
|
||||
{
|
||||
results.Add(Failed(serverHandle, tagAddress ?? string.Empty, itemHandle: 0, "Tag address is required."));
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
int itemHandle = AddItem(serverHandle, tagAddress);
|
||||
results.Add(Succeeded(serverHandle, tagAddress, itemHandle));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
results.Add(Failed(serverHandle, tagAddress, itemHandle: 0, exception.Message));
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public IReadOnlyList<SubscribeResult> AdviseItemBulk(
|
||||
int serverHandle,
|
||||
IEnumerable<int> itemHandles)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (itemHandles is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(itemHandles));
|
||||
}
|
||||
|
||||
List<SubscribeResult> results = new();
|
||||
foreach (int itemHandle in itemHandles)
|
||||
{
|
||||
try
|
||||
{
|
||||
Advise(serverHandle, itemHandle);
|
||||
results.Add(Succeeded(serverHandle, string.Empty, itemHandle));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
results.Add(Failed(serverHandle, string.Empty, itemHandle, exception.Message));
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public IReadOnlyList<SubscribeResult> RemoveItemBulk(
|
||||
int serverHandle,
|
||||
IEnumerable<int> itemHandles)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (itemHandles is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(itemHandles));
|
||||
}
|
||||
|
||||
List<SubscribeResult> results = new();
|
||||
foreach (int itemHandle in itemHandles)
|
||||
{
|
||||
try
|
||||
{
|
||||
RemoveItem(serverHandle, itemHandle);
|
||||
results.Add(Succeeded(serverHandle, string.Empty, itemHandle));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
results.Add(Failed(serverHandle, string.Empty, itemHandle, exception.Message));
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public IReadOnlyList<SubscribeResult> UnAdviseItemBulk(
|
||||
int serverHandle,
|
||||
IEnumerable<int> itemHandles)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (itemHandles is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(itemHandles));
|
||||
}
|
||||
|
||||
List<SubscribeResult> results = new();
|
||||
foreach (int itemHandle in itemHandles)
|
||||
{
|
||||
try
|
||||
{
|
||||
UnAdvise(serverHandle, itemHandle);
|
||||
results.Add(Succeeded(serverHandle, string.Empty, itemHandle));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
results.Add(Failed(serverHandle, string.Empty, itemHandle, exception.Message));
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public IReadOnlyList<SubscribeResult> SubscribeBulk(
|
||||
int serverHandle,
|
||||
IEnumerable<string> tagAddresses)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (tagAddresses is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagAddresses));
|
||||
}
|
||||
|
||||
List<SubscribeResult> results = new();
|
||||
foreach (string? tagAddress in tagAddresses)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(tagAddress))
|
||||
{
|
||||
results.Add(Failed(serverHandle, tagAddress ?? string.Empty, itemHandle: 0, "Tag address is required."));
|
||||
continue;
|
||||
}
|
||||
|
||||
int itemHandle = 0;
|
||||
try
|
||||
{
|
||||
itemHandle = AddItem(serverHandle, tagAddress);
|
||||
Advise(serverHandle, itemHandle);
|
||||
results.Add(Succeeded(serverHandle, tagAddress, itemHandle));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
string errorMessage = exception.Message;
|
||||
if (itemHandle != 0)
|
||||
{
|
||||
errorMessage = AppendRemoveItemCleanup(serverHandle, itemHandle, errorMessage);
|
||||
}
|
||||
|
||||
results.Add(Failed(serverHandle, tagAddress, itemHandle, errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public IReadOnlyList<SubscribeResult> UnsubscribeBulk(
|
||||
int serverHandle,
|
||||
IEnumerable<int> itemHandles)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (itemHandles is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(itemHandles));
|
||||
}
|
||||
|
||||
List<SubscribeResult> results = new();
|
||||
foreach (int itemHandle in itemHandles)
|
||||
{
|
||||
List<string> errors = new();
|
||||
|
||||
try
|
||||
{
|
||||
UnAdvise(serverHandle, itemHandle);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
errors.Add($"UnAdvise failed: {exception.Message}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
RemoveItem(serverHandle, itemHandle);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
errors.Add($"RemoveItem failed: {exception.Message}");
|
||||
}
|
||||
|
||||
results.Add(errors.Count == 0
|
||||
? Succeeded(serverHandle, string.Empty, itemHandle)
|
||||
: Failed(serverHandle, string.Empty, itemHandle, string.Join("; ", errors)));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public MxAccessShutdownResult ShutdownGracefully()
|
||||
{
|
||||
if (disposed)
|
||||
@@ -290,6 +486,53 @@ public sealed class MxAccessSession : IDisposable
|
||||
return ((long)serverHandle << 32) | (uint)itemHandle;
|
||||
}
|
||||
|
||||
private string AppendRemoveItemCleanup(
|
||||
int serverHandle,
|
||||
int itemHandle,
|
||||
string errorMessage)
|
||||
{
|
||||
try
|
||||
{
|
||||
RemoveItem(serverHandle, itemHandle);
|
||||
return $"{errorMessage}; cleanup RemoveItem succeeded.";
|
||||
}
|
||||
catch (Exception cleanupException)
|
||||
{
|
||||
return $"{errorMessage}; cleanup RemoveItem failed: {cleanupException.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
private static SubscribeResult Succeeded(
|
||||
int serverHandle,
|
||||
string tagAddress,
|
||||
int itemHandle)
|
||||
{
|
||||
return new SubscribeResult
|
||||
{
|
||||
ServerHandle = serverHandle,
|
||||
TagAddress = tagAddress,
|
||||
ItemHandle = itemHandle,
|
||||
WasSuccessful = true,
|
||||
ErrorMessage = string.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
private static SubscribeResult Failed(
|
||||
int serverHandle,
|
||||
string tagAddress,
|
||||
int itemHandle,
|
||||
string errorMessage)
|
||||
{
|
||||
return new SubscribeResult
|
||||
{
|
||||
ServerHandle = serverHandle,
|
||||
TagAddress = tagAddress,
|
||||
ItemHandle = itemHandle,
|
||||
WasSuccessful = false,
|
||||
ErrorMessage = errorMessage,
|
||||
};
|
||||
}
|
||||
|
||||
private void DisposeCore(ICollection<MxAccessShutdownFailure>? failures)
|
||||
{
|
||||
try
|
||||
|
||||
Reference in New Issue
Block a user