diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..bc9372b --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,13 @@ + + + Server + + + + Client + + + + App + + \ No newline at end of file diff --git a/README.md b/README.md index 9fe23d1..ea4a1bb 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,10 @@ it up to the internet for others to listen to, and control it yourself by loggin | [UniScan.Client.App.Platform.Desktop](src/Client/UniScan.Client.App.Platform.Desktop) | 0.0.3 | UniScan client for desktop | | [UniScan.Client.App.Platform.iOS](src/Client/UniScan.Client.App.Platform.iOS) | 0.0.1 | UniScan client for iOS | | [UniScan.Modules.DebugModule](src/Modules/UniScan.Modules.DebugModule) | 0.0.1 | | -| [UniScan.Modules.LibUvNetworkGroupModule](src/Modules/UniScan.Modules.LibUvNetworkGroupModule) | | | +| [UniScan.Modules.LibUvNetworkGroupModule](src/Modules/UniScan.Modules.LibUvNetworkGroupModule) | 1.0.1 | | +| [UniScan.Modules.WebSocketTransportMethod](src/Modules/UniScan.Modules.WebSocketTransportMethod) | 1.0.0 | | +| [UniScan.Audio](src/Audio/UniScan.Audio) | | | +| [UniScan.Audio.Server](src/Audio/UniScan.Audio.Server) | | | ## Credits (in the future, these will be included in the About page.) diff --git a/UniScan.slnx b/UniScan.slnx index 7200909..5c40cde 100644 --- a/UniScan.slnx +++ b/UniScan.slnx @@ -1,13 +1,48 @@ + + + + + + + + + + + + + + + + + - - + + + + + + + + - - - - + + + + + + + + + + + + + + + + @@ -46,12 +81,19 @@ + - + + + + - + + + + @@ -61,9 +103,18 @@ - - - + + + + + + + + + + + + diff --git a/external/Shiki b/external/Shiki index 26150a6..90eef0b 160000 --- a/external/Shiki +++ b/external/Shiki @@ -1 +1 @@ -Subproject commit 26150a6c8d62065053ff54f321069ecf81353e23 +Subproject commit 90eef0b2739c009eab63fe61effde6bf9cbf7793 diff --git a/src/Audio/UniScan.Audio.Server/AudioHostSocket.cs b/src/Audio/UniScan.Audio.Server/AudioHostSocket.cs new file mode 100755 index 0000000..8dbc407 --- /dev/null +++ b/src/Audio/UniScan.Audio.Server/AudioHostSocket.cs @@ -0,0 +1,65 @@ +using System.Collections.Immutable; +using DotNetty.Transport.Bootstrapping; +using DotNetty.Transport.Channels; +using Serilog; +using UniScan.Network; +using UniScan.Network.Group; +using UniScan.Network.Socket; + +namespace UniScan.Audio.Server; + +public class AudioHostSocket( + UniScanChannelInitializer channelInitializer, + IEnumerable methods, + IHostNetworkGroupManager groupManager) + : ISocket +{ + public ILogger Logger { get; } = Log.ForContext(); + + public UniScanChannelInitializer ChannelInitializer { get; } = channelInitializer; + + private readonly ImmutableList _hostMethods = [.. methods]; + private readonly List _channels = []; + + public async Task StartAsync() + { + if (_hostMethods.IsEmpty) + { + throw new Exception("No host methods found"); + } + + try + { + var task = _hostMethods.Select(async m => + { + IChannel c = await m.BindAsync(groupManager, ChannelInitializer); + Logger.Information("Audio hosting on transport {Transport}", m.ToDisplayString()); + + return c; + }); + + _channels.AddRange(await Task.WhenAll(task)); + } + catch (Exception ex) + { + Logger.Error(ex, "Failed to start audio host"); + await StopAsync(); + } + } + + public async Task StopAsync() + { + var t = _channels.Select(c => c.CloseAsync()); + await Task.WhenAll(t); + + _channels.Clear(); + } + + public async Task SendPacketAsync(IChannel? client, IPacket packet) + { + if (client is not { Active: true }) return false; + + await client.WriteAndFlushAsync(packet); + return true; + } +} \ No newline at end of file diff --git a/src/Audio/UniScan.Audio.Server/IAudioHostMethod.cs b/src/Audio/UniScan.Audio.Server/IAudioHostMethod.cs new file mode 100644 index 0000000..ecabafb --- /dev/null +++ b/src/Audio/UniScan.Audio.Server/IAudioHostMethod.cs @@ -0,0 +1,13 @@ +using DotNetty.Transport.Bootstrapping; +using DotNetty.Transport.Channels; +using UniScan.Network.Group; +using UniScan.Network.Socket; + +namespace UniScan.Audio.Server; + +public interface IAudioHostMethod +{ + Task BindAsync(IHostNetworkGroupManager networkGroupManager, UniScanChannelInitializer channelInitializer); + + string ToDisplayString(); +} \ No newline at end of file diff --git a/src/Audio/UniScan.Audio.Server/UniScan.Audio.Server.csproj b/src/Audio/UniScan.Audio.Server/UniScan.Audio.Server.csproj new file mode 100644 index 0000000..24f36dd --- /dev/null +++ b/src/Audio/UniScan.Audio.Server/UniScan.Audio.Server.csproj @@ -0,0 +1,15 @@ + + + + net10.0 + enable + enable + Debug;Release;Debug Server;Debug Client;Release Server;Release Client;Release App;Debug App + AnyCPU + + + + + + + diff --git a/src/Audio/UniScan.Audio/UniScan.Audio.csproj b/src/Audio/UniScan.Audio/UniScan.Audio.csproj new file mode 100644 index 0000000..576f4f4 --- /dev/null +++ b/src/Audio/UniScan.Audio/UniScan.Audio.csproj @@ -0,0 +1,11 @@ + + + + net10.0 + enable + enable + Debug;Release;Debug Server;Debug Client;Release Server;Release Client;Release App;Debug App + AnyCPU + + + diff --git a/src/Client/UniScan.Client.App.Platform.Android/UniScan.Client.App.Platform.Android.csproj b/src/Client/UniScan.Client.App.Platform.Android/UniScan.Client.App.Platform.Android.csproj index 638c231..afe1c40 100755 --- a/src/Client/UniScan.Client.App.Platform.Android/UniScan.Client.App.Platform.Android.csproj +++ b/src/Client/UniScan.Client.App.Platform.Android/UniScan.Client.App.Platform.Android.csproj @@ -14,6 +14,8 @@ 2 $(AssemblyVersion) UniScan client for Android + Release App;Debug App + AnyCPU diff --git a/src/Client/UniScan.Client.App.Platform.Browser/UniScan.Client.App.Platform.Browser.csproj b/src/Client/UniScan.Client.App.Platform.Browser/UniScan.Client.App.Platform.Browser.csproj index 3040520..d6cd884 100755 --- a/src/Client/UniScan.Client.App.Platform.Browser/UniScan.Client.App.Platform.Browser.csproj +++ b/src/Client/UniScan.Client.App.Platform.Browser/UniScan.Client.App.Platform.Browser.csproj @@ -22,6 +22,8 @@ UniScan Web 0.1.4 0.1.4 + Release App;Debug App + AnyCPU @@ -39,6 +41,7 @@ + diff --git a/src/Client/UniScan.Client.App.Platform.Desktop/UniScan.Client.App.Platform.Desktop.csproj b/src/Client/UniScan.Client.App.Platform.Desktop/UniScan.Client.App.Platform.Desktop.csproj index 96eafa9..04f8ab3 100755 --- a/src/Client/UniScan.Client.App.Platform.Desktop/UniScan.Client.App.Platform.Desktop.csproj +++ b/src/Client/UniScan.Client.App.Platform.Desktop/UniScan.Client.App.Platform.Desktop.csproj @@ -10,6 +10,8 @@ 0.0.3 0.0.3 UniScan client for desktop + Release App;Debug App + AnyCPU diff --git a/src/Client/UniScan.Client.App.Platform.iOS/UniScan.Client.App.Platform.iOS.csproj b/src/Client/UniScan.Client.App.Platform.iOS/UniScan.Client.App.Platform.iOS.csproj index 9b2b123..ea7e533 100755 --- a/src/Client/UniScan.Client.App.Platform.iOS/UniScan.Client.App.Platform.iOS.csproj +++ b/src/Client/UniScan.Client.App.Platform.iOS/UniScan.Client.App.Platform.iOS.csproj @@ -7,6 +7,7 @@ UniScan client for iOS 0.0.1 0.0.1 + Release App;Debug App diff --git a/src/Client/UniScan.Client.App/Core/EndPointParserValidator.cs b/src/Client/UniScan.Client.App/Core/EndPointParserValidator.cs new file mode 100644 index 0000000..770180b --- /dev/null +++ b/src/Client/UniScan.Client.App/Core/EndPointParserValidator.cs @@ -0,0 +1,19 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using UniScan.Client.App.UI.Validation; + +namespace UniScan.Client.App.Core; + +public abstract class EndpointValidatorParser : IValidatorParser +{ + public static bool Validate([NotNullWhen(true)] string? s, IFormatProvider? provider) + { + if (string.IsNullOrWhiteSpace(s) || !Uri.TryCreate("shit://" + s, UriKind.Absolute, out Uri? uri)) + return false; + + if (uri.Port == 0 || string.IsNullOrWhiteSpace(uri.Host)) + return false; + + return uri.HostNameType is UriHostNameType.Dns or UriHostNameType.IPv4 or UriHostNameType.IPv6; + } +} \ No newline at end of file diff --git a/src/Client/UniScan.Client.App/UI/Validation/ParsableValidatorAttribute.cs b/src/Client/UniScan.Client.App/UI/Validation/ParsableValidatorAttribute.cs index 1011c70..fd6958d 100644 --- a/src/Client/UniScan.Client.App/UI/Validation/ParsableValidatorAttribute.cs +++ b/src/Client/UniScan.Client.App/UI/Validation/ParsableValidatorAttribute.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; namespace UniScan.Client.App.UI.Validation; @@ -13,6 +14,25 @@ public class ParsableValidatorAttribute : ValidationAttribute return ValidationResult.Success; } + return new ValidationResult(ErrorMessage); + } +} + +public interface IValidatorParser +{ + static abstract bool Validate([NotNullWhen(true)] string? s, IFormatProvider? provider); +} + +public class ValidatorParserAttribute : ValidationAttribute + where TParser : IValidatorParser +{ + protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) + { + if (value is string v && TParser.Validate(v, null)) + { + return ValidationResult.Success; + } + return new ValidationResult(ErrorMessage); } } \ No newline at end of file diff --git a/src/Client/UniScan.Client.App/UI/ViewLocator.cs b/src/Client/UniScan.Client.App/UI/ViewLocator.cs index a6fde52..c9857db 100755 --- a/src/Client/UniScan.Client.App/UI/ViewLocator.cs +++ b/src/Client/UniScan.Client.App/UI/ViewLocator.cs @@ -1,7 +1,10 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Linq; using Avalonia.Controls; using Avalonia.Controls.Templates; +using Shiki.Common.Serialization.Registry; +using Shiki.Common.Serialization.Registry.Validators; using UniScan.Client.App.Views.ViewModel; namespace UniScan.Client.App.UI; @@ -23,7 +26,7 @@ public class ViewLocator : IDataTemplate return null; string name = param.GetType().FullName!.Replace("ControlViewModel", "Control", StringComparison.Ordinal).Replace("ViewModel", "View", StringComparison.Ordinal); - Type? type = Type.GetType(name); + Type? type = GlobalTypeRegistry>.GetScope(typeof(Control)).FirstOrDefault(kvp => kvp.Key == name).Value; if (type != null) { diff --git a/src/Client/UniScan.Client.App/UniScan.Client.App.csproj b/src/Client/UniScan.Client.App/UniScan.Client.App.csproj index f5e1d4a..0243348 100755 --- a/src/Client/UniScan.Client.App/UniScan.Client.App.csproj +++ b/src/Client/UniScan.Client.App/UniScan.Client.App.csproj @@ -8,6 +8,8 @@ 0.1.1 0.1.1 UniScan client + Release App;Debug App + AnyCPU diff --git a/src/Client/UniScan.Client.App/UniScanApp.axaml.cs b/src/Client/UniScan.Client.App/UniScanApp.axaml.cs index 7390d92..0a1d57f 100755 --- a/src/Client/UniScan.Client.App/UniScanApp.axaml.cs +++ b/src/Client/UniScan.Client.App/UniScanApp.axaml.cs @@ -54,7 +54,7 @@ public partial class UniScanApp : Application private static readonly UniScanPlatformAttribute? PlatformAttribute = PlatformContainingAssembly?.GetCustomAttribute(); private static readonly Assembly PlatformAssembly = PlatformContainingAssembly ?? typeof(UniScanApp).Assembly; - public static readonly Identifier Identifier = UniScanClient.ClientIdentifier.Derived("app"); + public static readonly Identifier Identifier = UniScanClient.Identifier.Derived("app"); public static readonly SemVersion PlatformVersion = SemVersion.Parse(PlatformAssembly.InformationalVersionString); public static readonly ClientSoftwareInfo SoftwareInfo = new(new SoftwareAssemblyInfo(Identifier, SemVersion.Parse(typeof(UniScanApp).Assembly.InformationalVersionString)), diff --git a/src/Client/UniScan.Client.App/Views/Remote/Connection/ConnectionMethod/TCPConnectionMethodFactoryView.axaml b/src/Client/UniScan.Client.App/Views/Remote/Connection/ConnectionMethod/TCPConnectionMethodFactoryView.axaml index 95ba6b5..dbabc74 100644 --- a/src/Client/UniScan.Client.App/Views/Remote/Connection/ConnectionMethod/TCPConnectionMethodFactoryView.axaml +++ b/src/Client/UniScan.Client.App/Views/Remote/Connection/ConnectionMethod/TCPConnectionMethodFactoryView.axaml @@ -11,8 +11,8 @@ - - + + diff --git a/src/Client/UniScan.Client.App/Views/Remote/Connection/ConnectionMethod/TCPConnectionMethodFactoryViewModel.cs b/src/Client/UniScan.Client.App/Views/Remote/Connection/ConnectionMethod/TCPConnectionMethodFactoryViewModel.cs index e7bc22b..7dfcc63 100644 --- a/src/Client/UniScan.Client.App/Views/Remote/Connection/ConnectionMethod/TCPConnectionMethodFactoryViewModel.cs +++ b/src/Client/UniScan.Client.App/Views/Remote/Connection/ConnectionMethod/TCPConnectionMethodFactoryViewModel.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Net; using CommunityToolkit.Mvvm.ComponentModel; +using UniScan.Client.App.Core; using UniScan.Client.App.UI.ConnectionMethod; using UniScan.Client.App.UI.Validation; using UniScan.Client.App.Views.ViewModel; @@ -22,7 +23,7 @@ public TCPConnectionMethodFactoryViewModel() [NotifyDataErrorInfo] [Required(ErrorMessage = "IP Address is required")] [NotifyPropertyChangedFor(nameof(IsValid))] - [ParsableValidator(ErrorMessage = "Must be a valid IP Address, such as 127.0.0.1")] + [ValidatorParser(ErrorMessage = "Must be a valid IP Address, such as 127.0.0.1")] public partial string Address { get; set; } [ObservableProperty] @@ -34,6 +35,14 @@ public TCPConnectionMethodFactoryViewModel() public bool IsValid => !GetErrors(nameof(Address)).Any() && !GetErrors(nameof(Port)).Any(); - public TCPRemoteConnectionMethod Create() => new(new IPEndPoint(IPAddress.Parse(Address), int.Parse(Port))); + public TCPRemoteConnectionMethod Create() + { + ushort port = ushort.Parse(Port); + EndPoint e = IPAddress.TryParse(Address, out IPAddress? addr) + ? new IPEndPoint(addr, port) + : new DnsEndPoint(Address, port); + + return new TCPRemoteConnectionMethod(e); + } } #endif \ No newline at end of file diff --git a/src/Client/UniScan.Client.App/Views/Remote/Connection/DisconnectedRemotePageView.axaml b/src/Client/UniScan.Client.App/Views/Remote/Connection/DisconnectedRemotePageView.axaml index 69272fc..16934c5 100644 --- a/src/Client/UniScan.Client.App/Views/Remote/Connection/DisconnectedRemotePageView.axaml +++ b/src/Client/UniScan.Client.App/Views/Remote/Connection/DisconnectedRemotePageView.axaml @@ -8,7 +8,7 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="UniScan.Client.App.Views.Remote.Connection.DisconnectedRemotePageView" x:DataType="connection:DisconnectedRemotePageViewModel"> - + @@ -17,7 +17,11 @@ - + + + + +