From 339be3ef782cdc497af65c7ef8db6d86f1e5c1e4 Mon Sep 17 00:00:00 2001 From: Dexrn ZacAttack Date: Sat, 31 Oct 2026 05:25:53 -0700 Subject: [PATCH] chore: bump version for release, handle device disconnect on client --- README.md | 6 +-- .../UniScan.Client.App.csproj | 4 +- .../Device/DeviceInfoControlViewModel.cs | 2 + .../Views/Remote/RemoteRootPageViewModel.cs | 2 +- .../Handler/DeviceInfoPacketHandler.cs | 34 ++++++++++++++++ .../Handler/DeviceStatePacketHandler.cs | 6 +++ .../UnsubscribeFromDevicePacketHandler.cs | 28 +++++++++++++ .../Internal/InternalUniScanClientModule.cs | 3 ++ ...ternalUniScanClientPipelineConfigurator.cs | 2 + .../Remote/Device/RemoteDevice.cs | 25 ++++++++++-- src/Network/UniScan.Network/Constants.cs | 2 +- .../UniScan.Network/Data/Device/DeviceDto.cs | 18 ++++++++- .../{InfoPacket.cs => DeviceInfoPacket.cs} | 10 +++-- .../Device/DeviceUnsubscribePacket.cs | 17 ++++++++ .../Device/GetModelCommandPacket.cs | 12 ------ .../Device/GetVersionCommandPacket.cs | 14 ------- .../Device/SetVolumeCommandPacket.cs | 11 ----- .../UniScan.Server.Core/Host/DeviceHost.cs | 2 +- .../Host/Network/HostClientsHandler.cs | 27 ++++++++++--- .../Handler/DeviceUnsubscribePacketHandler.cs | 40 +++++++++++++++++++ .../Handler/GetDeviceListPacketHandler.cs | 13 ++---- .../Internal/InternalUniScanServerModule.cs | 1 + ...ternalUniScanServerPipelineConfigurator.cs | 1 + .../UniScan.Server.Core.csproj | 4 +- .../Util/DeviceDtoExtensions.cs | 15 +++++++ .../UniScan.Server.Host.csproj | 4 +- 26 files changed, 230 insertions(+), 73 deletions(-) create mode 100644 src/Client/UniScan.Client.Core/Module/Modules/Internal/Handler/DeviceInfoPacketHandler.cs create mode 100644 src/Client/UniScan.Client.Core/Module/Modules/Internal/Handler/UnsubscribeFromDevicePacketHandler.cs rename src/Network/UniScan.Network/Protocol/Packets/Clientbound/Device/{InfoPacket.cs => DeviceInfoPacket.cs} (63%) create mode 100644 src/Network/UniScan.Network/Protocol/Packets/Serverbound/Device/DeviceUnsubscribePacket.cs delete mode 100755 src/Network/UniScan.Network/Protocol/Packets/Serverbound/Device/GetModelCommandPacket.cs delete mode 100755 src/Network/UniScan.Network/Protocol/Packets/Serverbound/Device/GetVersionCommandPacket.cs delete mode 100755 src/Network/UniScan.Network/Protocol/Packets/Serverbound/Device/SetVolumeCommandPacket.cs create mode 100644 src/Server/UniScan.Server.Core/Module/Modules/Internal/Handler/DeviceUnsubscribePacketHandler.cs create mode 100644 src/Server/UniScan.Server.Core/Util/DeviceDtoExtensions.cs diff --git a/README.md b/README.md index c24b804..936e0f0 100644 --- a/README.md +++ b/README.md @@ -158,8 +158,8 @@ Below, you can learn how to setup and install UniScan for your platform. | Name | Version | Description | |:-----------------------------------------------------------------------------------------------:|:-------:|------------------------------------------------| -| [UniScan.Server.Core](src/Server/UniScan.Server.Core) | 0.3.0 | Shared server code | -| [UniScan.Server.Host](src/Server/UniScan.Server.Host) | 0.1.1 | Bootstraps UniScan.Server.Core | +| [UniScan.Server.Core](src/Server/UniScan.Server.Core) | 0.4.0 | Shared server code | +| [UniScan.Server.Host](src/Server/UniScan.Server.Host) | 0.2.0 | Bootstraps UniScan.Server.Core | | [UniScan.Server.Authentication](src/Server/UniScan.Server.Authentication) | 0.0.1 | Common auth code and storage | | [UniScan.Platform](src/Platform/UniScan.Platform) | 0.1.0 | Shared platform abstractions | | [UniScan.Platform.DependencyInjection](src/Platform/UniScan.Platform.DependencyInjection) | 0.0.1 | Adds some DI extensions to UniScan.Platform | @@ -173,7 +173,7 @@ Below, you can learn how to setup and install UniScan for your platform. | [UniScan.Device](src/Core/UniScan.Device) | 0.3.0 | Device abstractions and transport methods | | [UniScan.UserInterface](src/Core/UniScan.UserInterface) | 0.0.1 | Serverside UI definitions | | [UniScan.Client.Core](src/Client/UniScan.Client.Core) | 0.3.0 | Holds shared client code, no dep on Avalonia | -| [UniScan.Client.App](src/Client/UniScan.Client.App) | 0.1.1 | UniScan client | +| [UniScan.Client.App](src/Client/UniScan.Client.App) | 0.2.0 | UniScan client | | [UniScan.Client.App.Platform.Android](src/Client/UniScan.Client.App.Platform.Android) | 0.0.6 | UniScan client for Android | | [UniScan.Client.App.Platform.Browser](src/Client/UniScan.Client.App.Platform.Browser) | 0.1.4 | UniScan client for web browsers (https://uniscan.dexrn.me) | | [UniScan.Client.App.Platform.Desktop](src/Client/UniScan.Client.App.Platform.Desktop) | 0.0.3 | UniScan client for desktop | diff --git a/src/Client/UniScan.Client.App/UniScan.Client.App.csproj b/src/Client/UniScan.Client.App/UniScan.Client.App.csproj index 0243348..37397a9 100755 --- a/src/Client/UniScan.Client.App/UniScan.Client.App.csproj +++ b/src/Client/UniScan.Client.App/UniScan.Client.App.csproj @@ -5,8 +5,8 @@ 14 true UniScanProject - 0.1.1 - 0.1.1 + 0.2.0 + 0.2.0 UniScan client Release App;Debug App AnyCPU diff --git a/src/Client/UniScan.Client.App/Views/Remote/Device/DeviceInfoControlViewModel.cs b/src/Client/UniScan.Client.App/Views/Remote/Device/DeviceInfoControlViewModel.cs index 65c5fcb..7f4f332 100644 --- a/src/Client/UniScan.Client.App/Views/Remote/Device/DeviceInfoControlViewModel.cs +++ b/src/Client/UniScan.Client.App/Views/Remote/Device/DeviceInfoControlViewModel.cs @@ -36,6 +36,8 @@ public DeviceInfoControlViewModel(DeviceViewModel deviceViewModel, IRemoteRootPa else { _logger.Information("No longer subscribed to device '{Identifier}'", deviceViewModel.Device.Identifier); + + _navigator.Navigate(null); } }).AddTo(_subscriptions); } diff --git a/src/Client/UniScan.Client.App/Views/Remote/RemoteRootPageViewModel.cs b/src/Client/UniScan.Client.App/Views/Remote/RemoteRootPageViewModel.cs index 89203e7..960d7c9 100644 --- a/src/Client/UniScan.Client.App/Views/Remote/RemoteRootPageViewModel.cs +++ b/src/Client/UniScan.Client.App/Views/Remote/RemoteRootPageViewModel.cs @@ -62,7 +62,7 @@ public void Dispose() void IRemoteRootPageDeviceNavigatorProxy.Navigate(RemoteDevice? device) { - if (device == null) + if (device == null)//TODO THIS IS SHIT BECAUSE THIS WILL SWITCH THE USER OUT OF WHATEVER PAGE REGARDLESS WHEN THE DEVICE IS UNSUBSCRIBED BY THE SERVER!!!!! WE DONT WANT SUCH SPECIFIC SHIT LIKE THIS BUT RUSHING THIS { Dispatcher.UIThread.Post(() => { CurrentSubpage = (ObservableObject?)_mainPage ?? _notConnectedPage; }); return; diff --git a/src/Client/UniScan.Client.Core/Module/Modules/Internal/Handler/DeviceInfoPacketHandler.cs b/src/Client/UniScan.Client.Core/Module/Modules/Internal/Handler/DeviceInfoPacketHandler.cs new file mode 100644 index 0000000..a20b4bc --- /dev/null +++ b/src/Client/UniScan.Client.Core/Module/Modules/Internal/Handler/DeviceInfoPacketHandler.cs @@ -0,0 +1,34 @@ +using DotNetty.Transport.Channels; +using ObservableCollections; +using Serilog; +using Shiki.Common.Identity; +using UniScan.Client.Core.Remote; +using UniScan.Client.Core.Remote.Device; +using UniScan.Core.State; +using UniScan.Core.State.Node; +using UniScan.Network.Protocol.Packets.Clientbound.Device; + +namespace UniScan.Client.Core.Module.Modules.Internal.Handler; + +public class DeviceInfoPacketHandler : SimpleChannelInboundHandler +{ + protected override void ChannelRead0(IChannelHandlerContext ctx, DeviceInfoPacket msg) + { + RemoteServer serv = ctx.Channel.GetAttribute(ServerAttributes.ServerAttribute).Get(); + + Log.Information("Received device info for {Identifier}: {Device}", msg.Device.DeviceIdentifier, msg.Device); + if (!serv.Devices.TryGetValue(msg.DeviceIdentifier, out RemoteDevice? device)) + { + serv.Devices[msg.DeviceIdentifier] = RemoteDevice.FromDto(msg.Device, serv); + return; + } + + //TODO better way than this to apply diffs? + if (!msg.Device.Connected && device.Subscribed.Value) + { + _ = device.UnsubscribeAsync(true); + } + + device.UpdateFrom(msg.Device); + } +} \ No newline at end of file diff --git a/src/Client/UniScan.Client.Core/Module/Modules/Internal/Handler/DeviceStatePacketHandler.cs b/src/Client/UniScan.Client.Core/Module/Modules/Internal/Handler/DeviceStatePacketHandler.cs index f693ef5..9427bc3 100644 --- a/src/Client/UniScan.Client.Core/Module/Modules/Internal/Handler/DeviceStatePacketHandler.cs +++ b/src/Client/UniScan.Client.Core/Module/Modules/Internal/Handler/DeviceStatePacketHandler.cs @@ -20,6 +20,12 @@ protected override void ChannelRead0(IChannelHandlerContext ctx, StatePacket msg foreach (var state in msg.States) { + if (state.Value is null) + { + device.States.Remove(state.Key); + continue; + } + device.States[state.Key] = state.Value; } } diff --git a/src/Client/UniScan.Client.Core/Module/Modules/Internal/Handler/UnsubscribeFromDevicePacketHandler.cs b/src/Client/UniScan.Client.Core/Module/Modules/Internal/Handler/UnsubscribeFromDevicePacketHandler.cs new file mode 100644 index 0000000..210630a --- /dev/null +++ b/src/Client/UniScan.Client.Core/Module/Modules/Internal/Handler/UnsubscribeFromDevicePacketHandler.cs @@ -0,0 +1,28 @@ +using DotNetty.Transport.Channels; +using ObservableCollections; +using Serilog; +using Shiki.Common.Identity; +using UniScan.Client.Core.Remote; +using UniScan.Client.Core.Remote.Device; +using UniScan.Core.State; +using UniScan.Core.State.Node; +using UniScan.Network.Protocol.Packets.Clientbound.Device; + +namespace UniScan.Client.Core.Module.Modules.Internal.Handler; + +public class UnsubscribeFromDevicePacketHandler : SimpleChannelInboundHandler +{ + protected override void ChannelRead0(IChannelHandlerContext ctx, UnsubscribeFromDevicePacket msg) + { + RemoteServer serv = ctx.Channel.GetAttribute(ServerAttributes.ServerAttribute).Get(); + + if (!serv.Devices.TryGetValue(msg.DeviceIdentifier, out RemoteDevice? device)) + { + Log.Warning("Received device unsubscribe packet for nonexistent device '{Identifier}'", msg.DeviceIdentifier); + return; + } + + Log.Information("Received device unsubscribe packet for device '{Identifier}'", msg.DeviceIdentifier); + _ = device.UnsubscribeAsync(false); + } +} \ No newline at end of file diff --git a/src/Client/UniScan.Client.Core/Module/Modules/Internal/InternalUniScanClientModule.cs b/src/Client/UniScan.Client.Core/Module/Modules/Internal/InternalUniScanClientModule.cs index 29c3c09..81ff261 100644 --- a/src/Client/UniScan.Client.Core/Module/Modules/Internal/InternalUniScanClientModule.cs +++ b/src/Client/UniScan.Client.Core/Module/Modules/Internal/InternalUniScanClientModule.cs @@ -23,6 +23,9 @@ public void ConfigureDi(IServiceCollection services) services.AddTransient(); services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddSingleton(); } diff --git a/src/Client/UniScan.Client.Core/Module/Modules/Internal/InternalUniScanClientPipelineConfigurator.cs b/src/Client/UniScan.Client.Core/Module/Modules/Internal/InternalUniScanClientPipelineConfigurator.cs index 453502e..7b94248 100644 --- a/src/Client/UniScan.Client.Core/Module/Modules/Internal/InternalUniScanClientPipelineConfigurator.cs +++ b/src/Client/UniScan.Client.Core/Module/Modules/Internal/InternalUniScanClientPipelineConfigurator.cs @@ -19,5 +19,7 @@ public void ConfigureHandlers(IChannelPipeline pipeline) pipeline.AddLast(nameof(DisconnectPacketHandler), serviceProvider.GetRequiredService()); pipeline.AddLast(nameof(RemoteInfoPacketHandler), serviceProvider.GetRequiredService()); pipeline.AddLast(nameof(DeviceStatePacketHandler), serviceProvider.GetRequiredService()); + pipeline.AddLast(nameof(DeviceInfoPacketHandler), serviceProvider.GetRequiredService()); + pipeline.AddLast(nameof(UnsubscribeFromDevicePacketHandler), serviceProvider.GetRequiredService()); } } \ No newline at end of file diff --git a/src/Client/UniScan.Client.Core/Remote/Device/RemoteDevice.cs b/src/Client/UniScan.Client.Core/Remote/Device/RemoteDevice.cs index f7c16c8..a33e834 100644 --- a/src/Client/UniScan.Client.Core/Remote/Device/RemoteDevice.cs +++ b/src/Client/UniScan.Client.Core/Remote/Device/RemoteDevice.cs @@ -8,6 +8,7 @@ using UniScan.Network.Data; using UniScan.Network.Data.Device; using UniScan.Network.Protocol.Packets.Bidirectional.Status; +using UniScan.Network.Protocol.Packets.Serverbound.Device; using UniScan.Network.Protocol.Packets.Serverbound.Subscription; namespace UniScan.Client.Core.Remote.Device; @@ -16,17 +17,17 @@ public class RemoteDevice(Slug id, string? name, bool connec { public Slug Identifier { get; } = id; - public string? DisplayName { get; } = name; + public string? DisplayName { get; private set; } = name; public string Name => DisplayName ?? Identifier.Value; - public bool Connected { get; } = connected; + public bool Connected { get; private set; } = connected; public RemoteServer Parent { get; } = parent; private readonly BindableReactiveProperty _subscribed = new(false); public IReadOnlyBindableReactiveProperty Subscribed => _subscribed; - public DeviceSpecifications? Specs { get; } = specs; + public DeviceSpecifications? Specs { get; private set; } = specs; public ObservableDictionary States { get; set; } = []; @@ -40,8 +41,26 @@ public async ValueTask> Subscribe() return !res.Value.Result.IsSuccessful ? new BooleanResult(res.Value.Result.Error) : BooleanResult.Successful; } + public async Task UnsubscribeAsync(bool notify)//todo this is just shit + { + if (notify) + { + await parent.Socket.SendRequestAsync(DeviceUnsubscribePacket.CreateRequest(Identifier)); + } + + _subscribed.Value = false; + } + public void Dispose() { _subscribed.Dispose(); } + + //TODO there has to be better way than this but rushing... + public void UpdateFrom(DeviceDto dto) + { + DisplayName = dto.DisplayName; + Connected = dto.Connected; + Specs = dto.Specs; + } } \ No newline at end of file diff --git a/src/Network/UniScan.Network/Constants.cs b/src/Network/UniScan.Network/Constants.cs index eb7a3b3..cccda65 100755 --- a/src/Network/UniScan.Network/Constants.cs +++ b/src/Network/UniScan.Network/Constants.cs @@ -2,6 +2,6 @@ namespace UniScan.Network; public class Constants { - public const int ProtocolVersion = 10; + public const int ProtocolVersion = 11; public const ushort DefaultPort = 'U' | ('S' << 2) | ('C' << 4) | ('N' << 6);//This packing really means nothing other than being an attempt at getting a nice port number } \ No newline at end of file diff --git a/src/Network/UniScan.Network/Data/Device/DeviceDto.cs b/src/Network/UniScan.Network/Data/Device/DeviceDto.cs index 6a82ffb..56d8e15 100644 --- a/src/Network/UniScan.Network/Data/Device/DeviceDto.cs +++ b/src/Network/UniScan.Network/Data/Device/DeviceDto.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; using MessagePack; using Semver; +using Shiki.Common.Identity; using Shiki.Common.Identity.Slug; using Shiki.Common.Identity.Slug.Formatting.Formatters; using UniScan.Network.Formatter.Semver; @@ -17,6 +18,19 @@ public record DeviceSpecifications( public record DeviceDto( [property: Key(0)] Slug DeviceIdentifier, [property: Key(1)] string? DisplayName, - [property: Key(2), MemberNotNullWhen(true, nameof(DeviceDto.Specs))] bool Connected, + [property: Key(2), MemberNotNullWhen(true, nameof(DeviceDto.Specs))] + bool Connected, [property: Key(3)] DeviceSpecifications? Specs -); \ No newline at end of file +) +{ + public static DeviceDto FromDevice(Slug deviceId, UniScan.Device.Device.Device device, string? displayName) + { + DeviceSpecifications? info = null; + if (device.DeviceInfo != null) + { + info = new DeviceSpecifications(device.DeviceInfo.Model, device.DeviceInfo.Version); + } + + return new DeviceDto(deviceId, displayName, device.Active.Value, info); + } +} \ No newline at end of file diff --git a/src/Network/UniScan.Network/Protocol/Packets/Clientbound/Device/InfoPacket.cs b/src/Network/UniScan.Network/Protocol/Packets/Clientbound/Device/DeviceInfoPacket.cs similarity index 63% rename from src/Network/UniScan.Network/Protocol/Packets/Clientbound/Device/InfoPacket.cs rename to src/Network/UniScan.Network/Protocol/Packets/Clientbound/Device/DeviceInfoPacket.cs index f9bfcb6..e17b74c 100644 --- a/src/Network/UniScan.Network/Protocol/Packets/Clientbound/Device/InfoPacket.cs +++ b/src/Network/UniScan.Network/Protocol/Packets/Clientbound/Device/DeviceInfoPacket.cs @@ -1,3 +1,4 @@ +using MessagePack; using Shiki.Common.Identity.Slug; using Shiki.Common.Identity.Slug.Formatting.Formatters; using UniScan.Network.Data.Device; @@ -7,8 +8,9 @@ namespace UniScan.Network.Protocol.Packets.Clientbound.Device; [RegistryPacket("UniScan", "packet", "clientbound", "device", "info")] -public readonly record struct InfoPacket( - Slug DeviceIdentifier, - DeviceDto Device, - Guid? RequestId +[MessagePackObject] +public readonly record struct DeviceInfoPacket( + [property: Key(0)] Slug DeviceIdentifier, + [property: Key(1)] DeviceDto Device, + [property: Key(2)] Guid? RequestId ) : IClientboundPacket, IResponsePayloadPart, ISelectedDevicePayloadPart; \ No newline at end of file diff --git a/src/Network/UniScan.Network/Protocol/Packets/Serverbound/Device/DeviceUnsubscribePacket.cs b/src/Network/UniScan.Network/Protocol/Packets/Serverbound/Device/DeviceUnsubscribePacket.cs new file mode 100644 index 0000000..12504e8 --- /dev/null +++ b/src/Network/UniScan.Network/Protocol/Packets/Serverbound/Device/DeviceUnsubscribePacket.cs @@ -0,0 +1,17 @@ +using MessagePack; +using Shiki.Common.Identity.Slug; +using Shiki.Common.Identity.Slug.Formatting.Formatters; +using UniScan.Network.Protocol.Packets.Bidirectional.Status; +using UniScan.Network.Protocol.PayloadPart; +using UniScan.Network.Registry; +using UniScan.Network.Request; + +namespace UniScan.Network.Protocol.Packets.Serverbound.Device; + +[RegistryPacket("UniScan", "packet", "serverbound", "device", "client_unsubscribed")] +[MessagePackObject] +[method: RequestConstructor] +public partial record DeviceUnsubscribePacket( + [property: Key(0)] Slug DeviceIdentifier, + [property: Key(1)] Guid? RequestId +) : IServerboundPacket, IRequestPayloadPart; \ No newline at end of file diff --git a/src/Network/UniScan.Network/Protocol/Packets/Serverbound/Device/GetModelCommandPacket.cs b/src/Network/UniScan.Network/Protocol/Packets/Serverbound/Device/GetModelCommandPacket.cs deleted file mode 100755 index d32e9de..0000000 --- a/src/Network/UniScan.Network/Protocol/Packets/Serverbound/Device/GetModelCommandPacket.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Shiki.Common.Identity.Slug; -using Shiki.Common.Identity.Slug.Formatting.Formatters; -using UniScan.Network.Protocol.Packets.Clientbound.Device; -using UniScan.Network.Protocol.PayloadPart; -using UniScan.Network.Registry; -using UniScan.Network.Request; - -namespace UniScan.Network.Protocol.Packets.Serverbound.Device; - -[RegistryPacket("UniScan", "packet", "serverbound", "device", "get_model")] -[method: RequestConstructor] -public partial record GetModelCommandPacket(Slug DeviceIdentifier, Guid? RequestId) : IServerboundPacket, ISelectedDevicePayloadPart, IRequiresAuthenticationPayloadPart, IRequestPayloadPart; \ No newline at end of file diff --git a/src/Network/UniScan.Network/Protocol/Packets/Serverbound/Device/GetVersionCommandPacket.cs b/src/Network/UniScan.Network/Protocol/Packets/Serverbound/Device/GetVersionCommandPacket.cs deleted file mode 100755 index 110706e..0000000 --- a/src/Network/UniScan.Network/Protocol/Packets/Serverbound/Device/GetVersionCommandPacket.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Shiki.Common.Identity.Slug; -using Shiki.Common.Identity.Slug.Formatting.Formatters; -using UniScan.Network.Protocol.Packets.Clientbound.Device; -using UniScan.Network.Protocol.PayloadPart; -using UniScan.Network.Registry; -using UniScan.Network.Request; -using UniScan.Network.User.Permission; - -namespace UniScan.Network.Protocol.Packets.Serverbound.Device; - -[RegistryPacket("UniScan", "packet", "serverbound", "device", "get_version")] -[RequiredHandlerPermission("UniScan", "permission", "device", "get_version")] -[method: RequestConstructor] -public partial record GetVersionCommandPacket(Slug DeviceIdentifier, Guid? RequestId) : IServerboundPacket, ISelectedDevicePayloadPart, IRequiresAuthenticationPayloadPart, IRequestPayloadPart; \ No newline at end of file diff --git a/src/Network/UniScan.Network/Protocol/Packets/Serverbound/Device/SetVolumeCommandPacket.cs b/src/Network/UniScan.Network/Protocol/Packets/Serverbound/Device/SetVolumeCommandPacket.cs deleted file mode 100755 index c4d2e6d..0000000 --- a/src/Network/UniScan.Network/Protocol/Packets/Serverbound/Device/SetVolumeCommandPacket.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Shiki.Common.Identity.Slug; -using Shiki.Common.Identity.Slug.Formatting.Formatters; -using UniScan.Network.Protocol.PayloadPart; -using UniScan.Network.Registry; -using UniScan.Network.User.Permission; - -namespace UniScan.Network.Protocol.Packets.Serverbound.Device; - -[RegistryPacket("UniScan", "packet", "serverbound", "device", "set_volume")] -[RequiredHandlerPermission("UniScan", "permission", "device", "set_volume")] -public record SetVolumeCommandPacket(int Volume, Slug DeviceIdentifier) : IServerboundPacket, ISelectedDevicePayloadPart, IRequiresAuthenticationPayloadPart; \ No newline at end of file diff --git a/src/Server/UniScan.Server.Core/Host/DeviceHost.cs b/src/Server/UniScan.Server.Core/Host/DeviceHost.cs index 105ce4d..c5a3e4d 100755 --- a/src/Server/UniScan.Server.Core/Host/DeviceHost.cs +++ b/src/Server/UniScan.Server.Core/Host/DeviceHost.cs @@ -40,7 +40,7 @@ public DeviceHost(Slug identifier, string? displayName, Devi this.Logger = Log.ForContext("SourceContext", ToString()); - NetworkClients = new HostClientsHandler(Identifier, Device); + NetworkClients = new HostClientsHandler(Identifier, Device, this); } public async Task StartAsync(CancellationToken ct = default) diff --git a/src/Server/UniScan.Server.Core/Host/Network/HostClientsHandler.cs b/src/Server/UniScan.Server.Core/Host/Network/HostClientsHandler.cs index 4cd5b65..61f4206 100644 --- a/src/Server/UniScan.Server.Core/Host/Network/HostClientsHandler.cs +++ b/src/Server/UniScan.Server.Core/Host/Network/HostClientsHandler.cs @@ -10,8 +10,11 @@ using UniScan.Core.State; using UniScan.Core.State.Node; using UniScan.Device.Device; +using UniScan.Network.Data.Device; using UniScan.Network.Protocol.Packets.Clientbound.Device; +using UniScan.Network.Protocol.Packets.Serverbound; using UniScan.Network.Server; +using UniScan.Server.Core.Util; namespace UniScan.Server.Core.Host.Network; @@ -19,6 +22,8 @@ public sealed class HostClientsHandler : IDisposable, IAsyncDisposable { private readonly Slug _deviceId; private readonly Device.Device.Device _device; + private readonly DeviceHost _host; + private IDeviceState? _currentState; private Dictionary _currentStates = []; @@ -26,15 +31,20 @@ public sealed class HostClientsHandler : IDisposable, IAsyncDisposable private readonly CompositeDisposable _disposables = new(); - public HostClientsHandler(Slug deviceId, Device.Device.Device device) + public HostClientsHandler(Slug deviceId, Device.Device.Device device, DeviceHost host) { _deviceId = deviceId; _device = device; + _host = host; _device.State.AsObservable().DistinctUntilChanged().Subscribe(HandleState).AddTo(_disposables); _device.Active.AsObservable().DistinctUntilChanged().Subscribe(HandleActiveChanged).AddTo(_disposables); } + public HostClientsHandler(DeviceHost host) : this(host.Identifier, host.Device, host) + { + } + private void HandleState(IDeviceState? state) { _currentState = state; @@ -73,15 +83,17 @@ private void HandleState(IDeviceState? state) public void HandleActiveChanged(bool active) { - if (!active) + foreach ((IChannelId id, IChannel channel) in _subscribers.Channels) { - foreach ((IChannelId id, IChannel channel) in _subscribers.Channels) + if (!active) { _subscribers.Remove(id); channel.WriteAndFlushAsync(new UnsubscribeFromDevicePacket(_deviceId, new TranslatableTextComponent(Identifier - .CreateInstance("UniScan:i18n/device/disconnected_inactive")))); + .CreateInstance("UniScan:i18n/device/disconnected_inactive")))); } + + channel.WriteAndFlushAsync(new DeviceInfoPacket(_deviceId, DeviceDto.FromDeviceHost(_host), null));//TODO this should probably all be handled in a different way because this is only writing info when device turns off/is disconnected } } @@ -95,7 +107,12 @@ public void AddClient(IChannel channel) channel.WriteAndFlushAsync(new StatePacket(_currentStates.ToDictionary(kvp => kvp.Key, IStateNode? (kvp) => kvp.Value), null, _deviceId)); } - } + } + + public void RemoveClient(IChannel channel) + { + _subscribers.Remove(channel); + } public bool Contains(IChannel channel) => _subscribers.Contains(channel); diff --git a/src/Server/UniScan.Server.Core/Module/Modules/Internal/Handler/DeviceUnsubscribePacketHandler.cs b/src/Server/UniScan.Server.Core/Module/Modules/Internal/Handler/DeviceUnsubscribePacketHandler.cs new file mode 100644 index 0000000..dd4851a --- /dev/null +++ b/src/Server/UniScan.Server.Core/Module/Modules/Internal/Handler/DeviceUnsubscribePacketHandler.cs @@ -0,0 +1,40 @@ +using DotNetty.Transport.Channels; +using Shiki.Common.Identity; +using Shiki.Common.Result; +using UniScan.Network.Data; +using UniScan.Network.Data.Device; +using UniScan.Network.Protocol.Packets.Bidirectional.Status; +using UniScan.Network.Protocol.Packets.Clientbound; +using UniScan.Network.Protocol.Packets.Serverbound; +using UniScan.Network.Protocol.Packets.Serverbound.Device; +using UniScan.Server.Core.Host; + +namespace UniScan.Server.Core.Module.Modules.Internal.Handler; + +public class DeviceUnsubscribePacketHandler(DeviceHostManager deviceHostManager) : SimpleChannelInboundHandler +{ + private readonly DeviceHostManager _deviceHostManager = deviceHostManager; + + protected override void ChannelRead0(IChannelHandlerContext ctx, DeviceUnsubscribePacket msg) + { + if (msg.RequestId == null) + return; + + if (!_deviceHostManager.Devices.TryGetValue(msg.DeviceIdentifier, out DeviceHost? device)) + { + //TODO codegen to generate WriteAndFlushPacketAsync for each packet variation + ctx.WriteAndFlushAsync(new AcknowledgePacket(new BooleanResult(new KeyNotFoundException()), + msg.RequestId)); + return; + } + + if (!device.NetworkClients.Contains(ctx.Channel)) + { + ctx.WriteAndFlushAsync(new AcknowledgePacket(new BooleanResult(new InvalidOperationException("Not subscribed to this device")), msg.RequestId)); + return; + } + + device.NetworkClients.RemoveClient(ctx.Channel); + ctx.WriteAndFlushAsync(new AcknowledgePacket(TransportableBooleanResult.Successful, msg.RequestId)); + } +} \ No newline at end of file diff --git a/src/Server/UniScan.Server.Core/Module/Modules/Internal/Handler/GetDeviceListPacketHandler.cs b/src/Server/UniScan.Server.Core/Module/Modules/Internal/Handler/GetDeviceListPacketHandler.cs index 6c3a079..1ae6956 100644 --- a/src/Server/UniScan.Server.Core/Module/Modules/Internal/Handler/GetDeviceListPacketHandler.cs +++ b/src/Server/UniScan.Server.Core/Module/Modules/Internal/Handler/GetDeviceListPacketHandler.cs @@ -6,6 +6,7 @@ using UniScan.Network.Protocol.Packets.Clientbound; using UniScan.Network.Protocol.Packets.Serverbound; using UniScan.Server.Core.Host; +using UniScan.Server.Core.Util; namespace UniScan.Server.Core.Module.Modules.Internal.Handler; @@ -22,16 +23,8 @@ protected override void ChannelRead0(IChannelHandlerContext ctx, GetDeviceListPa if (msg.RequestId == null) return; - var devices = _deviceHostManager.Devices.ToDictionary(i => i.Key, h => - { - DeviceSpecifications? info = null; - if (h.Value.Device.DeviceInfo != null) - { - info = new DeviceSpecifications(h.Value.Device.DeviceInfo.Model, h.Value.Device.DeviceInfo.Version); - } - - return new DeviceDto(h.Key, h.Value.DisplayName, h.Value.Device.Active.Value, info); - }); + var devices = _deviceHostManager.Devices.ToDictionary(i => i.Key, h => DeviceDto.FromDeviceHost(h.Value)); + ctx.WriteAndFlushAsync(new DeviceListPacket(devices, msg.RequestId)); } } \ No newline at end of file diff --git a/src/Server/UniScan.Server.Core/Module/Modules/Internal/InternalUniScanServerModule.cs b/src/Server/UniScan.Server.Core/Module/Modules/Internal/InternalUniScanServerModule.cs index 54957fb..5545fff 100644 --- a/src/Server/UniScan.Server.Core/Module/Modules/Internal/InternalUniScanServerModule.cs +++ b/src/Server/UniScan.Server.Core/Module/Modules/Internal/InternalUniScanServerModule.cs @@ -52,6 +52,7 @@ public void ConfigureDi(IServiceCollection services) services.AddSingleton(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/src/Server/UniScan.Server.Core/Module/Modules/Internal/InternalUniScanServerPipelineConfigurator.cs b/src/Server/UniScan.Server.Core/Module/Modules/Internal/InternalUniScanServerPipelineConfigurator.cs index 468d7c7..1f11f70 100644 --- a/src/Server/UniScan.Server.Core/Module/Modules/Internal/InternalUniScanServerPipelineConfigurator.cs +++ b/src/Server/UniScan.Server.Core/Module/Modules/Internal/InternalUniScanServerPipelineConfigurator.cs @@ -30,5 +30,6 @@ public void ConfigureHandlers(IChannelPipeline pipeline) pipeline.AddLast(nameof(SubscribePacketHandler), _serviceProvider.GetRequiredService()); pipeline.AddLast(nameof(GetDeviceListPacketHandler), _serviceProvider.GetRequiredService()); pipeline.AddLast(nameof(DisconnectPacketHandler), _serviceProvider.GetRequiredService()); + pipeline.AddLast(nameof(DeviceUnsubscribePacketHandler), _serviceProvider.GetRequiredService()); } } \ No newline at end of file diff --git a/src/Server/UniScan.Server.Core/UniScan.Server.Core.csproj b/src/Server/UniScan.Server.Core/UniScan.Server.Core.csproj index 65fc779..97ad022 100755 --- a/src/Server/UniScan.Server.Core/UniScan.Server.Core.csproj +++ b/src/Server/UniScan.Server.Core/UniScan.Server.Core.csproj @@ -9,8 +9,8 @@ UniScan.Server.Core UniScan.Server.Core UniScanProject - 0.3.0 - 0.3.0 + 0.4.0 + 0.4.0 Shared server code Debug Server;Release Server AnyCPU diff --git a/src/Server/UniScan.Server.Core/Util/DeviceDtoExtensions.cs b/src/Server/UniScan.Server.Core/Util/DeviceDtoExtensions.cs new file mode 100644 index 0000000..12b2bee --- /dev/null +++ b/src/Server/UniScan.Server.Core/Util/DeviceDtoExtensions.cs @@ -0,0 +1,15 @@ +using System.Runtime.CompilerServices; +using UniScan.Network.Data.Device; +using UniScan.Server.Core.Host; + +namespace UniScan.Server.Core.Util; + +public static class DeviceDtoExtensions +{ + extension(DeviceDto) + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static DeviceDto FromDeviceHost(DeviceHost host) => + DeviceDto.FromDevice(host.Identifier, host.Device, host.DisplayName); + } +} \ No newline at end of file diff --git a/src/Server/UniScan.Server.Host/UniScan.Server.Host.csproj b/src/Server/UniScan.Server.Host/UniScan.Server.Host.csproj index d8c4d5d..e088060 100644 --- a/src/Server/UniScan.Server.Host/UniScan.Server.Host.csproj +++ b/src/Server/UniScan.Server.Host/UniScan.Server.Host.csproj @@ -6,8 +6,8 @@ enable enable UniScanProject - 0.1.1 - 0.1.1 + 0.2.0 + 0.2.0 Bootstraps UniScan.Server.Core Debug Server;Release Server AnyCPU