Skip to content

Commit aa59693

Browse files
pacmancoderawakecoding
authored andcommitted
feat(client): Implement RDM messages support in high level client
1 parent 31d2a7c commit aa59693

File tree

12 files changed

+679
-6
lines changed

12 files changed

+679
-6
lines changed

dotnet/Devolutions.NowClient.Cli/Program.cs

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ static async Task<int> Main(params string[] args)
5151
{
5252
repeat = true;
5353

54-
Console.Write("Operation (msg/run/pwsh/logoff/lock/exit): ");
54+
Console.Write("Operation (msg/run/pwsh/logoff/lock/rdm-run/rdm-version/exit): ");
5555
var operation = Console.ReadLine()?.Trim().ToLowerInvariant() ?? string.Empty;
5656

5757
switch (operation)
@@ -104,6 +104,12 @@ static async Task<int> Main(params string[] args)
104104
await client.SessionLock();
105105
Console.WriteLine("OK");
106106
break;
107+
case "rdm-run":
108+
await ExecuteRdmRunCommand(client);
109+
break;
110+
case "rdm-version":
111+
await ExecuteRdmVersionCommand(client);
112+
break;
107113
case "exit":
108114
Console.WriteLine("Exiting...");
109115
repeat = false;
@@ -244,4 +250,71 @@ private static async Task ExecutePowerShellCommand(NowClient client, string comm
244250
throw;
245251
}
246252
}
253+
254+
/// <summary>
255+
/// Executes the RDM run command - starts an RDM application.
256+
/// </summary>
257+
private static async Task ExecuteRdmRunCommand(NowClient client)
258+
{
259+
try
260+
{
261+
// Check if RDM is available
262+
Console.WriteLine("Checking RDM availability...");
263+
var isAvailable = await client.IsRdmAppAvailable();
264+
if (!isAvailable)
265+
{
266+
Console.WriteLine("RDM is not available on this system.");
267+
return;
268+
}
269+
270+
// Start RDM application
271+
Console.WriteLine("Starting RDM application...");
272+
var startParams = new RdmStartParams();
273+
274+
await client.RdmStart(startParams);
275+
Console.WriteLine("RDM application started successfully.");
276+
}
277+
catch (Exception ex)
278+
{
279+
Console.Error.WriteLine($"Error executing RDM run command: {ex.Message}");
280+
}
281+
}
282+
283+
/// <summary>
284+
/// Executes the RDM version command - displays RDM version information.
285+
/// </summary>
286+
private static async Task ExecuteRdmVersionCommand(NowClient client)
287+
{
288+
try
289+
{
290+
// Check if RDM is available
291+
var isAvailable = await client.IsRdmAppAvailable();
292+
if (!isAvailable)
293+
{
294+
Console.WriteLine("RDM is not available on this system.");
295+
return;
296+
}
297+
298+
Console.WriteLine("RDM is available.");
299+
300+
// Get and display version information
301+
var rdmVersion = await client.GetRdmVersion();
302+
if (rdmVersion != null)
303+
{
304+
Console.WriteLine($"RDM Version: {rdmVersion.Version}");
305+
if (!string.IsNullOrEmpty(rdmVersion.Extra))
306+
{
307+
Console.WriteLine($"Extra information: {rdmVersion.Extra}");
308+
}
309+
}
310+
else
311+
{
312+
Console.WriteLine("Could not retrieve RDM version information.");
313+
}
314+
}
315+
catch (Exception ex)
316+
{
317+
Console.Error.WriteLine($"Error executing RDM version command: {ex.Message}");
318+
}
319+
}
247320
}

dotnet/Devolutions.NowClient/src/NowClient.cs

Lines changed: 163 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Threading.Channels;
33

44
using Devolutions.NowClient.Worker;
5+
using Devolutions.NowProto;
56
using Devolutions.NowProto.Capabilities;
67
using Devolutions.NowProto.Messages;
78

@@ -64,7 +65,9 @@ public static async Task<NowClient> Connect(INowTransport transportImpl)
6465
}
6566
});
6667

67-
return new NowClient(capabilities, workerTask, clientChannel.Writer);
68+
var client = new NowClient(capabilities, workerTask, clientChannel.Writer);
69+
70+
return client;
6871
}
6972

7073
/// <summary>
@@ -336,6 +339,157 @@ public async Task ForceTermiate()
336339
await _commandWriter.WriteAsync(new CommandChannelClose());
337340
}
338341

342+
// RDM Methods
343+
344+
/// <summary>
345+
/// Sets the callback for RDM application notifications.
346+
/// </summary>
347+
public async Task SetRdmAppNotifyHandler(RdmAppNotifyHandler? handler)
348+
{
349+
ThrowIfWorkerTerminated();
350+
351+
await _commandWriter.WriteAsync(new CommandSetRdmAppNotifyHandler(handler));
352+
}
353+
354+
private bool _rdmCapabilitiesSent = false;
355+
private RdmCapabilityInfo? _rdmCapabilities = null;
356+
357+
/// <summary>
358+
/// Performs RDM capabilities exchange with the server.
359+
/// This method is automatically called before any RDM app or session operations if not already called.
360+
/// </summary>
361+
public async Task RdmSync()
362+
{
363+
ThrowIfWorkerTerminated();
364+
365+
var clientTimestamp = DateTimeOffset.UtcNow;
366+
var message = new NowMsgRdmCapabilities.Builder(
367+
(ulong)clientTimestamp.ToUnixTimeSeconds(),
368+
""
369+
).Build();
370+
371+
var responseHandler = new TaskCompletionSource<NowMsgRdmCapabilities>();
372+
var command = new CommandRdmCapabilities(message, responseHandler);
373+
374+
await _commandWriter.WriteAsync(command);
375+
376+
var response = await responseHandler.Task.WaitAsync(TimeSpan.FromSeconds(10));
377+
378+
_rdmCapabilities = new RdmCapabilityInfo
379+
{
380+
IsAppAvailable = response.IsAppAvailable,
381+
RdmVersion = response.RdmVersion,
382+
VersionExtra = response.VersionExtra,
383+
ServerTimestamp = DateTimeOffset.FromUnixTimeSeconds((long)response.Timestamp),
384+
};
385+
386+
_rdmCapabilitiesSent = true;
387+
}
388+
389+
/// <summary>
390+
/// Checks if RDM application is available on the server.
391+
/// Automatically performs capabilities exchange if not already done.
392+
/// </summary>
393+
public async Task<bool> IsRdmAppAvailable()
394+
{
395+
await EnsureRdmCapabilitiesSent();
396+
397+
return _rdmCapabilities?.IsAppAvailable ?? false;
398+
}
399+
400+
/// <summary>
401+
/// Gets the RDM version information from the server.
402+
/// Automatically performs capabilities exchange if not already done.
403+
/// </summary>
404+
public async Task<RdmVersion?> GetRdmVersion()
405+
{
406+
await EnsureRdmCapabilitiesSent();
407+
408+
if (_rdmCapabilities == null || !_rdmCapabilities.IsAppAvailable)
409+
return null;
410+
411+
return new RdmVersion(_rdmCapabilities.RdmVersion,
412+
string.IsNullOrEmpty(_rdmCapabilities.VersionExtra) ? null : _rdmCapabilities.VersionExtra);
413+
}
414+
415+
/// <summary>
416+
/// Starts the RDM application on the server.
417+
/// Automatically performs capabilities exchange if not already done.
418+
/// </summary>
419+
public async Task RdmStart(RdmStartParams? startParams = null)
420+
{
421+
ThrowIfWorkerTerminated();
422+
await EnsureRdmCapabilitiesSent();
423+
424+
var parameters = startParams ?? new RdmStartParams();
425+
var builder = new NowMsgRdmAppStart.Builder()
426+
.Timeout((uint)parameters.Timeout.TotalSeconds);
427+
428+
if (parameters.LaunchFlags.HasFlag(NowRdmLaunchFlags.JumpMode))
429+
builder = builder.WithJumpMode();
430+
if (parameters.LaunchFlags.HasFlag(NowRdmLaunchFlags.Maximized))
431+
builder = builder.WithMaximized();
432+
if (parameters.LaunchFlags.HasFlag(NowRdmLaunchFlags.Fullscreen))
433+
builder = builder.WithFullscreen();
434+
435+
var message = builder.Build();
436+
var command = new CommandRdmAppStart(message);
437+
438+
await _commandWriter.WriteAsync(command);
439+
}
440+
441+
/// <summary>
442+
/// Sends an action command to the RDM application.
443+
/// Automatically performs capabilities exchange if not already done.
444+
/// </summary>
445+
public async Task RdmAction(NowRdmAppAction action, string actionData = "")
446+
{
447+
ThrowIfWorkerTerminated();
448+
await EnsureRdmCapabilitiesSent();
449+
450+
var message = new NowMsgRdmAppAction(action, actionData);
451+
var command = new CommandRdmAppAction(message);
452+
453+
await _commandWriter.WriteAsync(command);
454+
}
455+
456+
/// <summary>
457+
/// Starts a new RDM session.
458+
/// Automatically performs capabilities exchange if not already done.
459+
/// </summary>
460+
public async Task<RdmSession> RdmSessionStart(RdmSessionStartParams sessionParams)
461+
{
462+
ThrowIfWorkerTerminated();
463+
await EnsureRdmCapabilitiesSent();
464+
465+
var message = new NowMsgRdmSessionStart(sessionParams.SessionId, sessionParams.ConnectionId, sessionParams.ConnectionData);
466+
var session = new RdmSession(sessionParams.SessionId, _commandWriter, sessionParams.NotifyHandler);
467+
var command = new CommandRdmSessionStart(message, session);
468+
469+
await _commandWriter.WriteAsync(command);
470+
471+
return session;
472+
}
473+
474+
/// <summary>
475+
/// Ensures that RDM capabilities have been exchanged with the server.
476+
/// </summary>
477+
private async Task EnsureRdmCapabilitiesSent()
478+
{
479+
if (!_rdmCapabilitiesSent)
480+
{
481+
// Check if server version supports RDM functionality
482+
if (Capabilities.Version < MIN_RDM_ENABLED_VERSION)
483+
{
484+
throw new NowClientException(
485+
$"RDM functionality requires NOW-proto server version {MIN_RDM_ENABLED_VERSION.Major}.{MIN_RDM_ENABLED_VERSION.Minor} or higher. " +
486+
$"Current server version is {Capabilities.Version.Major}.{Capabilities.Version.Minor}.");
487+
}
488+
489+
await RdmSync();
490+
}
491+
}
492+
339493
private static void ThrowCapabilitiesError(string capability)
340494
{
341495
throw new NowClientException($"{capability} is not supported by server.");
@@ -351,18 +505,24 @@ private void ThrowIfWorkerTerminated()
351505

352506
private NowClient(
353507
NowMsgChannelCapset capabilities,
354-
Task runnerTask,
508+
Task workerTask,
355509
ChannelWriter<IClientCommand> commandWriter
356510
)
357511
{
358512
this.Capabilities = capabilities;
359-
this._runnerTask = runnerTask;
513+
this._runnerTask = workerTask;
360514
this._commandWriter = commandWriter;
361515
}
362516

363517
private const int TimeoutConnectSeconds = 10;
364518
private const int IoChannelCapacity = 1024;
365519

520+
/// <summary>
521+
/// Minimum NOW-proto server version required for RDM functionality.
522+
/// RDM features were introduced in version 1.3, so servers with version 1.2 and below are not supported.
523+
/// </summary>
524+
private static readonly NowProtoVersion MIN_RDM_ENABLED_VERSION = new(1, 3);
525+
366526
/// <summary>
367527
/// Check if the NOW-proto channel has been terminated.
368528
/// </summary>

0 commit comments

Comments
 (0)