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