|
@@ -24,11 +24,9 @@ namespace Rssdp.Infrastructure
|
|
private List<DiscoveredSsdpDevice> _Devices;
|
|
private List<DiscoveredSsdpDevice> _Devices;
|
|
private ISsdpCommunicationsServer _CommunicationsServer;
|
|
private ISsdpCommunicationsServer _CommunicationsServer;
|
|
|
|
|
|
- private IList<DiscoveredSsdpDevice> _SearchResults;
|
|
|
|
- private object _SearchResultsSynchroniser;
|
|
|
|
-
|
|
|
|
- private ITimer _ExpireCachedDevicesTimer;
|
|
|
|
|
|
+ private ITimer _BroadcastTimer;
|
|
private ITimerFactory _timerFactory;
|
|
private ITimerFactory _timerFactory;
|
|
|
|
+ private object _timerLock = new object();
|
|
|
|
|
|
private static readonly TimeSpan DefaultSearchWaitTime = TimeSpan.FromSeconds(4);
|
|
private static readonly TimeSpan DefaultSearchWaitTime = TimeSpan.FromSeconds(4);
|
|
private static readonly TimeSpan OneSecond = TimeSpan.FromSeconds(1);
|
|
private static readonly TimeSpan OneSecond = TimeSpan.FromSeconds(1);
|
|
@@ -48,7 +46,6 @@ namespace Rssdp.Infrastructure
|
|
_timerFactory = timerFactory;
|
|
_timerFactory = timerFactory;
|
|
_CommunicationsServer.ResponseReceived += CommsServer_ResponseReceived;
|
|
_CommunicationsServer.ResponseReceived += CommsServer_ResponseReceived;
|
|
|
|
|
|
- _SearchResultsSynchroniser = new object();
|
|
|
|
_Devices = new List<DiscoveredSsdpDevice>();
|
|
_Devices = new List<DiscoveredSsdpDevice>();
|
|
}
|
|
}
|
|
|
|
|
|
@@ -92,11 +89,52 @@ namespace Rssdp.Infrastructure
|
|
|
|
|
|
#region Search Overloads
|
|
#region Search Overloads
|
|
|
|
|
|
|
|
+ public void RestartBroadcastTimer(TimeSpan dueTime, TimeSpan period)
|
|
|
|
+ {
|
|
|
|
+ lock (_timerLock)
|
|
|
|
+ {
|
|
|
|
+ if (_BroadcastTimer == null)
|
|
|
|
+ {
|
|
|
|
+ _BroadcastTimer = _timerFactory.Create(OnBroadcastTimerCallback, null, dueTime, period);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ _BroadcastTimer.Change(dueTime, period);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public void DisposeBroadcastTimer()
|
|
|
|
+ {
|
|
|
|
+ lock (_timerLock)
|
|
|
|
+ {
|
|
|
|
+ if (_BroadcastTimer != null)
|
|
|
|
+ {
|
|
|
|
+ _BroadcastTimer.Dispose();
|
|
|
|
+ _BroadcastTimer = null;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private async void OnBroadcastTimerCallback(object state)
|
|
|
|
+ {
|
|
|
|
+ StartListeningForNotifications();
|
|
|
|
+ RemoveExpiredDevicesFromCache();
|
|
|
|
+
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ await SearchAsync(CancellationToken.None).ConfigureAwait(false);
|
|
|
|
+ }
|
|
|
|
+ catch (Exception ex)
|
|
|
|
+ {
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
/// <summary>
|
|
/// <summary>
|
|
/// Performs a search for all devices using the default search timeout.
|
|
/// Performs a search for all devices using the default search timeout.
|
|
/// </summary>
|
|
/// </summary>
|
|
- /// <returns>A task whose result is an <see cref="IEnumerable{T}"/> of <see cref="DiscoveredSsdpDevice" /> instances, representing all found devices.</returns>
|
|
|
|
- public Task<IEnumerable<DiscoveredSsdpDevice>> SearchAsync(CancellationToken cancellationToken)
|
|
|
|
|
|
+ private Task SearchAsync(CancellationToken cancellationToken)
|
|
{
|
|
{
|
|
return SearchAsync(SsdpConstants.SsdpDiscoverAllSTHeader, DefaultSearchWaitTime, cancellationToken);
|
|
return SearchAsync(SsdpConstants.SsdpDiscoverAllSTHeader, DefaultSearchWaitTime, cancellationToken);
|
|
}
|
|
}
|
|
@@ -111,8 +149,7 @@ namespace Rssdp.Infrastructure
|
|
/// <item><term>Device type</term><description>Fully qualified device type starting with urn: i.e urn:schemas-upnp-org:Basic:1</description></item>
|
|
/// <item><term>Device type</term><description>Fully qualified device type starting with urn: i.e urn:schemas-upnp-org:Basic:1</description></item>
|
|
/// </list>
|
|
/// </list>
|
|
/// </param>
|
|
/// </param>
|
|
- /// <returns>A task whose result is an <see cref="IEnumerable{T}"/> of <see cref="DiscoveredSsdpDevice" /> instances, representing all found devices.</returns>
|
|
|
|
- public Task<IEnumerable<DiscoveredSsdpDevice>> SearchAsync(string searchTarget)
|
|
|
|
|
|
+ private Task SearchAsync(string searchTarget)
|
|
{
|
|
{
|
|
return SearchAsync(searchTarget, DefaultSearchWaitTime, CancellationToken.None);
|
|
return SearchAsync(searchTarget, DefaultSearchWaitTime, CancellationToken.None);
|
|
}
|
|
}
|
|
@@ -121,13 +158,12 @@ namespace Rssdp.Infrastructure
|
|
/// Performs a search for all devices using the specified search timeout.
|
|
/// Performs a search for all devices using the specified search timeout.
|
|
/// </summary>
|
|
/// </summary>
|
|
/// <param name="searchWaitTime">The amount of time to wait for network responses to the search request. Longer values will likely return more devices, but increase search time. A value between 1 and 5 seconds is recommended by the UPnP 1.1 specification, this method requires the value be greater 1 second if it is not zero. Specify TimeSpan.Zero to return only devices already in the cache.</param>
|
|
/// <param name="searchWaitTime">The amount of time to wait for network responses to the search request. Longer values will likely return more devices, but increase search time. A value between 1 and 5 seconds is recommended by the UPnP 1.1 specification, this method requires the value be greater 1 second if it is not zero. Specify TimeSpan.Zero to return only devices already in the cache.</param>
|
|
- /// <returns>A task whose result is an <see cref="IEnumerable{T}"/> of <see cref="DiscoveredSsdpDevice" /> instances, representing all found devices.</returns>
|
|
|
|
- public Task<IEnumerable<DiscoveredSsdpDevice>> SearchAsync(TimeSpan searchWaitTime)
|
|
|
|
|
|
+ private Task SearchAsync(TimeSpan searchWaitTime)
|
|
{
|
|
{
|
|
return SearchAsync(SsdpConstants.SsdpDiscoverAllSTHeader, searchWaitTime, CancellationToken.None);
|
|
return SearchAsync(SsdpConstants.SsdpDiscoverAllSTHeader, searchWaitTime, CancellationToken.None);
|
|
}
|
|
}
|
|
|
|
|
|
- public async Task<IEnumerable<DiscoveredSsdpDevice>> SearchAsync(string searchTarget, TimeSpan searchWaitTime, CancellationToken cancellationToken)
|
|
|
|
|
|
+ private Task SearchAsync(string searchTarget, TimeSpan searchWaitTime, CancellationToken cancellationToken)
|
|
{
|
|
{
|
|
if (searchTarget == null) throw new ArgumentNullException("searchTarget");
|
|
if (searchTarget == null) throw new ArgumentNullException("searchTarget");
|
|
if (searchTarget.Length == 0) throw new ArgumentException("searchTarget cannot be an empty string.", "searchTarget");
|
|
if (searchTarget.Length == 0) throw new ArgumentException("searchTarget cannot be an empty string.", "searchTarget");
|
|
@@ -136,48 +172,7 @@ namespace Rssdp.Infrastructure
|
|
|
|
|
|
ThrowIfDisposed();
|
|
ThrowIfDisposed();
|
|
|
|
|
|
- if (_SearchResults != null) throw new InvalidOperationException("Search already in progress. Only one search at a time is allowed.");
|
|
|
|
- _SearchResults = new List<DiscoveredSsdpDevice>();
|
|
|
|
-
|
|
|
|
- // If searchWaitTime == 0 then we are only going to report unexpired cached items, not actually do a search.
|
|
|
|
- if (searchWaitTime > TimeSpan.Zero)
|
|
|
|
- await BroadcastDiscoverMessage(searchTarget, SearchTimeToMXValue(searchWaitTime), cancellationToken).ConfigureAwait(false);
|
|
|
|
-
|
|
|
|
- lock (_SearchResultsSynchroniser)
|
|
|
|
- {
|
|
|
|
- foreach (var device in GetUnexpiredDevices().Where(NotificationTypeMatchesFilter))
|
|
|
|
- {
|
|
|
|
- DeviceFound(device, false, null);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (searchWaitTime != TimeSpan.Zero)
|
|
|
|
- await Task.Delay(searchWaitTime, cancellationToken).ConfigureAwait(false);
|
|
|
|
-
|
|
|
|
- IEnumerable<DiscoveredSsdpDevice> retVal = null;
|
|
|
|
-
|
|
|
|
- try
|
|
|
|
- {
|
|
|
|
- lock (_SearchResultsSynchroniser)
|
|
|
|
- {
|
|
|
|
- retVal = _SearchResults;
|
|
|
|
- _SearchResults = null;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- RemoveExpiredDevicesFromCache();
|
|
|
|
- }
|
|
|
|
- finally
|
|
|
|
- {
|
|
|
|
- var server = _CommunicationsServer;
|
|
|
|
- try
|
|
|
|
- {
|
|
|
|
- if (server != null) // In case we were disposed while searching.
|
|
|
|
- server.StopListeningForResponses();
|
|
|
|
- }
|
|
|
|
- catch (ObjectDisposedException) { }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return retVal;
|
|
|
|
|
|
+ return BroadcastDiscoverMessage(searchTarget, SearchTimeToMXValue(searchWaitTime), cancellationToken);
|
|
}
|
|
}
|
|
|
|
|
|
#endregion
|
|
#endregion
|
|
@@ -287,9 +282,7 @@ namespace Rssdp.Infrastructure
|
|
|
|
|
|
if (disposing)
|
|
if (disposing)
|
|
{
|
|
{
|
|
- var timer = _ExpireCachedDevicesTimer;
|
|
|
|
- if (timer != null)
|
|
|
|
- timer.Dispose();
|
|
|
|
|
|
+ DisposeBroadcastTimer();
|
|
|
|
|
|
var commsServer = _CommunicationsServer;
|
|
var commsServer = _CommunicationsServer;
|
|
_CommunicationsServer = null;
|
|
_CommunicationsServer = null;
|
|
@@ -332,40 +325,9 @@ namespace Rssdp.Infrastructure
|
|
|
|
|
|
private void DeviceFound(DiscoveredSsdpDevice device, bool isNewDevice, IpAddressInfo localIpAddress)
|
|
private void DeviceFound(DiscoveredSsdpDevice device, bool isNewDevice, IpAddressInfo localIpAddress)
|
|
{
|
|
{
|
|
- // Don't raise the event if we've already done it for a cached
|
|
|
|
- // version of this device, and the cached version isn't
|
|
|
|
- // "significantly" different, i.e location and cachelifetime
|
|
|
|
- // haven't changed.
|
|
|
|
- var raiseEvent = false;
|
|
|
|
-
|
|
|
|
if (!NotificationTypeMatchesFilter(device)) return;
|
|
if (!NotificationTypeMatchesFilter(device)) return;
|
|
|
|
|
|
- lock (_SearchResultsSynchroniser)
|
|
|
|
- {
|
|
|
|
- if (_SearchResults != null)
|
|
|
|
- {
|
|
|
|
- var existingDevice = FindExistingDeviceNotification(_SearchResults, device.NotificationType, device.Usn);
|
|
|
|
- if (existingDevice == null)
|
|
|
|
- {
|
|
|
|
- _SearchResults.Add(device);
|
|
|
|
- raiseEvent = true;
|
|
|
|
- }
|
|
|
|
- else
|
|
|
|
- {
|
|
|
|
- if (existingDevice.DescriptionLocation != device.DescriptionLocation || existingDevice.CacheLifetime != device.CacheLifetime)
|
|
|
|
- {
|
|
|
|
- _SearchResults.Remove(existingDevice);
|
|
|
|
- _SearchResults.Add(device);
|
|
|
|
- raiseEvent = true;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- else
|
|
|
|
- raiseEvent = true;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (raiseEvent)
|
|
|
|
- OnDeviceAvailable(device, isNewDevice, localIpAddress);
|
|
|
|
|
|
+ OnDeviceAvailable(device, isNewDevice, localIpAddress);
|
|
}
|
|
}
|
|
|
|
|
|
private bool NotificationTypeMatchesFilter(DiscoveredSsdpDevice device)
|
|
private bool NotificationTypeMatchesFilter(DiscoveredSsdpDevice device)
|
|
@@ -450,8 +412,6 @@ namespace Rssdp.Infrastructure
|
|
};
|
|
};
|
|
|
|
|
|
AddOrUpdateDiscoveredDevice(device, localIpAddress);
|
|
AddOrUpdateDiscoveredDevice(device, localIpAddress);
|
|
-
|
|
|
|
- ResetExpireCachedDevicesTimer();
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -477,26 +437,9 @@ namespace Rssdp.Infrastructure
|
|
if (NotificationTypeMatchesFilter(deadDevice))
|
|
if (NotificationTypeMatchesFilter(deadDevice))
|
|
OnDeviceUnavailable(deadDevice, false);
|
|
OnDeviceUnavailable(deadDevice, false);
|
|
}
|
|
}
|
|
-
|
|
|
|
- ResetExpireCachedDevicesTimer();
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- private void ResetExpireCachedDevicesTimer()
|
|
|
|
- {
|
|
|
|
- if (IsDisposed) return;
|
|
|
|
-
|
|
|
|
- if (_ExpireCachedDevicesTimer == null)
|
|
|
|
- _ExpireCachedDevicesTimer = _timerFactory.Create(this.ExpireCachedDevices, null, System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
|
|
|
|
-
|
|
|
|
- _ExpireCachedDevicesTimer.Change(60000, System.Threading.Timeout.Infinite);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private void ExpireCachedDevices(object state)
|
|
|
|
- {
|
|
|
|
- RemoveExpiredDevicesFromCache();
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
#region Header/Message Processing Utilities
|
|
#region Header/Message Processing Utilities
|
|
|
|
|
|
private static string GetFirstHeaderStringValue(string headerName, HttpResponseMessage message)
|
|
private static string GetFirstHeaderStringValue(string headerName, HttpResponseMessage message)
|
|
@@ -624,20 +567,6 @@ namespace Rssdp.Infrastructure
|
|
|
|
|
|
if (existingDevices != null && existingDevices.Any())
|
|
if (existingDevices != null && existingDevices.Any())
|
|
{
|
|
{
|
|
- lock (_SearchResultsSynchroniser)
|
|
|
|
- {
|
|
|
|
- if (_SearchResults != null)
|
|
|
|
- {
|
|
|
|
- var resultsToRemove = (from result in _SearchResults where result.Usn == deviceUsn select result).ToArray();
|
|
|
|
- foreach (var result in resultsToRemove)
|
|
|
|
- {
|
|
|
|
- if (this.IsDisposed) return true;
|
|
|
|
-
|
|
|
|
- _SearchResults.Remove(result);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
foreach (var removedDevice in existingDevices)
|
|
foreach (var removedDevice in existingDevices)
|
|
{
|
|
{
|
|
if (NotificationTypeMatchesFilter(removedDevice))
|
|
if (NotificationTypeMatchesFilter(removedDevice))
|