mirror of
https://github.com/immense/Remotely.git
synced 2025-10-26 11:27:15 +00:00
Made circuit events strongly-typed via IMessenger.
This commit is contained in:
parent
33d4384bec
commit
c9aa682fda
@ -111,6 +111,10 @@ dotnet_style_qualification_for_event = false:silent
|
||||
dotnet_style_qualification_for_method = false:silent
|
||||
dotnet_style_qualification_for_property = false:silent
|
||||
dotnet_style_qualification_for_field = false:silent
|
||||
|
||||
# IDE0130: Namespace does not match folder structure
|
||||
dotnet_diagnostic.IDE0130.severity = error
|
||||
|
||||
[*.cs]
|
||||
csharp_indent_labels = one_less_than_current
|
||||
csharp_using_directive_placement = outside_namespace:silent
|
||||
@ -167,3 +171,6 @@ dotnet_diagnostic.CS8600.severity = error
|
||||
|
||||
# CS8602: Dereference of a possibly null reference.
|
||||
dotnet_diagnostic.CS8602.severity = error
|
||||
|
||||
# CS8631: The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match constraint type.
|
||||
dotnet_diagnostic.CS8631.severity = error
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Immense.SimpleMessenger;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Remotely.Server.Hubs;
|
||||
using Remotely.Server.Models.Messages;
|
||||
using Remotely.Server.Services;
|
||||
using Remotely.Shared.Enums;
|
||||
using Remotely.Shared.ViewModels;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Remotely.Server.Components.Devices;
|
||||
@ -30,10 +30,13 @@ public partial class ChatCard : AuthComponentBase, IDisposable
|
||||
[Inject]
|
||||
private IJsInterop JsInterop { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IMessenger Messenger { get; init; } = null!;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
AppState.PropertyChanged -= AppState_PropertyChanged;
|
||||
CircuitConnection.MessageReceived -= CircuitConnection_MessageReceived;
|
||||
Messenger.Unregister<ChatReceivedMessage, string>(this, CircuitConnection.ConnectionId);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
@ -46,7 +49,51 @@ public partial class ChatCard : AuthComponentBase, IDisposable
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
AppState.PropertyChanged += AppState_PropertyChanged;
|
||||
CircuitConnection.MessageReceived += CircuitConnection_MessageReceived;
|
||||
await Messenger.Register<ChatReceivedMessage, string>(
|
||||
this,
|
||||
CircuitConnection.ConnectionId,
|
||||
HandleChatMessageReceived);
|
||||
}
|
||||
|
||||
private async Task HandleChatMessageReceived(ChatReceivedMessage message)
|
||||
{
|
||||
if (message.DeviceId != Session.DeviceId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var session = AppState.DevicesFrameChatSessions.Find(x => x.DeviceId == message.DeviceId);
|
||||
|
||||
if (session is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.DidDisconnect)
|
||||
{
|
||||
session.ChatHistory.Add(new ChatHistoryItem()
|
||||
{
|
||||
Message = $"{Session.DeviceName} disconnected.",
|
||||
Origin = ChatHistoryItemOrigin.System
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
session.ChatHistory.Add(new ChatHistoryItem()
|
||||
{
|
||||
Message = message.MessageText,
|
||||
Origin = ChatHistoryItemOrigin.Device
|
||||
});
|
||||
}
|
||||
|
||||
if (!session.IsExpanded)
|
||||
{
|
||||
session.MissedChats++;
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
JsInterop.ScrollToEnd(_chatMessagesWindow);
|
||||
}
|
||||
|
||||
private async void AppState_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
@ -57,48 +104,6 @@ public partial class ChatCard : AuthComponentBase, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private async void CircuitConnection_MessageReceived(object? sender, Models.CircuitEvent e)
|
||||
{
|
||||
if (e.EventName == Models.CircuitEventName.ChatReceived)
|
||||
{
|
||||
var deviceId = (string)e.Params[0];
|
||||
|
||||
if (deviceId == Session.DeviceId)
|
||||
{
|
||||
var deviceName = (string)e.Params[1];
|
||||
var message = (string)e.Params[2];
|
||||
var disconnected = (bool)e.Params[3];
|
||||
|
||||
var session = AppState.DevicesFrameChatSessions.Find(x => x.DeviceId == deviceId);
|
||||
|
||||
if (disconnected)
|
||||
{
|
||||
session.ChatHistory.Add(new ChatHistoryItem()
|
||||
{
|
||||
Message = $"{Session.DeviceName} disconnected.",
|
||||
Origin = ChatHistoryItemOrigin.System
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
session.ChatHistory.Add(new ChatHistoryItem()
|
||||
{
|
||||
Message = message,
|
||||
Origin = ChatHistoryItemOrigin.Device
|
||||
});
|
||||
}
|
||||
|
||||
if (!session.IsExpanded)
|
||||
{
|
||||
session.MissedChats++;
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
JsInterop.ScrollToEnd(_chatMessagesWindow);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void CloseChatCard()
|
||||
{
|
||||
AppState.DevicesFrameChatSessions.RemoveAll(x => x.DeviceId == Session.DeviceId);
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Immense.SimpleMessenger;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Remotely.Server.Hubs;
|
||||
using Remotely.Server.Models.Messages;
|
||||
using Remotely.Server.Services;
|
||||
using Remotely.Shared.Entities;
|
||||
using Remotely.Shared.Enums;
|
||||
using Remotely.Shared.ViewModels;
|
||||
using System;
|
||||
@ -20,10 +23,13 @@ public partial class ChatFrame : AuthComponentBase, IDisposable
|
||||
[Inject]
|
||||
private ICircuitConnection CircuitConnection { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IMessenger Messenger { get; init; } = null!;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
AppState.PropertyChanged -= AppState_PropertyChanged;
|
||||
CircuitConnection.MessageReceived -= CircuitConnection_MessageReceived;
|
||||
Messenger.Unregister<ChatReceivedMessage, string>(this, CircuitConnection.ConnectionId);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
@ -31,44 +37,36 @@ public partial class ChatFrame : AuthComponentBase, IDisposable
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
AppState.PropertyChanged += AppState_PropertyChanged;
|
||||
CircuitConnection.MessageReceived += CircuitConnection_MessageReceived;
|
||||
await Messenger.Register<ChatReceivedMessage, string>(
|
||||
this,
|
||||
CircuitConnection.ConnectionId,
|
||||
HandleChatMessageReceived);
|
||||
}
|
||||
|
||||
private void CircuitConnection_MessageReceived(object? sender, Models.CircuitEvent e)
|
||||
private async Task HandleChatMessageReceived(ChatReceivedMessage message)
|
||||
{
|
||||
if (e.EventName == Models.CircuitEventName.ChatReceived)
|
||||
if (AppState.DevicesFrameChatSessions.Exists(x => x.DeviceId == message.DeviceId) ||
|
||||
message.DidDisconnect)
|
||||
{
|
||||
var deviceId = (string)e.Params[0];
|
||||
|
||||
if (!AppState.DevicesFrameChatSessions.Exists(x => x.DeviceId == deviceId))
|
||||
{
|
||||
var deviceName = (string)e.Params[1];
|
||||
var message = (string)e.Params[2];
|
||||
var disconnected = (bool)e.Params[3];
|
||||
|
||||
if (disconnected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var newChat = new ChatSession()
|
||||
{
|
||||
DeviceId = deviceId,
|
||||
DeviceName = deviceName,
|
||||
IsExpanded = true
|
||||
};
|
||||
|
||||
newChat.ChatHistory.Add(new ChatHistoryItem()
|
||||
{
|
||||
Message = message,
|
||||
Origin = ChatHistoryItemOrigin.Device
|
||||
});
|
||||
|
||||
AppState.DevicesFrameChatSessions.Add(newChat);
|
||||
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var newChat = new ChatSession()
|
||||
{
|
||||
DeviceId = message.DeviceId,
|
||||
DeviceName = message.DeviceName,
|
||||
IsExpanded = true
|
||||
};
|
||||
|
||||
newChat.ChatHistory.Add(new ChatHistoryItem()
|
||||
{
|
||||
Message = message.MessageText,
|
||||
Origin = ChatHistoryItemOrigin.Device
|
||||
});
|
||||
|
||||
AppState.DevicesFrameChatSessions.Add(newChat);
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private void AppState_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
|
||||
@ -1,13 +1,10 @@
|
||||
using Immense.RemoteControl.Server.Abstractions;
|
||||
using Immense.SimpleMessenger;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Remotely.Server.Auth;
|
||||
using Remotely.Server.Enums;
|
||||
using Remotely.Server.Hubs;
|
||||
using Remotely.Server.Models;
|
||||
using Remotely.Server.Models.Messages;
|
||||
using Remotely.Server.Services;
|
||||
using Remotely.Shared.Entities;
|
||||
using Remotely.Shared.Enums;
|
||||
@ -15,7 +12,6 @@ using Remotely.Shared.Utilities;
|
||||
using Remotely.Shared.ViewModels;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
@ -69,10 +65,13 @@ public partial class DeviceCard : AuthComponentBase, IDisposable
|
||||
[Inject]
|
||||
private IUpgradeService UpgradeService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IMessenger Messenger { get; init; } = null!;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
AppState.PropertyChanged -= AppState_PropertyChanged;
|
||||
CircuitConnection.MessageReceived -= CircuitConnection_MessageReceived;
|
||||
Messenger.Unregister<DeviceStateChangedMessage, string>(this, CircuitConnection.ConnectionId);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
@ -84,7 +83,48 @@ public partial class DeviceCard : AuthComponentBase, IDisposable
|
||||
_currentVersion = UpgradeService.GetCurrentVersion();
|
||||
_deviceGroups = DataService.GetDeviceGroups(UserName);
|
||||
AppState.PropertyChanged += AppState_PropertyChanged;
|
||||
CircuitConnection.MessageReceived += CircuitConnection_MessageReceived;
|
||||
await Messenger.Register<DeviceStateChangedMessage, string>(
|
||||
this,
|
||||
CircuitConnection.ConnectionId,
|
||||
HandleDeviceStateChanged);
|
||||
}
|
||||
|
||||
private async Task HandleDeviceStateChanged(DeviceStateChangedMessage message)
|
||||
{
|
||||
if (message.Device.ID != Device.ID)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: It would be cool to decorate user-editable properties
|
||||
// with a "UserEditable" attribute, then use a source generator
|
||||
// to create/update a method that copies property values for
|
||||
// those that do not have the attribute. We could do the same
|
||||
// with reflection, but this method is called too frequently,
|
||||
// and the performance hit would likely be significant.
|
||||
|
||||
// If the card is expanded, only update the immutable UI
|
||||
// elements, so any changes to the form fields aren't lost.
|
||||
if (IsExpanded)
|
||||
{
|
||||
Device.CurrentUser = message.Device.CurrentUser;
|
||||
Device.Platform = message.Device.Platform;
|
||||
Device.TotalStorage = message.Device.TotalStorage;
|
||||
Device.UsedStorage = message.Device.UsedStorage;
|
||||
Device.Drives = message.Device.Drives;
|
||||
Device.CpuUtilization = message.Device.CpuUtilization;
|
||||
Device.TotalMemory = message.Device.TotalMemory;
|
||||
Device.UsedMemory = message.Device.UsedMemory;
|
||||
Device.AgentVersion = message.Device.AgentVersion;
|
||||
Device.LastOnline = message.Device.LastOnline;
|
||||
Device.PublicIP = message.Device.PublicIP;
|
||||
Device.MacAddresses = message.Device.MacAddresses;
|
||||
}
|
||||
else
|
||||
{
|
||||
Device = message.Device;
|
||||
}
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private void AppState_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
@ -97,25 +137,6 @@ public partial class DeviceCard : AuthComponentBase, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private async void CircuitConnection_MessageReceived(object? sender, CircuitEvent e)
|
||||
{
|
||||
switch (e.EventName)
|
||||
{
|
||||
case CircuitEventName.DeviceUpdate:
|
||||
case CircuitEventName.DeviceWentOffline:
|
||||
{
|
||||
if (e.Params?.FirstOrDefault() is Device device &&
|
||||
device.ID == Device?.ID)
|
||||
{
|
||||
Device = device;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
private void ContextMenuOpening(MouseEventArgs args)
|
||||
{
|
||||
if (GetCardState() == DeviceCardState.Normal)
|
||||
@ -331,7 +352,7 @@ public partial class DeviceCard : AuthComponentBase, IDisposable
|
||||
await CircuitConnection.UninstallAgents(new[] { Device.ID });
|
||||
AppState.DevicesFrameFocusedDevice = null;
|
||||
AppState.DevicesFrameFocusedCardState = DeviceCardState.Normal;
|
||||
ParentFrame.Refresh();
|
||||
await ParentFrame.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,22 +1,22 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Immense.SimpleMessenger;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Remotely.Server.Enums;
|
||||
using Remotely.Server.Hubs;
|
||||
using Remotely.Server.Models;
|
||||
using Remotely.Server.Models.Messages;
|
||||
using Remotely.Server.Services;
|
||||
using Remotely.Shared.Attributes;
|
||||
using Remotely.Shared.Entities;
|
||||
using Remotely.Shared.Utilities;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Remotely.Server.Components.Devices;
|
||||
@ -29,7 +29,7 @@ public partial class DevicesFrame : AuthComponentBase, IDisposable
|
||||
private readonly string _deviceGroupNone = Guid.NewGuid().ToString();
|
||||
private readonly List<DeviceGroup> _deviceGroups = new();
|
||||
private readonly List<Device> _devicesForPage = new();
|
||||
private readonly object _devicesLock = new();
|
||||
private readonly SemaphoreSlim _devicesLock = new(1,1);
|
||||
private readonly List<Device> _filteredDevices = new();
|
||||
private readonly List<PropertyInfo> _sortableProperties = new();
|
||||
private int _currentPage = 1;
|
||||
@ -50,24 +50,34 @@ public partial class DevicesFrame : AuthComponentBase, IDisposable
|
||||
private IDataService DataService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IJsInterop JsInterop { get; init; } = null!;
|
||||
private IToastService ToastService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IToastService ToastService { get; init; } = null!;
|
||||
private IMessenger Messenger { get; init; } = null!;
|
||||
|
||||
private int TotalPages => (int)Math.Max(1, Math.Ceiling((decimal)_filteredDevices.Count / _devicesPerPage));
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
CircuitConnection.MessageReceived -= CircuitConnection_MessageReceived;
|
||||
Messenger.Unregister<DisplayNotificationMessage, string>(this, CircuitConnection.ConnectionId);
|
||||
Messenger.Unregister<ScriptResultMessage, string>(this, CircuitConnection.ConnectionId);
|
||||
Messenger.Unregister<DeviceStateChangedMessage, string>(this, CircuitConnection.ConnectionId);
|
||||
|
||||
AppState.PropertyChanged -= AppState_PropertyChanged;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
private async Task HandleDisplayNotificationMessage(DisplayNotificationMessage message)
|
||||
{
|
||||
LoadDevices();
|
||||
InvokeAsync(StateHasChanged);
|
||||
AppState.AddTerminalLine(message.ConsoleText);
|
||||
ToastService.ShowToast(message.ToastText, classString: message.ClassName);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
public async Task Refresh()
|
||||
{
|
||||
await LoadDevices();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
@ -76,7 +86,21 @@ public partial class DevicesFrame : AuthComponentBase, IDisposable
|
||||
|
||||
EnsureUserSet();
|
||||
|
||||
CircuitConnection.MessageReceived += CircuitConnection_MessageReceived;
|
||||
await Messenger.Register<DisplayNotificationMessage, string>(
|
||||
this,
|
||||
CircuitConnection.ConnectionId,
|
||||
HandleDisplayNotificationMessage);
|
||||
|
||||
await Messenger.Register<DeviceStateChangedMessage, string>(
|
||||
this,
|
||||
CircuitConnection.ConnectionId,
|
||||
HandleDeviceStateChangedMessage);
|
||||
|
||||
await Messenger.Register<ScriptResultMessage, string>(
|
||||
this,
|
||||
CircuitConnection.ConnectionId,
|
||||
HandleScriptResultMessage);
|
||||
|
||||
AppState.PropertyChanged += AppState_PropertyChanged;
|
||||
|
||||
_deviceGroups.Clear();
|
||||
@ -91,17 +115,43 @@ public partial class DevicesFrame : AuthComponentBase, IDisposable
|
||||
|
||||
_sortableProperties.AddRange(sortableProperties);
|
||||
|
||||
LoadDevices();
|
||||
await LoadDevices();
|
||||
}
|
||||
|
||||
protected override bool ShouldRender()
|
||||
private async Task HandleScriptResultMessage(ScriptResultMessage message)
|
||||
{
|
||||
var shouldRender = base.ShouldRender();
|
||||
if (shouldRender)
|
||||
await AddScriptResult(message.ScriptResult);
|
||||
}
|
||||
|
||||
private async Task HandleDeviceStateChangedMessage(DeviceStateChangedMessage message)
|
||||
{
|
||||
await _devicesLock.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
FilterDevices();
|
||||
var device = message.Device;
|
||||
|
||||
foreach (var collection in new[] { _allDevices, _devicesForPage })
|
||||
{
|
||||
var index = collection.FindIndex(x => x.ID == device.ID);
|
||||
if (index > -1)
|
||||
{
|
||||
collection[index] = device;
|
||||
}
|
||||
}
|
||||
|
||||
Debouncer.Debounce(TimeSpan.FromSeconds(2), Refresh);
|
||||
}
|
||||
return shouldRender;
|
||||
finally
|
||||
{
|
||||
_devicesLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
await FilterDevices();
|
||||
}
|
||||
|
||||
private async Task AddScriptResult(ScriptResult result)
|
||||
@ -138,65 +188,16 @@ public partial class DevicesFrame : AuthComponentBase, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private async void CircuitConnection_MessageReceived(object? sender, CircuitEvent args)
|
||||
{
|
||||
switch (args.EventName)
|
||||
{
|
||||
case CircuitEventName.DeviceUpdate:
|
||||
case CircuitEventName.DeviceWentOffline:
|
||||
{
|
||||
if (args.Params?.FirstOrDefault() is Device device)
|
||||
{
|
||||
lock (_devicesLock)
|
||||
{
|
||||
var index = _allDevices.FindIndex(x => x.ID == device.ID);
|
||||
if (index > -1)
|
||||
{
|
||||
_allDevices[index] = device;
|
||||
}
|
||||
|
||||
index = _devicesForPage.FindIndex(x => x.ID == device.ID);
|
||||
if (index > -1)
|
||||
{
|
||||
_devicesForPage[index] = device;
|
||||
}
|
||||
}
|
||||
Debouncer.Debounce(TimeSpan.FromSeconds(2), Refresh);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CircuitEventName.DisplayMessage:
|
||||
{
|
||||
var terminalMessage = (string)args.Params[0];
|
||||
var toastMessage = (string)args.Params[1];
|
||||
var className = (string)args.Params[2];
|
||||
AppState.AddTerminalLine(terminalMessage);
|
||||
ToastService.ShowToast(toastMessage, classString: className);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
break;
|
||||
case CircuitEventName.ScriptResult:
|
||||
{
|
||||
if (args.Params[0] is ScriptResult result)
|
||||
{
|
||||
await AddScriptResult(result);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearSelectedCard()
|
||||
{
|
||||
AppState.DevicesFrameFocusedDevice = string.Empty;
|
||||
AppState.DevicesFrameFocusedCardState = DeviceCardState.Normal;
|
||||
}
|
||||
|
||||
private void FilterDevices()
|
||||
private async Task FilterDevices()
|
||||
{
|
||||
lock (_devicesLock)
|
||||
await _devicesLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
_filteredDevices.Clear();
|
||||
var appendDevices = new List<Device>();
|
||||
@ -262,8 +263,12 @@ public partial class DevicesFrame : AuthComponentBase, IDisposable
|
||||
|
||||
_devicesForPage.Clear();
|
||||
_devicesForPage.AddRange(appendDevices.Concat(devicesForPage));
|
||||
}
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
_devicesLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -277,17 +282,18 @@ public partial class DevicesFrame : AuthComponentBase, IDisposable
|
||||
return $"oi-sort-{_sortDirection.ToString().ToLower()}";
|
||||
}
|
||||
|
||||
private void HandleRefreshClicked()
|
||||
private async Task HandleRefreshClicked()
|
||||
{
|
||||
Refresh();
|
||||
await Refresh();
|
||||
ToastService.ShowToast("Devices refreshed.");
|
||||
}
|
||||
|
||||
private void LoadDevices()
|
||||
private async Task LoadDevices()
|
||||
{
|
||||
EnsureUserSet();
|
||||
|
||||
lock (_devicesLock)
|
||||
await _devicesLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
_allDevices.Clear();
|
||||
|
||||
@ -297,8 +303,12 @@ public partial class DevicesFrame : AuthComponentBase, IDisposable
|
||||
|
||||
_allDevices.AddRange(devices);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_devicesLock.Release();
|
||||
}
|
||||
|
||||
FilterDevices();
|
||||
await FilterDevices();
|
||||
}
|
||||
private void PageDown()
|
||||
{
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
using Immense.RemoteControl.Server.Abstractions;
|
||||
using Immense.SimpleMessenger;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Remotely.Server.Components.ModalContents;
|
||||
using Remotely.Server.Hubs;
|
||||
using Remotely.Server.Models.Messages;
|
||||
using Remotely.Server.Services;
|
||||
using Remotely.Shared.Entities;
|
||||
using Remotely.Shared.Enums;
|
||||
@ -38,6 +40,9 @@ public partial class Terminal : AuthComponentBase, IDisposable
|
||||
[Inject]
|
||||
private IDataService DataService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IMessenger Messenger { get; init; } = null!;
|
||||
|
||||
private string InputText
|
||||
{
|
||||
get => _inputText ?? string.Empty;
|
||||
@ -90,7 +95,7 @@ public partial class Terminal : AuthComponentBase, IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
AppState.PropertyChanged -= AppState_PropertyChanged;
|
||||
CircuitConnection.MessageReceived -= CircuitConnection_MessageReceived;
|
||||
Messenger.Unregister<PowerShellCompletionsMessage, string>(this, CircuitConnection.ConnectionId);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
@ -106,9 +111,32 @@ public partial class Terminal : AuthComponentBase, IDisposable
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
CircuitConnection.MessageReceived += CircuitConnection_MessageReceived;
|
||||
await Messenger.Register<PowerShellCompletionsMessage, string>(
|
||||
this,
|
||||
CircuitConnection.ConnectionId,
|
||||
HandlePowerShellCompletionsMessage);
|
||||
AppState.PropertyChanged += AppState_PropertyChanged;
|
||||
}
|
||||
|
||||
private async Task HandlePowerShellCompletionsMessage(PowerShellCompletionsMessage message)
|
||||
{
|
||||
var completion = message.Completion;
|
||||
var intent = message.Intent;
|
||||
|
||||
switch (intent)
|
||||
{
|
||||
case CompletionIntent.ShowAll:
|
||||
await DisplayCompletions(completion.CompletionMatches);
|
||||
break;
|
||||
case CompletionIntent.NextResult:
|
||||
ApplyCompletion(completion);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
AppState.InvokePropertyChanged(nameof(AppState.TerminalLines));
|
||||
}
|
||||
|
||||
private void ApplyCompletion(PwshCommandCompletion completion)
|
||||
{
|
||||
try
|
||||
@ -142,27 +170,6 @@ public partial class Terminal : AuthComponentBase, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private async void CircuitConnection_MessageReceived(object? sender, Models.CircuitEvent e)
|
||||
{
|
||||
if (e.EventName == Models.CircuitEventName.PowerShellCompletions)
|
||||
{
|
||||
var completion = (PwshCommandCompletion)e.Params[0];
|
||||
var intent = (CompletionIntent)e.Params[1];
|
||||
|
||||
switch (intent)
|
||||
{
|
||||
case CompletionIntent.ShowAll:
|
||||
await DisplayCompletions(completion.CompletionMatches);
|
||||
break;
|
||||
case CompletionIntent.NextResult:
|
||||
ApplyCompletion(completion);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
AppState.InvokePropertyChanged(nameof(AppState.TerminalLines));
|
||||
}
|
||||
}
|
||||
private async Task DisplayCompletions(List<PwshCompletionResult> completionMatches)
|
||||
{
|
||||
var deviceId = AppState.DevicesFrameSelectedDevices.FirstOrDefault();
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
@using Nihs.SimpleMessenger;
|
||||
@using Immense.SimpleMessenger;
|
||||
@using Remotely.Server.Models.Messages;
|
||||
@inject IMessenger Messenger
|
||||
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
using Immense.RemoteControl.Server.Hubs;
|
||||
using Immense.SimpleMessenger;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Remotely.Server.Models;
|
||||
using Remotely.Server.Models.Messages;
|
||||
using Remotely.Server.Services;
|
||||
using Remotely.Shared;
|
||||
using Remotely.Shared.Dtos;
|
||||
@ -24,6 +26,7 @@ public class AgentHub : Hub<IAgentHubClient>
|
||||
private readonly ICircuitManager _circuitManager;
|
||||
private readonly IDataService _dataService;
|
||||
private readonly IExpiringTokenService _expiringTokenService;
|
||||
private readonly IMessenger _messenger;
|
||||
private readonly ILogger<AgentHub> _logger;
|
||||
private readonly IAgentHubSessionCache _serviceSessionCache;
|
||||
private readonly IHubContext<ViewerHub> _viewerHubContext;
|
||||
@ -34,6 +37,7 @@ public class AgentHub : Hub<IAgentHubClient>
|
||||
IHubContext<ViewerHub> viewerHubContext,
|
||||
ICircuitManager circuitManager,
|
||||
IExpiringTokenService expiringTokenService,
|
||||
IMessenger messenger,
|
||||
ILogger<AgentHub> logger)
|
||||
{
|
||||
_dataService = dataService;
|
||||
@ -42,6 +46,7 @@ public class AgentHub : Hub<IAgentHubClient>
|
||||
_appConfig = appConfig;
|
||||
_circuitManager = circuitManager;
|
||||
_expiringTokenService = expiringTokenService;
|
||||
_messenger = messenger;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@ -65,20 +70,21 @@ public class AgentHub : Hub<IAgentHubClient>
|
||||
}
|
||||
}
|
||||
|
||||
public Task Chat(string message, bool disconnected, string browserConnectionId)
|
||||
public async Task Chat(string messageText, bool disconnected, string browserConnectionId)
|
||||
{
|
||||
if (Device is null)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_circuitManager.TryGetConnection(browserConnectionId, out var connection))
|
||||
if (_circuitManager.TryGetConnection(browserConnectionId, out _))
|
||||
{
|
||||
return connection.InvokeCircuitEvent(CircuitEventName.ChatReceived, Device.ID, $"{Device.DeviceName}", message, disconnected);
|
||||
var message = new ChatReceivedMessage(Device.ID, $"{Device.DeviceName}", messageText, disconnected);
|
||||
await _messenger.Send(message, browserConnectionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Clients.Caller.SendChatMessage(
|
||||
await Clients.Caller.SendChatMessage(
|
||||
senderName: string.Empty,
|
||||
message: string.Empty,
|
||||
orgName: string.Empty,
|
||||
@ -152,7 +158,8 @@ public class AgentHub : Hub<IAgentHubClient>
|
||||
|
||||
foreach (var connection in connections)
|
||||
{
|
||||
await connection.InvokeCircuitEvent(CircuitEventName.DeviceUpdate, Device);
|
||||
var message = new DeviceStateChangedMessage(Device);
|
||||
await _messenger.Send(message, connection.ConnectionId);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -212,7 +219,8 @@ public class AgentHub : Hub<IAgentHubClient>
|
||||
|
||||
foreach (var connection in connections)
|
||||
{
|
||||
_ = connection.InvokeCircuitEvent(CircuitEventName.DeviceUpdate, Device);
|
||||
var message = new DeviceStateChangedMessage(Device);
|
||||
await _messenger.Send(message, connection.ConnectionId);
|
||||
}
|
||||
|
||||
|
||||
@ -220,19 +228,22 @@ public class AgentHub : Hub<IAgentHubClient>
|
||||
}
|
||||
|
||||
|
||||
public Task<bool> DisplayMessage(string consoleMessage, string popupMessage, string className, string requesterID)
|
||||
public Task DisplayMessage(string consoleMessage, string popupMessage, string className, string requesterId)
|
||||
{
|
||||
return _circuitManager.InvokeOnConnection(requesterID, CircuitEventName.DisplayMessage, consoleMessage, popupMessage, className);
|
||||
var message = new DisplayNotificationMessage(consoleMessage, popupMessage, className);
|
||||
return _messenger.Send(message, requesterId);
|
||||
}
|
||||
|
||||
public Task<bool> DownloadFile(string fileID, string requesterID)
|
||||
public Task DownloadFile(string fileID, string requesterId)
|
||||
{
|
||||
return _circuitManager.InvokeOnConnection(requesterID, CircuitEventName.DownloadFile, fileID);
|
||||
var message = new DownloadFileMessage(fileID);
|
||||
return _messenger.Send(message, requesterId);
|
||||
}
|
||||
|
||||
public Task<bool> DownloadFileProgress(int progressPercent, string requesterID)
|
||||
public Task DownloadFileProgress(int progressPercent, string requesterId)
|
||||
{
|
||||
return _circuitManager.InvokeOnConnection(requesterID, CircuitEventName.DownloadFileProgress, progressPercent);
|
||||
var message = new DownloadFileProgressMessage(progressPercent);
|
||||
return _messenger.Send(message, requesterId);
|
||||
}
|
||||
|
||||
public string GetServerUrl()
|
||||
@ -245,7 +256,7 @@ public class AgentHub : Hub<IAgentHubClient>
|
||||
return $"{Device?.ServerVerificationToken}";
|
||||
}
|
||||
|
||||
public override Task OnDisconnectedAsync(Exception? exception)
|
||||
public override async Task OnDisconnectedAsync(Exception? exception)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -265,10 +276,11 @@ public class AgentHub : Hub<IAgentHubClient>
|
||||
|
||||
foreach (var connection in connections)
|
||||
{
|
||||
connection.InvokeCircuitEvent(CircuitEventName.DeviceWentOffline, Device);
|
||||
var message = new DeviceStateChangedMessage(Device);
|
||||
await _messenger.Send(message, connection.ConnectionId);
|
||||
}
|
||||
}
|
||||
return base.OnDisconnectedAsync(exception);
|
||||
await base.OnDisconnectedAsync(exception);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -278,7 +290,8 @@ public class AgentHub : Hub<IAgentHubClient>
|
||||
|
||||
public Task ReturnPowerShellCompletions(PwshCommandCompletion completion, CompletionIntent intent, string senderConnectionId)
|
||||
{
|
||||
return _circuitManager.InvokeOnConnection(senderConnectionId, CircuitEventName.PowerShellCompletions, completion, intent);
|
||||
var message = new PowerShellCompletionsMessage(completion, intent);
|
||||
return _messenger.Send(message, senderConnectionId);
|
||||
}
|
||||
|
||||
public async Task ScriptResult(string scriptResultId)
|
||||
@ -289,9 +302,8 @@ public class AgentHub : Hub<IAgentHubClient>
|
||||
return;
|
||||
}
|
||||
|
||||
_ = await _circuitManager.InvokeOnConnection($"{result.Value.SenderConnectionID}",
|
||||
CircuitEventName.ScriptResult,
|
||||
result.Value);
|
||||
var message = new ScriptResultMessage(result.Value);
|
||||
await _messenger.Send(message, $"{result.Value.SenderConnectionID}");
|
||||
}
|
||||
|
||||
public void ScriptResultViaApi(string commandID, string requestID)
|
||||
@ -305,7 +317,8 @@ public class AgentHub : Hub<IAgentHubClient>
|
||||
|
||||
public Task SendLogs(string logChunk, string requesterConnectionId)
|
||||
{
|
||||
return _circuitManager.InvokeOnConnection(requesterConnectionId, CircuitEventName.RemoteLogsReceived, logChunk);
|
||||
var message = new ReceiveLogsMessage(logChunk);
|
||||
return _messenger.Send(message, requesterConnectionId);
|
||||
}
|
||||
public void SetServerVerificationToken(string verificationToken)
|
||||
{
|
||||
@ -316,9 +329,10 @@ public class AgentHub : Hub<IAgentHubClient>
|
||||
Device.ServerVerificationToken = verificationToken;
|
||||
_dataService.SetServerVerificationToken(Device.ID, verificationToken);
|
||||
}
|
||||
public Task TransferCompleted(string transferID, string requesterID)
|
||||
public Task TransferCompleted(string transferId, string requesterId)
|
||||
{
|
||||
return _circuitManager.InvokeOnConnection(requesterID, CircuitEventName.TransferCompleted, transferID);
|
||||
var message = new TransferCompleteMessage(transferId);
|
||||
return _messenger.Send(message, requesterId);
|
||||
}
|
||||
private async Task<bool> CheckForDeviceBan(params string[] deviceIdNameOrIPs)
|
||||
{
|
||||
|
||||
@ -1,15 +1,12 @@
|
||||
using Immense.RemoteControl.Server.Abstractions;
|
||||
using Immense.RemoteControl.Server.Services;
|
||||
using Immense.RemoteControl.Server.Services;
|
||||
using Immense.RemoteControl.Shared;
|
||||
using Immense.RemoteControl.Shared.Helpers;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Immense.SimpleMessenger;
|
||||
using Microsoft.AspNetCore.Components.Server.Circuits;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Remotely.Server.Auth;
|
||||
using Remotely.Server.Models;
|
||||
using Remotely.Server.Models.Messages;
|
||||
using Remotely.Server.Services;
|
||||
using Remotely.Shared;
|
||||
using Remotely.Shared.Entities;
|
||||
@ -20,7 +17,6 @@ using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -28,7 +24,8 @@ namespace Remotely.Server.Hubs;
|
||||
|
||||
public interface ICircuitConnection
|
||||
{
|
||||
event EventHandler<CircuitEvent>? MessageReceived;
|
||||
string ConnectionId { get; }
|
||||
|
||||
RemotelyUser User { get; }
|
||||
|
||||
Task DeleteRemoteLogs(string deviceId);
|
||||
@ -39,7 +36,6 @@ public interface ICircuitConnection
|
||||
|
||||
Task GetRemoteLogs(string deviceId);
|
||||
|
||||
Task InvokeCircuitEvent(CircuitEventName eventName, params object[] args);
|
||||
Task ReinstallAgents(string[] deviceIDs);
|
||||
|
||||
Task<Result<RemoteControlSessionEx>> RemoteControl(string deviceID, bool viewOnly);
|
||||
@ -80,12 +76,11 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
|
||||
private readonly ICircuitManager _circuitManager;
|
||||
private readonly IDataService _dataService;
|
||||
private readonly IRemoteControlSessionCache _remoteControlSessionCache;
|
||||
private readonly ConcurrentQueue<CircuitEvent> _eventQueue = new();
|
||||
private readonly IExpiringTokenService _expiringTokenService;
|
||||
private readonly ILogger<CircuitConnection> _logger;
|
||||
private readonly IAgentHubSessionCache _agentSessionCache;
|
||||
private readonly IMessenger _messenger;
|
||||
private readonly IToastService _toastService;
|
||||
private readonly ManualResetEventSlim _initSignal = new();
|
||||
private RemotelyUser? _user;
|
||||
|
||||
public CircuitConnection(
|
||||
@ -99,6 +94,7 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
|
||||
IExpiringTokenService expiringTokenService,
|
||||
IRemoteControlSessionCache remoteControlSessionCache,
|
||||
IAgentHubSessionCache agentSessionCache,
|
||||
IMessenger messenger,
|
||||
ILogger<CircuitConnection> logger)
|
||||
{
|
||||
_dataService = dataService;
|
||||
@ -111,33 +107,17 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
|
||||
_expiringTokenService = expiringTokenService;
|
||||
_remoteControlSessionCache = remoteControlSessionCache;
|
||||
_agentSessionCache = agentSessionCache;
|
||||
_messenger = messenger;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
||||
public event EventHandler<CircuitEvent>? MessageReceived;
|
||||
|
||||
public string ConnectionId { get; } = Guid.NewGuid().ToString();
|
||||
|
||||
public RemotelyUser User
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_initSignal.Wait(TimeSpan.FromSeconds(5)) && _user is not null)
|
||||
{
|
||||
return _user;
|
||||
}
|
||||
_logger.LogError("Failed to resolve user.");
|
||||
throw new InvalidOperationException("Failed to resolve user.");
|
||||
}
|
||||
internal set
|
||||
{
|
||||
_user = value;
|
||||
if (_user is not null)
|
||||
{
|
||||
_initSignal.Set();
|
||||
}
|
||||
}
|
||||
get => _user ?? throw new InvalidOperationException("User is not set.");
|
||||
internal set => _user = value;
|
||||
}
|
||||
|
||||
|
||||
@ -212,12 +192,6 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
|
||||
return _agentHubContext.Clients.Client(key).GetLogs(ConnectionId);
|
||||
}
|
||||
|
||||
public Task InvokeCircuitEvent(CircuitEventName eventName, params object[] args)
|
||||
{
|
||||
_eventQueue.Enqueue(new CircuitEvent(eventName, args));
|
||||
return Task.Run(ProcessMessages);
|
||||
}
|
||||
|
||||
public override async Task OnCircuitClosedAsync(Circuit circuit, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(ConnectionId))
|
||||
@ -237,9 +211,8 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
|
||||
_toastService.ShowToast2("Authorization failure.", Enums.ToastType.Error);
|
||||
return;
|
||||
}
|
||||
_user = userResult.Value;
|
||||
User = userResult.Value;
|
||||
_circuitManager.TryAddConnection(ConnectionId, this);
|
||||
_initSignal.Set();
|
||||
}
|
||||
await base.OnCircuitOpenedAsync(circuit, cancellationToken);
|
||||
}
|
||||
@ -256,10 +229,13 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
|
||||
{
|
||||
if (!_agentSessionCache.TryGetByDeviceId(deviceId, out var targetDevice))
|
||||
{
|
||||
MessageReceived?.Invoke(this, new CircuitEvent(CircuitEventName.DisplayMessage,
|
||||
var message = new DisplayNotificationMessage(
|
||||
"The selected device is not online.",
|
||||
"Device is not online.",
|
||||
"bg-warning"));
|
||||
"bg-warning");
|
||||
|
||||
await _messenger.Send(message, ConnectionId);
|
||||
|
||||
return Result.Fail<RemoteControlSessionEx>("Device is not online.");
|
||||
}
|
||||
|
||||
@ -281,19 +257,24 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
|
||||
|
||||
if (sessionCount >= _appConfig.RemoteControlSessionLimit)
|
||||
{
|
||||
MessageReceived?.Invoke(this, new CircuitEvent(CircuitEventName.DisplayMessage,
|
||||
var message = new DisplayNotificationMessage(
|
||||
"There are already the maximum amount of active remote control sessions for your organization.",
|
||||
"Max number of concurrent sessions reached.",
|
||||
"bg-warning"));
|
||||
"bg-warning");
|
||||
|
||||
await _messenger.Send(message, ConnectionId);
|
||||
|
||||
return Result.Fail<RemoteControlSessionEx>("Max number of concurrent sessions reached.");
|
||||
}
|
||||
|
||||
if (!_agentSessionCache.TryGetConnectionId(targetDevice.ID, out var serviceConnectionId))
|
||||
{
|
||||
MessageReceived?.Invoke(this, new CircuitEvent(CircuitEventName.DisplayMessage,
|
||||
"Service connection not found.",
|
||||
"Service connection not found.",
|
||||
"bg-warning"));
|
||||
var message = new DisplayNotificationMessage(
|
||||
"Service connection not found.",
|
||||
"Service connection not found.",
|
||||
"bg-warning");
|
||||
|
||||
await _messenger.Send(message, ConnectionId);
|
||||
return Result.Fail<RemoteControlSessionEx>("Service connection not found.");
|
||||
}
|
||||
|
||||
@ -463,24 +444,30 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
|
||||
_dataService.RemoveDevices(deviceIDs);
|
||||
}
|
||||
|
||||
public Task UpdateTags(string deviceID, string tags)
|
||||
public async Task UpdateTags(string deviceID, string tags)
|
||||
{
|
||||
if (_dataService.DoesUserHaveAccessToDevice(deviceID, User))
|
||||
{
|
||||
if (tags.Length > 200)
|
||||
{
|
||||
MessageReceived?.Invoke(this, new CircuitEvent(CircuitEventName.DisplayMessage,
|
||||
var message = new CircuitEvent(CircuitEventName.DisplayMessage,
|
||||
$"Tag must be 200 characters or less. Supplied length is {tags.Length}.",
|
||||
"Tag must be under 200 characters.",
|
||||
"bg-warning"));
|
||||
"bg-warning");
|
||||
|
||||
await _messenger.Send(message, ConnectionId);
|
||||
return;
|
||||
}
|
||||
_dataService.UpdateTags(deviceID, tags);
|
||||
MessageReceived?.Invoke(this, new CircuitEvent(CircuitEventName.DisplayMessage,
|
||||
|
||||
await _dataService.UpdateTags(deviceID, tags);
|
||||
|
||||
var successMessage = new DisplayNotificationMessage(
|
||||
"Device updated successfully.",
|
||||
"Device updated.",
|
||||
"bg-success"));
|
||||
"bg-success");
|
||||
|
||||
await _messenger.Send(successMessage, ConnectionId);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task<Result> WakeDevice(Device device)
|
||||
@ -606,23 +593,6 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessMessages()
|
||||
{
|
||||
lock (_eventQueue)
|
||||
{
|
||||
while (_eventQueue.TryDequeue(out var circuitEvent))
|
||||
{
|
||||
try
|
||||
{
|
||||
MessageReceived?.Invoke(this, circuitEvent);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error while invoking circuit event.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendWakeCommand(Device deviceToWake, IEnumerable<Device> peerDevices)
|
||||
{
|
||||
|
||||
3
Server/Models/Messages/ChatReceivedMessage.cs
Normal file
3
Server/Models/Messages/ChatReceivedMessage.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace Remotely.Server.Models.Messages;
|
||||
|
||||
public record ChatReceivedMessage(string DeviceId, string DeviceName, string MessageText, bool DidDisconnect = false);
|
||||
5
Server/Models/Messages/DeviceStateChangedMessage.cs
Normal file
5
Server/Models/Messages/DeviceStateChangedMessage.cs
Normal file
@ -0,0 +1,5 @@
|
||||
using Remotely.Shared.Entities;
|
||||
|
||||
namespace Remotely.Server.Models.Messages;
|
||||
|
||||
public record DeviceStateChangedMessage(Device Device);
|
||||
3
Server/Models/Messages/DisplayNotificationMessage.cs
Normal file
3
Server/Models/Messages/DisplayNotificationMessage.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace Remotely.Server.Models.Messages;
|
||||
|
||||
public record DisplayNotificationMessage(string ConsoleText, string ToastText, string ClassName);
|
||||
3
Server/Models/Messages/DownloadFileMessage.cs
Normal file
3
Server/Models/Messages/DownloadFileMessage.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace Remotely.Server.Models.Messages;
|
||||
|
||||
public record DownloadFileMessage(string MessageId);
|
||||
3
Server/Models/Messages/DownloadFileProgressMessage.cs
Normal file
3
Server/Models/Messages/DownloadFileProgressMessage.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace Remotely.Server.Models.Messages;
|
||||
|
||||
public record DownloadFileProgressMessage(int ProgressPercent);
|
||||
6
Server/Models/Messages/PowerShellCompletionsMessage.cs
Normal file
6
Server/Models/Messages/PowerShellCompletionsMessage.cs
Normal file
@ -0,0 +1,6 @@
|
||||
using Remotely.Shared.Enums;
|
||||
using Remotely.Shared.Models;
|
||||
|
||||
namespace Remotely.Server.Models.Messages;
|
||||
|
||||
public record PowerShellCompletionsMessage(PwshCommandCompletion Completion, CompletionIntent Intent);
|
||||
3
Server/Models/Messages/ReceiveLogsMessage.cs
Normal file
3
Server/Models/Messages/ReceiveLogsMessage.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace Remotely.Server.Models.Messages;
|
||||
|
||||
public record ReceiveLogsMessage(string LogChunk);
|
||||
5
Server/Models/Messages/ScriptResultMessage.cs
Normal file
5
Server/Models/Messages/ScriptResultMessage.cs
Normal file
@ -0,0 +1,5 @@
|
||||
using Remotely.Shared.Entities;
|
||||
|
||||
namespace Remotely.Server.Models.Messages;
|
||||
|
||||
public record ScriptResultMessage(ScriptResult ScriptResult);
|
||||
@ -1,13 +1,3 @@
|
||||
namespace Remotely.Server.Models.Messages;
|
||||
|
||||
public class ShowLoaderMessage
|
||||
{
|
||||
public ShowLoaderMessage(bool isShown, string statusMessage)
|
||||
{
|
||||
IsShown = isShown;
|
||||
StatusMessage = statusMessage;
|
||||
}
|
||||
|
||||
public bool IsShown { get; }
|
||||
public string StatusMessage { get; }
|
||||
}
|
||||
public record ShowLoaderMessage(bool IsShown, string StatusMessage);
|
||||
3
Server/Models/Messages/TransferCompleteMessage.cs
Normal file
3
Server/Models/Messages/TransferCompleteMessage.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace Remotely.Server.Models.Messages;
|
||||
|
||||
public record TransferCompleteMessage(string TransferId);
|
||||
@ -1,8 +1,10 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Immense.SimpleMessenger;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Remotely.Server.Components;
|
||||
using Remotely.Server.Hubs;
|
||||
using Remotely.Server.Models.Messages;
|
||||
using Remotely.Server.Services;
|
||||
using Remotely.Shared.Entities;
|
||||
using Remotely.Shared.Enums;
|
||||
@ -52,6 +54,9 @@ public partial class DeviceDetails : AuthComponentBase
|
||||
[Inject]
|
||||
private IToastService ToastService { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IMessenger Messenger { get; init; } = null!;
|
||||
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
@ -74,18 +79,18 @@ public partial class DeviceDetails : AuthComponentBase
|
||||
}
|
||||
|
||||
_deviceGroups = DataService.GetDeviceGroups(UserName);
|
||||
CircuitConnection.MessageReceived += CircuitConnection_MessageReceived;
|
||||
await Messenger.Register<ReceiveLogsMessage, string>(
|
||||
this,
|
||||
CircuitConnection.ConnectionId,
|
||||
HandleReceiveLogsMessage);
|
||||
|
||||
_isLoading = false;
|
||||
}
|
||||
|
||||
private void CircuitConnection_MessageReceived(object? sender, Models.CircuitEvent e)
|
||||
private async Task HandleReceiveLogsMessage(ReceiveLogsMessage message)
|
||||
{
|
||||
if (e.EventName == Models.CircuitEventName.RemoteLogsReceived)
|
||||
{
|
||||
var logChunk = (string)e.Params[0];
|
||||
_logLines.Enqueue(logChunk);
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
_logLines.Enqueue(message.LogChunk);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task DeleteLogs()
|
||||
|
||||
@ -14,7 +14,6 @@ using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using Remotely.Server.Areas.Identity;
|
||||
using Remotely.Server.Auth;
|
||||
using Remotely.Server.Data;
|
||||
@ -28,10 +27,10 @@ using Remotely.Server.Services.RcImplementations;
|
||||
using Remotely.Shared.Services;
|
||||
using System;
|
||||
using Serilog;
|
||||
using Nihs.SimpleMessenger;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
using RatePolicyNames = Remotely.Server.RateLimiting.PolicyNames;
|
||||
using Remotely.Shared.Entities;
|
||||
using Immense.SimpleMessenger;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
var configuration = builder.Configuration;
|
||||
@ -211,7 +210,7 @@ services.AddScoped<IScriptScheduleDispatcher, ScriptScheduleDispatcher>();
|
||||
services.AddSingleton<IOtpProvider, OtpProvider>();
|
||||
services.AddSingleton<IEmbeddedServerDataSearcher, EmbeddedServerDataSearcher>();
|
||||
services.AddSingleton<ILogsManager, LogsManager>();
|
||||
services.AddScoped<IMessenger>((services) => new WeakReferenceMessenger());
|
||||
services.AddSingleton(WeakReferenceMessenger.Default);
|
||||
|
||||
services.AddRemoteControlServer(config =>
|
||||
{
|
||||
|
||||
@ -32,7 +32,6 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.EventLog" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.18.1" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="7.0.8" />
|
||||
<PackageReference Include="Nihs.SimpleMessenger" Version="1.0.0" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.4" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="7.0.0" />
|
||||
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
|
||||
|
||||
@ -15,7 +15,6 @@ public interface ICircuitManager
|
||||
ICollection<ICircuitConnection> Connections { get; }
|
||||
bool TryAddConnection(string id, ICircuitConnection connection);
|
||||
bool TryRemoveConnection(string id, [NotNullWhen(true)] out ICircuitConnection? connection);
|
||||
Task<bool> InvokeOnConnection(string id, CircuitEventName eventName, params object[] args);
|
||||
bool TryGetConnection(string id, [NotNullWhen(true)] out ICircuitConnection? connection);
|
||||
}
|
||||
public class CircuitManager : ICircuitManager
|
||||
@ -30,23 +29,6 @@ public class CircuitManager : ICircuitManager
|
||||
|
||||
public ICollection<ICircuitConnection> Connections => _connections.Values;
|
||||
|
||||
public Task<bool> InvokeOnConnection(string browserConnectionId, CircuitEventName eventName, params object[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_connections.TryGetValue(browserConnectionId, out var result))
|
||||
{
|
||||
result.InvokeCircuitEvent(eventName, args);
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error while invoking circuit event.");
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryAddConnection(string id, ICircuitConnection connection)
|
||||
{
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
using Nihs.ConcurrentList;
|
||||
using Remotely.Server.Enums;
|
||||
using Remotely.Server.Enums;
|
||||
using Remotely.Shared.Enums;
|
||||
using Remotely.Shared.Models;
|
||||
using Remotely.Shared.Primitives;
|
||||
using Remotely.Shared.ViewModels;
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
using Immense.RemoteControl.Shared.Primitives;
|
||||
using Nihs.SimpleMessenger;
|
||||
using Immense.SimpleMessenger;
|
||||
using Remotely.Server.Models.Messages;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
using Nihs.ConcurrentList;
|
||||
using Remotely.Server.Enums;
|
||||
using Remotely.Server.Enums;
|
||||
using Remotely.Server.Models;
|
||||
using Remotely.Shared.Primitives;
|
||||
using System;
|
||||
using System.Timers;
|
||||
|
||||
|
||||
198
Shared/Primitives/ConcurrentList.cs
Normal file
198
Shared/Primitives/ConcurrentList.cs
Normal file
@ -0,0 +1,198 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Remotely.Shared.Primitives;
|
||||
|
||||
/// <summary>
|
||||
/// A simple, lock-based implementation of a thread-safe List<T>.
|
||||
/// Note that a copy is returned when enumerating the list.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class ConcurrentList<T> : IList<T>
|
||||
{
|
||||
|
||||
private readonly List<T> _list = new();
|
||||
private readonly object _lock = new();
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _list.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _list[index];
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_list[index] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
public void Add(T item)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_list.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddRange(IEnumerable<T> collection)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_list.AddRange(collection);
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_list.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _list.Contains(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_list.CopyTo(array, arrayIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Exists(Predicate<T> predicate)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _list.Exists(predicate);
|
||||
}
|
||||
}
|
||||
|
||||
public List<T> FindAll(Predicate<T> predicate)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _list.FindAll(predicate);
|
||||
}
|
||||
}
|
||||
|
||||
public T? Find(Predicate<T> predicate)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _list.Find(predicate);
|
||||
}
|
||||
}
|
||||
|
||||
public int FindIndex(Predicate<T> predicate)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _list.FindIndex(predicate);
|
||||
}
|
||||
}
|
||||
public T? FindLast(Predicate<T> predicate)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _list.FindLast(predicate);
|
||||
}
|
||||
}
|
||||
|
||||
public int FindLastIndex(Predicate<T> predicate)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _list.FindLastIndex(predicate);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _list.ToList().GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _list.ToList().GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
||||
public int IndexOf(T item)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _list.IndexOf(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void Insert(int index, T item)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_list.Insert(index, item);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(T item)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _list.Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveAll(Predicate<T> predicate)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_list.RemoveAll(predicate);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_list.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
public void RemoveRange(int index, int count)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_list.RemoveRange(index, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -11,7 +11,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="7.0.9" />
|
||||
<PackageReference Include="Nihs.ConcurrentList" Version="1.0.0" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
<PackageReference Include="System.Security.Principal.Windows" Version="5.0.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="7.0.3" />
|
||||
|
||||
@ -26,4 +26,20 @@ public static class Debouncer
|
||||
_timers.TryAdd(key, timer);
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
public static void Debounce(TimeSpan wait, Func<Task> func, [CallerMemberName] string key = "")
|
||||
{
|
||||
if (_timers.TryRemove(key, out var timer))
|
||||
{
|
||||
timer.Stop();
|
||||
timer.Dispose();
|
||||
}
|
||||
timer = new Timer(wait.TotalMilliseconds)
|
||||
{
|
||||
AutoReset = false
|
||||
};
|
||||
timer.Elapsed += (s, e) => func();
|
||||
_timers.TryAdd(key, timer);
|
||||
timer.Start();
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
using Nihs.ConcurrentList;
|
||||
using Remotely.Shared.Primitives;
|
||||
using System;
|
||||
|
||||
namespace Remotely.Shared.ViewModels;
|
||||
|
||||
@ -13,8 +13,10 @@ using System.Security.Claims;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Remotely.Shared.Interfaces;
|
||||
using Immense.SimpleMessenger;
|
||||
using Remotely.Tests;
|
||||
|
||||
namespace Remotely.Tests;
|
||||
namespace Remotely.Server.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class AgentHubTests
|
||||
@ -34,17 +36,19 @@ public class AgentHubTests
|
||||
var viewerHub = new Mock<IHubContext<ViewerHub>>();
|
||||
var expiringTokenService = new Mock<IExpiringTokenService>();
|
||||
var serviceSessionCache = new Mock<IAgentHubSessionCache>();
|
||||
var messenger = new Mock<IMessenger>();
|
||||
var logger = new Mock<ILogger<AgentHub>>();
|
||||
|
||||
appConfig.Setup(x => x.BannedDevices).Returns(new string[] { $"{_testData.Org1Device1.DeviceName}" });
|
||||
|
||||
var hub = new AgentHub(
|
||||
_dataService,
|
||||
appConfig.Object,
|
||||
serviceSessionCache.Object,
|
||||
viewerHub.Object,
|
||||
circuitManager.Object,
|
||||
_dataService,
|
||||
appConfig.Object,
|
||||
serviceSessionCache.Object,
|
||||
viewerHub.Object,
|
||||
circuitManager.Object,
|
||||
expiringTokenService.Object,
|
||||
messenger.Object,
|
||||
logger.Object);
|
||||
|
||||
var hubClients = new Mock<IHubCallerClients<IAgentHubClient>>();
|
||||
@ -71,6 +75,7 @@ public class AgentHubTests
|
||||
var viewerHub = new Mock<IHubContext<ViewerHub>>();
|
||||
var expiringTokenService = new Mock<IExpiringTokenService>();
|
||||
var serviceSessionCache = new Mock<IAgentHubSessionCache>();
|
||||
var messenger = new Mock<IMessenger>();
|
||||
var logger = new Mock<ILogger<AgentHub>>();
|
||||
|
||||
appConfig.Setup(x => x.BannedDevices).Returns(new string[] { _testData.Org1Device1.ID });
|
||||
@ -82,6 +87,7 @@ public class AgentHubTests
|
||||
viewerHub.Object,
|
||||
circuitManager.Object,
|
||||
expiringTokenService.Object,
|
||||
messenger.Object,
|
||||
logger.Object);
|
||||
|
||||
var hubClients = new Mock<IHubCallerClients<IAgentHubClient>>();
|
||||
@ -123,7 +129,7 @@ public class AgentHubTests
|
||||
|
||||
public override void Abort()
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#nullable enable
|
||||
using Castle.Core.Logging;
|
||||
using Immense.RemoteControl.Server.Services;
|
||||
using Immense.SimpleMessenger;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@ -37,6 +38,7 @@ public class CircuitConnectionTests
|
||||
private Mock<IToastService> _toastService;
|
||||
private Mock<IExpiringTokenService> _expiringTokenService;
|
||||
private Mock<IRemoteControlSessionCache> _remoteControlSessionCache;
|
||||
private Mock<IMessenger> _messenger;
|
||||
private Mock<IAgentHubSessionCache> _agentSessionCache;
|
||||
private Mock<ILogger<CircuitConnection>> _logger;
|
||||
private CircuitConnection _circuitConnection;
|
||||
@ -57,6 +59,7 @@ public class CircuitConnectionTests
|
||||
_toastService = new Mock<IToastService>();
|
||||
_expiringTokenService = new Mock<IExpiringTokenService>();
|
||||
_remoteControlSessionCache = new Mock<IRemoteControlSessionCache>();
|
||||
_messenger = new Mock<IMessenger>();
|
||||
_agentSessionCache = new Mock<IAgentHubSessionCache>();
|
||||
_logger = new Mock<ILogger<CircuitConnection>>();
|
||||
|
||||
@ -71,6 +74,7 @@ public class CircuitConnectionTests
|
||||
_expiringTokenService.Object,
|
||||
_remoteControlSessionCache.Object,
|
||||
_agentSessionCache.Object,
|
||||
_messenger.Object,
|
||||
_logger.Object);
|
||||
}
|
||||
|
||||
|
||||
80
Tests/Shared.Tests/ConcurrentListTests.cs
Normal file
80
Tests/Shared.Tests/ConcurrentListTests.cs
Normal file
@ -0,0 +1,80 @@
|
||||
using Remotely.Shared.Primitives;
|
||||
|
||||
namespace Remotely.Shared.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class ConcurrentListTests
|
||||
{
|
||||
private readonly int _startCount = 500_000;
|
||||
private ConcurrentList<int> _list = new();
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
{
|
||||
_list = new ConcurrentList<int>();
|
||||
for (var i = 0; i < _startCount; i++)
|
||||
{
|
||||
_list.Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void MultipleOperations_GivenMultipleThreads_Ok()
|
||||
{
|
||||
Assert.IsTrue(_list.Contains(500));
|
||||
Assert.IsTrue(_list.Contains(100_002));
|
||||
|
||||
var reset1 = new ManualResetEvent(false);
|
||||
var reset2 = new ManualResetEvent(false);
|
||||
var exceptions = 0;
|
||||
|
||||
// Add and remove items from two separate background threads.
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
for (var i = 0; i < 5_000; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
Assert.IsTrue(_list.Remove(500 + i));
|
||||
_list.RemoveAt(100_000);
|
||||
_list.Add(42);
|
||||
_list.Insert(200_000, 100);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Interlocked.Increment(ref exceptions);
|
||||
}
|
||||
}
|
||||
reset1.Set();
|
||||
});
|
||||
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
for (var i = 5_000; i < 10_000; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
Assert.IsTrue(_list.Remove(500 + i));
|
||||
_list.RemoveAt(100_000);
|
||||
_list.Add(42);
|
||||
_list.Insert(200_000, 100);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Interlocked.Increment(ref exceptions);
|
||||
}
|
||||
}
|
||||
reset2.Set();
|
||||
});
|
||||
|
||||
reset1.WaitOne();
|
||||
reset2.WaitOne();
|
||||
|
||||
Assert.IsFalse(_list.Contains(500));
|
||||
Assert.IsFalse(_list.Contains(100_002));
|
||||
// We should have the original count with which we started.
|
||||
Assert.AreEqual(_startCount, _list.Count);
|
||||
Assert.AreEqual(0, exceptions);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user