reduce device discovery traffic
This commit is contained in:
parent
4704d22944
commit
e0d5f7d158
|
@ -19,13 +19,12 @@ using Rssdp.Infrastructure;
|
|||
|
||||
namespace Emby.Dlna.Ssdp
|
||||
{
|
||||
public class DeviceDiscovery : IDeviceDiscovery, IDisposable
|
||||
public class DeviceDiscovery : IDeviceDiscovery
|
||||
{
|
||||
private bool _disposed;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly CancellationTokenSource _tokenSource;
|
||||
|
||||
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered;
|
||||
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
|
||||
|
@ -37,8 +36,6 @@ namespace Emby.Dlna.Ssdp
|
|||
|
||||
public DeviceDiscovery(ILogger logger, IServerConfigurationManager config, ISocketFactory socketFactory, ITimerFactory timerFactory)
|
||||
{
|
||||
_tokenSource = new CancellationTokenSource();
|
||||
|
||||
_logger = logger;
|
||||
_config = config;
|
||||
_socketFactory = socketFactory;
|
||||
|
@ -59,39 +56,10 @@ namespace Emby.Dlna.Ssdp
|
|||
_deviceLocator.DeviceAvailable += deviceLocator_DeviceAvailable;
|
||||
_deviceLocator.DeviceUnavailable += _DeviceLocator_DeviceUnavailable;
|
||||
|
||||
// Perform a search so we don't have to wait for devices to broadcast notifications
|
||||
// again to get any results right away (notifications are broadcast periodically).
|
||||
StartAsyncSearch();
|
||||
}
|
||||
var dueTime = TimeSpan.FromSeconds(5);
|
||||
var interval = TimeSpan.FromSeconds(_config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds);
|
||||
|
||||
private void StartAsyncSearch()
|
||||
{
|
||||
Task.Factory.StartNew(async (o) =>
|
||||
{
|
||||
while (!_tokenSource.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Enable listening for notifications (optional)
|
||||
_deviceLocator.StartListeningForNotifications();
|
||||
|
||||
await _deviceLocator.SearchAsync(_tokenSource.Token).ConfigureAwait(false);
|
||||
|
||||
var delay = _config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds * 1000;
|
||||
|
||||
await Task.Delay(delay, _tokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error searching for devices", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}, CancellationToken.None, TaskCreationOptions.LongRunning);
|
||||
_deviceLocator.RestartBroadcastTimer(dueTime, interval);
|
||||
}
|
||||
|
||||
// Process each found device in the event handler
|
||||
|
@ -141,7 +109,11 @@ namespace Emby.Dlna.Ssdp
|
|||
if (!_disposed)
|
||||
{
|
||||
_disposed = true;
|
||||
_tokenSource.Cancel();
|
||||
if (_deviceLocator != null)
|
||||
{
|
||||
_deviceLocator.Dispose();
|
||||
_deviceLocator = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -139,6 +139,23 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
_memoryStreamFactory = memoryStreamFactory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dashboard UI path.
|
||||
/// </summary>
|
||||
/// <value>The dashboard UI path.</value>
|
||||
public string DashboardUIPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_serverConfigurationManager.Configuration.DashboardSourcePath))
|
||||
{
|
||||
return _serverConfigurationManager.Configuration.DashboardSourcePath;
|
||||
}
|
||||
|
||||
return Path.Combine(_serverConfigurationManager.ApplicationPaths.ApplicationResourcesPath, "dashboard-ui");
|
||||
}
|
||||
}
|
||||
|
||||
public object Get(GetFavIcon request)
|
||||
{
|
||||
return Get(new GetDashboardResource
|
||||
|
@ -176,7 +193,7 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
|
||||
if (plugin != null && stream != null)
|
||||
{
|
||||
return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => GetPackageCreator().ModifyHtml("dummy.html", stream, null, _appHost.ApplicationVersion.ToString(), null));
|
||||
return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => GetPackageCreator(DashboardUIPath).ModifyHtml("dummy.html", stream, null, _appHost.ApplicationVersion.ToString(), null));
|
||||
}
|
||||
|
||||
throw new ResourceNotFoundException();
|
||||
|
@ -274,9 +291,11 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
path = path.Replace("bower_components" + _appHost.ApplicationVersion, "bower_components", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
var contentType = MimeTypes.GetMimeType(path);
|
||||
var basePath = DashboardUIPath;
|
||||
|
||||
// Bounce them to the startup wizard if it hasn't been completed yet
|
||||
if (!_serverConfigurationManager.Configuration.IsStartupWizardCompleted && path.IndexOf("wizard", StringComparison.OrdinalIgnoreCase) == -1 && GetPackageCreator().IsCoreHtml(path))
|
||||
if (!_serverConfigurationManager.Configuration.IsStartupWizardCompleted &&
|
||||
path.IndexOf("wizard", StringComparison.OrdinalIgnoreCase) == -1 && GetPackageCreator(basePath).IsCoreHtml(path))
|
||||
{
|
||||
// But don't redirect if an html import is being requested.
|
||||
if (path.IndexOf("bower_components", StringComparison.OrdinalIgnoreCase) == -1)
|
||||
|
@ -296,7 +315,7 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
!contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) &&
|
||||
!contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var stream = await GetResourceStream(path, localizationCulture).ConfigureAwait(false);
|
||||
var stream = await GetResourceStream(basePath, path, localizationCulture).ConfigureAwait(false);
|
||||
return _resultFactory.GetResult(stream, contentType);
|
||||
}
|
||||
|
||||
|
@ -311,7 +330,7 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
|
||||
var cacheKey = (_appHost.ApplicationVersion + (localizationCulture ?? string.Empty) + path).GetMD5();
|
||||
|
||||
return await _resultFactory.GetStaticResult(Request, cacheKey, null, cacheDuration, contentType, () => GetResourceStream(path, localizationCulture)).ConfigureAwait(false);
|
||||
return await _resultFactory.GetStaticResult(Request, cacheKey, null, cacheDuration, contentType, () => GetResourceStream(basePath, path, localizationCulture)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private string GetLocalizationCulture()
|
||||
|
@ -322,86 +341,72 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
/// <summary>
|
||||
/// Gets the resource stream.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <param name="localizationCulture">The localization culture.</param>
|
||||
/// <returns>Task{Stream}.</returns>
|
||||
private Task<Stream> GetResourceStream(string path, string localizationCulture)
|
||||
private Task<Stream> GetResourceStream(string basePath, string virtualPath, string localizationCulture)
|
||||
{
|
||||
return GetPackageCreator()
|
||||
.GetResource(path, null, localizationCulture, _appHost.ApplicationVersion.ToString());
|
||||
return GetPackageCreator(basePath)
|
||||
.GetResource(virtualPath, null, localizationCulture, _appHost.ApplicationVersion.ToString());
|
||||
}
|
||||
|
||||
private PackageCreator GetPackageCreator()
|
||||
private PackageCreator GetPackageCreator(string basePath)
|
||||
{
|
||||
return new PackageCreator(_fileSystem, _logger, _serverConfigurationManager, _memoryStreamFactory);
|
||||
return new PackageCreator(basePath, _fileSystem, _logger, _serverConfigurationManager, _memoryStreamFactory);
|
||||
}
|
||||
|
||||
public async Task<object> Get(GetDashboardPackage request)
|
||||
{
|
||||
var mode = request.Mode;
|
||||
|
||||
var path = !string.IsNullOrWhiteSpace(mode) ?
|
||||
Path.Combine(_serverConfigurationManager.ApplicationPaths.ProgramDataPath, "webclient-dump")
|
||||
var inputPath = string.IsNullOrWhiteSpace(mode) ?
|
||||
DashboardUIPath
|
||||
: "C:\\dev\\emby-web-mobile-master\\dist";
|
||||
|
||||
var targetPath = !string.IsNullOrWhiteSpace(mode) ?
|
||||
inputPath
|
||||
: "C:\\dev\\emby-web-mobile\\src";
|
||||
|
||||
try
|
||||
var packageCreator = GetPackageCreator(inputPath);
|
||||
|
||||
if (!string.Equals(inputPath, targetPath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_fileSystem.DeleteDirectory(path, true);
|
||||
try
|
||||
{
|
||||
_fileSystem.DeleteDirectory(targetPath, true);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
CopyDirectory(inputPath, targetPath);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
var creator = GetPackageCreator();
|
||||
|
||||
CopyDirectory(creator.DashboardUIPath, path);
|
||||
|
||||
string culture = null;
|
||||
|
||||
var appVersion = _appHost.ApplicationVersion.ToString();
|
||||
|
||||
// Try to trim the output size a bit
|
||||
var bowerPath = Path.Combine(path, "bower_components");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(mode))
|
||||
{
|
||||
// Delete things that are unneeded in an attempt to keep the output as trim as possible
|
||||
|
||||
DeleteFoldersByName(Path.Combine(bowerPath, "emby-webcomponents", "fonts"), "roboto");
|
||||
_fileSystem.DeleteDirectory(Path.Combine(path, "css", "images", "tour"), true);
|
||||
}
|
||||
|
||||
await DumpHtml(creator.DashboardUIPath, path, mode, culture, appVersion);
|
||||
await DumpHtml(packageCreator, inputPath, targetPath, mode, culture, appVersion);
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private void DeleteFoldersByName(string path, string name)
|
||||
{
|
||||
var directories = _fileSystem.GetDirectories(path, true)
|
||||
.Where(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
foreach (var directory in directories)
|
||||
{
|
||||
_fileSystem.DeleteDirectory(directory.FullName, true);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DumpHtml(string source, string destination, string mode, string culture, string appVersion)
|
||||
private async Task DumpHtml(PackageCreator packageCreator, string source, string destination, string mode, string culture, string appVersion)
|
||||
{
|
||||
foreach (var file in _fileSystem.GetFiles(source))
|
||||
{
|
||||
var filename = file.Name;
|
||||
|
||||
await DumpFile(filename, Path.Combine(destination, filename), mode, culture, appVersion).ConfigureAwait(false);
|
||||
if (!string.Equals(file.Extension, ".html", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
await DumpFile(packageCreator, filename, Path.Combine(destination, filename), mode, culture, appVersion).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DumpFile(string resourceVirtualPath, string destinationFilePath, string mode, string culture, string appVersion)
|
||||
private async Task DumpFile(PackageCreator packageCreator, string resourceVirtualPath, string destinationFilePath, string mode, string culture, string appVersion)
|
||||
{
|
||||
using (var stream = await GetPackageCreator().GetResource(resourceVirtualPath, mode, culture, appVersion).ConfigureAwait(false))
|
||||
using (var stream = await packageCreator.GetResource(resourceVirtualPath, mode, culture, appVersion).ConfigureAwait(false))
|
||||
{
|
||||
using (var fs = _fileSystem.GetFileStream(destinationFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
|
||||
{
|
||||
|
|
|
@ -19,31 +19,33 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
private readonly ILogger _logger;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IMemoryStreamFactory _memoryStreamFactory;
|
||||
private readonly string _basePath;
|
||||
|
||||
public PackageCreator(IFileSystem fileSystem, ILogger logger, IServerConfigurationManager config, IMemoryStreamFactory memoryStreamFactory)
|
||||
public PackageCreator(string basePath, IFileSystem fileSystem, ILogger logger, IServerConfigurationManager config, IMemoryStreamFactory memoryStreamFactory)
|
||||
{
|
||||
_fileSystem = fileSystem;
|
||||
_logger = logger;
|
||||
_config = config;
|
||||
_memoryStreamFactory = memoryStreamFactory;
|
||||
_basePath = basePath;
|
||||
}
|
||||
|
||||
public async Task<Stream> GetResource(string path,
|
||||
public async Task<Stream> GetResource(string virtualPath,
|
||||
string mode,
|
||||
string localizationCulture,
|
||||
string appVersion)
|
||||
{
|
||||
var resourceStream = GetRawResourceStream(path);
|
||||
var resourceStream = GetRawResourceStream(virtualPath);
|
||||
|
||||
if (resourceStream != null)
|
||||
{
|
||||
// Don't apply any caching for html pages
|
||||
// jQuery ajax doesn't seem to handle if-modified-since correctly
|
||||
if (IsFormat(path, "html"))
|
||||
if (IsFormat(virtualPath, "html"))
|
||||
{
|
||||
if (IsCoreHtml(path))
|
||||
if (IsCoreHtml(virtualPath))
|
||||
{
|
||||
resourceStream = await ModifyHtml(path, resourceStream, mode, appVersion, localizationCulture).ConfigureAwait(false);
|
||||
resourceStream = await ModifyHtml(virtualPath, resourceStream, mode, appVersion, localizationCulture).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -62,33 +64,13 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
return Path.GetExtension(path).EndsWith(format, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dashboard UI path.
|
||||
/// </summary>
|
||||
/// <value>The dashboard UI path.</value>
|
||||
public string DashboardUIPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_config.Configuration.DashboardSourcePath))
|
||||
{
|
||||
return _config.Configuration.DashboardSourcePath;
|
||||
}
|
||||
|
||||
return Path.Combine(_config.ApplicationPaths.ApplicationResourcesPath, "dashboard-ui");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dashboard resource path.
|
||||
/// </summary>
|
||||
/// <param name="virtualPath">The virtual path.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
private string GetDashboardResourcePath(string virtualPath)
|
||||
{
|
||||
var rootPath = DashboardUIPath;
|
||||
|
||||
var fullPath = Path.Combine(rootPath, virtualPath.Replace('/', _fileSystem.DirectorySeparatorChar));
|
||||
var fullPath = Path.Combine(_basePath, virtualPath.Replace('/', _fileSystem.DirectorySeparatorChar));
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -100,7 +82,7 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
}
|
||||
|
||||
// Don't allow file system access outside of the source folder
|
||||
if (!_fileSystem.ContainsSubPath(rootPath, fullPath))
|
||||
if (!_fileSystem.ContainsSubPath(_basePath, fullPath))
|
||||
{
|
||||
throw new SecurityException("Access denied");
|
||||
}
|
||||
|
@ -118,10 +100,8 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
path = GetDashboardResourcePath(path);
|
||||
var parent = Path.GetDirectoryName(path);
|
||||
|
||||
var basePath = DashboardUIPath;
|
||||
|
||||
return string.Equals(basePath, parent, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(Path.Combine(basePath, "voice"), parent, StringComparison.OrdinalIgnoreCase);
|
||||
return string.Equals(_basePath, parent, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(Path.Combine(_basePath, "voice"), parent, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -319,11 +299,9 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
/// <summary>
|
||||
/// Gets the raw resource stream.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <returns>Task{Stream}.</returns>
|
||||
private Stream GetRawResourceStream(string path)
|
||||
private Stream GetRawResourceStream(string virtualPath)
|
||||
{
|
||||
return _fileSystem.GetFileStream(GetDashboardResourcePath(path), FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, true);
|
||||
return _fileSystem.GetFileStream(GetDashboardResourcePath(virtualPath), FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,11 +24,9 @@ namespace Rssdp.Infrastructure
|
|||
private List<DiscoveredSsdpDevice> _Devices;
|
||||
private ISsdpCommunicationsServer _CommunicationsServer;
|
||||
|
||||
private IList<DiscoveredSsdpDevice> _SearchResults;
|
||||
private object _SearchResultsSynchroniser;
|
||||
|
||||
private ITimer _ExpireCachedDevicesTimer;
|
||||
private ITimer _BroadcastTimer;
|
||||
private ITimerFactory _timerFactory;
|
||||
private object _timerLock = new object();
|
||||
|
||||
private static readonly TimeSpan DefaultSearchWaitTime = TimeSpan.FromSeconds(4);
|
||||
private static readonly TimeSpan OneSecond = TimeSpan.FromSeconds(1);
|
||||
|
@ -48,7 +46,6 @@ namespace Rssdp.Infrastructure
|
|||
_timerFactory = timerFactory;
|
||||
_CommunicationsServer.ResponseReceived += CommsServer_ResponseReceived;
|
||||
|
||||
_SearchResultsSynchroniser = new object();
|
||||
_Devices = new List<DiscoveredSsdpDevice>();
|
||||
}
|
||||
|
||||
|
@ -92,11 +89,52 @@ namespace Rssdp.Infrastructure
|
|||
|
||||
#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>
|
||||
/// Performs a search for all devices using the default search timeout.
|
||||
/// </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);
|
||||
}
|
||||
|
@ -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>
|
||||
/// </list>
|
||||
/// </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);
|
||||
}
|
||||
|
@ -121,13 +158,12 @@ namespace Rssdp.Infrastructure
|
|||
/// Performs a search for all devices using the specified search timeout.
|
||||
/// </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>
|
||||
/// <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);
|
||||
}
|
||||
|
||||
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.Length == 0) throw new ArgumentException("searchTarget cannot be an empty string.", "searchTarget");
|
||||
|
@ -136,48 +172,7 @@ namespace Rssdp.Infrastructure
|
|||
|
||||
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
|
||||
|
@ -287,9 +282,7 @@ namespace Rssdp.Infrastructure
|
|||
|
||||
if (disposing)
|
||||
{
|
||||
var timer = _ExpireCachedDevicesTimer;
|
||||
if (timer != null)
|
||||
timer.Dispose();
|
||||
DisposeBroadcastTimer();
|
||||
|
||||
var commsServer = _CommunicationsServer;
|
||||
_CommunicationsServer = null;
|
||||
|
@ -332,40 +325,9 @@ namespace Rssdp.Infrastructure
|
|||
|
||||
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;
|
||||
|
||||
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)
|
||||
|
@ -450,8 +412,6 @@ namespace Rssdp.Infrastructure
|
|||
};
|
||||
|
||||
AddOrUpdateDiscoveredDevice(device, localIpAddress);
|
||||
|
||||
ResetExpireCachedDevicesTimer();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -477,26 +437,9 @@ namespace Rssdp.Infrastructure
|
|||
if (NotificationTypeMatchesFilter(deadDevice))
|
||||
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
|
||||
|
||||
private static string GetFirstHeaderStringValue(string headerName, HttpResponseMessage message)
|
||||
|
@ -624,20 +567,6 @@ namespace Rssdp.Infrastructure
|
|||
|
||||
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)
|
||||
{
|
||||
if (NotificationTypeMatchesFilter(removedDevice))
|
||||
|
|
Loading…
Reference in New Issue
Block a user