From af503dc1bb6ddf3da52f406e04b5cd2aabb4ece7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Holger=20B=C3=B6rchers?= Date: Thu, 3 Apr 2025 16:05:46 +0200 Subject: [PATCH] Add Ip- and Mac address --- src/App.axaml | 5 +++ src/DiscoveredDeviceViewModel.cs | 50 ++++++++++++++++++++++++--- src/GlobalUsings.cs | 1 + src/IpAddressComparer.cs | 13 +++++++ src/MainWindow.axaml | 10 +++++- src/MainWindowViewModel.cs | 59 ++++++++++++++++++++++++++------ 6 files changed, 121 insertions(+), 17 deletions(-) create mode 100644 src/IpAddressComparer.cs diff --git a/src/App.axaml b/src/App.axaml index 89a7161..2664838 100644 --- a/src/App.axaml +++ b/src/App.axaml @@ -2,9 +2,14 @@ x:Class="SddpViewer.App" xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:collections="clr-namespace:System.Collections;assembly=System.Runtime" + xmlns:sddpViewer="clr-namespace:SddpViewer" RequestedThemeVariant="Default"> + + + diff --git a/src/DiscoveredDeviceViewModel.cs b/src/DiscoveredDeviceViewModel.cs index 616d53f..f67c761 100644 --- a/src/DiscoveredDeviceViewModel.cs +++ b/src/DiscoveredDeviceViewModel.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Net.NetworkInformation; namespace SddpViewer; @@ -7,10 +8,11 @@ using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using Rssdp; -public partial class DiscoveredDeviceViewModel : ObservableObject +public partial class DiscoveredDeviceViewModel : ObservableObject, IDisposable { private readonly DiscoveredSsdpDevice _device; private SsdpDevice? _ssdpDevice; + private readonly CancellationTokenSource _cancellationTokenSource = new(); public DiscoveredDeviceViewModel(DiscoveredSsdpDevice device) { @@ -20,19 +22,45 @@ public partial class DiscoveredDeviceViewModel : ObservableObject IpAddress = EvaluateIpAddress(device); MacAddress = EvaluateMacAddress(); HostName = EvaluateHostName(); + RunPing(_cancellationTokenSource.Token); + } + + private async void RunPing(CancellationToken token) + { + try + { + using Ping ping = new(); + while (!token.IsCancellationRequested) + { + var result = await ping.SendPingAsync(IpAddress, TimeSpan.FromSeconds(1), cancellationToken: token).ConfigureAwait(true); + Online = result.Status == IPStatus.Success; + await Task.Delay(1000, token).ConfigureAwait(true); + } + } + catch (TaskCanceledException e) + { + Console.WriteLine(e); + } + catch (OperationCanceledException e) + { + Console.WriteLine(e); + } } public string HostName { get; set; } public string MacAddress { get; set; } - public string IpAddress { get; set; } + public IPAddress IpAddress { get; set; } public DateTime DiscoveredAt { get; } [ObservableProperty] public partial string ResponseHeader { get; set; } + [ObservableProperty] + public partial bool Online { get; set; } + /// /// Sets or returns the type of notification, being either a uuid, device type, service type or upnp:rootdevice. /// @@ -73,6 +101,11 @@ public partial class DiscoveredDeviceViewModel : ObservableObject [ObservableProperty] public partial string Version { get; set; } = ""; + public void Cancel() + { + _cancellationTokenSource.Cancel(); + } + public async Task GetFurtherInformationAsync() { _ssdpDevice = await _device.GetDeviceInfo().ConfigureAwait(false); @@ -81,7 +114,7 @@ public partial class DiscoveredDeviceViewModel : ObservableObject PresentationUrl = _ssdpDevice.PresentationUrl; ModelNumber = _ssdpDevice.ModelNumber; Version = _ssdpDevice.SerialNumber?.Split(',').Last() ?? new Version().ToString(); - HttpClient client = new HttpClient(); + using var client = new HttpClient(); var response = await client.GetAsync(_ssdpDevice.ModelUrl).ConfigureAwait(false); ResponseHeader = await response.Content.ReadAsStringAsync(); } @@ -91,11 +124,12 @@ public partial class DiscoveredDeviceViewModel : ObservableObject private string GetResponseHeader() => string.Join("," + Environment.NewLine, _device.ResponseHeaders.Select(x => $"{{{x.Key} : {string.Join(";", x.Value)}}}")); - private string EvaluateIpAddress(DiscoveredSsdpDevice device) => device.DescriptionLocation.Host; + private IPAddress EvaluateIpAddress(DiscoveredSsdpDevice device) => + IPAddress.TryParse(device.DescriptionLocation.Host, out var value) ? value : IPAddress.Any; private string EvaluateMacAddress() { - var lookupResult = ArpLookup.Arp.Lookup(IPAddress.Parse(IpAddress)); + var lookupResult = ArpLookup.Arp.Lookup(IpAddress); return lookupResult is null ? "Unknown" : string.Join(":", lookupResult.GetAddressBytes().Select(b => $"{b:x2}")); } @@ -103,4 +137,10 @@ public partial class DiscoveredDeviceViewModel : ObservableObject { return obj is DiscoveredDeviceViewModel viewModel && Equals(viewModel.Usn, Usn); } + + public void Dispose() + { + _cancellationTokenSource.Cancel(); + _cancellationTokenSource.Dispose(); + } } diff --git a/src/GlobalUsings.cs b/src/GlobalUsings.cs index 302ce28..0ec108c 100644 --- a/src/GlobalUsings.cs +++ b/src/GlobalUsings.cs @@ -1,3 +1,4 @@ // Global using directives +global using System.Collections; global using Avalonia; diff --git a/src/IpAddressComparer.cs b/src/IpAddressComparer.cs new file mode 100644 index 0000000..118e8e9 --- /dev/null +++ b/src/IpAddressComparer.cs @@ -0,0 +1,13 @@ +namespace SddpViewer; + +public class IpAddressComparer : IComparer, IComparer +{ + public int Compare(DiscoveredDeviceViewModel? x, DiscoveredDeviceViewModel? y) + { + var aByte = x?.IpAddress.GetAddressBytes().Sum(b => (long)b) ?? 0; + var bByte = y?.IpAddress.GetAddressBytes().Sum(b => (long)b) ?? 0; + return aByte.CompareTo(bByte); + } + + public int Compare(object? x, object? y) => Compare(x as DiscoveredDeviceViewModel, y as DiscoveredDeviceViewModel); +} diff --git a/src/MainWindow.axaml b/src/MainWindow.axaml index e2b8b73..b8da75d 100644 --- a/src/MainWindow.axaml +++ b/src/MainWindow.axaml @@ -43,12 +43,20 @@ Grid.Column="0" Grid.ColumnSpan="2" AutoGenerateColumns="False" + FrozenColumnCount="2" IsReadOnly="True" ItemsSource="{Binding SddpDevices}"> + - + diff --git a/src/MainWindowViewModel.cs b/src/MainWindowViewModel.cs index 2f05d94..5167990 100644 --- a/src/MainWindowViewModel.cs +++ b/src/MainWindowViewModel.cs @@ -14,8 +14,8 @@ public sealed partial class MainWindowViewModel : ObservableObject, IDisposable public MainWindowViewModel() { NetworkAdapters = NetworkAdapter.GetAvailableNetworkAdapter().ToArray(); - SelectedNetworkAdapter = NetworkAdapters.FirstOrDefault(); - SddpDevices = new ObservableCollection(); + SelectedNetworkAdapter = NetworkAdapters.FirstOrDefault(x => x.IpAddress.ToString() == DeviceIpAddress); + SddpDevices = []; SddpDevices.CollectionChanged += SddpDevices_OnCollectionChanged; } @@ -23,18 +23,55 @@ public sealed partial class MainWindowViewModel : ObservableObject, IDisposable { try { - foreach (object eNewItem in e.NewItems ?? Array.Empty()) + switch (e.Action) { - if (eNewItem is DiscoveredDeviceViewModel discoveredDeviceViewModel) - { - await discoveredDeviceViewModel.GetFurtherInformationAsync(); - } + case NotifyCollectionChangedAction.Add: + await AddAction(e.NewItems); + break; + case NotifyCollectionChangedAction.Remove: + RemoveAction(e.OldItems); + break; + case NotifyCollectionChangedAction.Replace: + RemoveAction(e.OldItems); + await AddAction(e.NewItems); + break; + case NotifyCollectionChangedAction.Reset: + RemoveAction(sender as ObservableCollection); + break; + case NotifyCollectionChangedAction.Move: + break; + default: + throw new ArgumentOutOfRangeException(); } } catch (Exception ex) { throw; // TODO handle exception } + + return; + } + + private static void RemoveAction(IList? list) + { + foreach (object eOldItem in list ?? Array.Empty()) + { + if (eOldItem is DiscoveredDeviceViewModel discoveredDeviceViewModel) + { + discoveredDeviceViewModel.Dispose(); + } + } + } + + private static async Task AddAction(IList? list) + { + foreach (object eNewItem in list ?? Array.Empty()) + { + if (eNewItem is DiscoveredDeviceViewModel discoveredDeviceViewModel) + { + await discoveredDeviceViewModel.GetFurtherInformationAsync(); + } + } } private void LocatorOnDeviceUnavailable(object? sender, DeviceUnavailableEventArgs e) @@ -58,16 +95,16 @@ public sealed partial class MainWindowViewModel : ObservableObject, IDisposable } [ObservableProperty] - private IReadOnlyList _networkAdapters; + public partial IReadOnlyList NetworkAdapters { get; set; } [ObservableProperty] - private NetworkAdapter? _selectedNetworkAdapter; + public partial NetworkAdapter? SelectedNetworkAdapter { get; set; } [ObservableProperty] - private string _deviceIpAddress = "192.168.42.193"; + public partial string DeviceIpAddress { get; set; } = "192.168.42.192"; [ObservableProperty] - private string _notificationFilter = "upnp:rootdevice"; + public partial string NotificationFilter { get; set; } = "upnp:rootdevice"; private SsdpDeviceLocator? _locator;