using System.Collections.Specialized; namespace SddpViewer; using System.Collections.ObjectModel; using System.Threading.Tasks; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Rssdp; public sealed partial class MainWindowViewModel : ObservableObject, IDisposable { public MainWindowViewModel() { NetworkAdapters = NetworkAdapter.GetAvailableNetworkAdapter().ToArray(); SelectedNetworkAdapter = NetworkAdapters.FirstOrDefault(x => x.IpAddress.ToString() == DeviceIpAddress); SddpDevices = []; SddpDevices.CollectionChanged += SddpDevices_OnCollectionChanged; } private static async void SddpDevices_OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) { 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(); } } 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) { var existingDevice = SddpDevices.FirstOrDefault(x => string.Equals(x.Usn, e.DiscoveredDevice.Usn, StringComparison.Ordinal)); if (existingDevice is null) return; Dispatcher.UIThread.Invoke(() => SddpDevices.Remove(existingDevice)); } private void LocatorOnDeviceAvailable(object? sender, DeviceAvailableEventArgs e) { if (!e.IsNewlyDiscovered) { return; } var discoveredDeviceViewModel = new DiscoveredDeviceViewModel(e.DiscoveredDevice); if (!SddpDevices.Contains(discoveredDeviceViewModel)) Dispatcher.UIThread.Invoke(() => SddpDevices.Add(discoveredDeviceViewModel)); } [ObservableProperty] public partial IReadOnlyList NetworkAdapters { get; set; } [ObservableProperty] public partial NetworkAdapter? SelectedNetworkAdapter { get; set; } [ObservableProperty] public partial string DeviceIpAddress { get; set; } = "192.168.42.192"; [ObservableProperty] public partial string NotificationFilter { get; set; } = "upnp:rootdevice"; private SsdpDeviceLocator? _locator; public ObservableCollection SddpDevices { get; } [RelayCommand] private async Task SearchDevicesNowAsync() { SddpDevices.Clear(); if (_locator is not null) { _locator.StopListeningForNotifications(); _locator.DeviceAvailable -= LocatorOnDeviceAvailable; _locator.DeviceUnavailable -= LocatorOnDeviceUnavailable; _locator.Dispose(); } await StartListening(async locator => { foreach (var discoveredSsdpDevice in await locator.SearchAsync()) { var discoveredDeviceViewModel = new DiscoveredDeviceViewModel(discoveredSsdpDevice); Dispatcher.UIThread.Invoke(() => SddpDevices.Add(discoveredDeviceViewModel)); } }); } [RelayCommand] private async Task ResearchAsync() { if (_locator is not null) { await _locator.SearchAsync(); } } [RelayCommand] private async Task StartListening(Func? action = null) { _locator = new SsdpDeviceLocator(SelectedNetworkAdapter?.IpAddress.ToString()); if (!string.IsNullOrWhiteSpace(NotificationFilter)) { _locator.NotificationFilter = NotificationFilter; } if (action is not null) { await action.Invoke(_locator); } _locator.DeviceAvailable += LocatorOnDeviceAvailable; _locator.DeviceUnavailable += LocatorOnDeviceUnavailable; _locator.StartListeningForNotifications(); } public void Dispose() { _locator?.Dispose(); } }