@ -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 ITimer _BroadcastTimer ;
private object _SearchResultsSynchroniser ;
private ITimer _ExpireCachedDevicesTimer ;
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>
private Task SearchAsync ( CancellationToken cancellationToken )
public Task < IEnumerable < DiscoveredSsdpDevice > > 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>
private Task SearchAsync ( string searchTarget )
public Task < IEnumerable < DiscoveredSsdpDevice > > 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>
private Task SearchAsync ( TimeSpan searchWaitTime )
public Task < IEnumerable < DiscoveredSsdpDevice > > SearchAsync ( TimeSpan searchWaitTime )
{
{
return SearchAsync ( SsdpConstants . SsdpDiscoverAllSTHeader , searchWaitTime , CancellationToken . None ) ;
return SearchAsync ( SsdpConstants . SsdpDiscoverAllSTHeader , searchWaitTime , CancellationToken . None ) ;
}
}
p ublic async Task < IEnumerable < DiscoveredSsdpDevice > > SearchAsync ( string searchTarget , TimeSpan searchWaitTime , CancellationToken cancellationToken )
p rivate 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." ) ;
return BroadcastDiscoverMessage ( searchTarget , SearchTimeToMXValue ( searchWaitTime ) , cancellationToken ) ;
_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 ;
}
}
# endregion
# endregion
@ -287,9 +282,7 @@ namespace Rssdp.Infrastructure
if ( disposing )
if ( disposing )
{
{
var timer = _ExpireCachedDevicesTimer ;
DisposeBroadcastTimer ( ) ;
if ( timer ! = null )
timer . Dispose ( ) ;
var commsServer = _CommunicationsServer ;
var commsServer = _CommunicationsServer ;
_CommunicationsServer = null ;
_CommunicationsServer = null ;
@ -332,39 +325,8 @@ 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 ) ;
}
}
@ -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 ) )