Merge pull request #2449 from MediaBrowser/dev

Dev
This commit is contained in:
Luke 2017-02-07 13:35:29 -05:00 committed by GitHub
commit ab43339506
15 changed files with 151 additions and 273 deletions

View File

@ -12,7 +12,7 @@ namespace Emby.Common.Implementations.EnvironmentInfo
public MediaBrowser.Model.System.Architecture? CustomArchitecture { get; set; } public MediaBrowser.Model.System.Architecture? CustomArchitecture { get; set; }
public MediaBrowser.Model.System.OperatingSystem? CustomOperatingSystem { get; set; } public MediaBrowser.Model.System.OperatingSystem? CustomOperatingSystem { get; set; }
public MediaBrowser.Model.System.OperatingSystem OperatingSystem public virtual MediaBrowser.Model.System.OperatingSystem OperatingSystem
{ {
get get
{ {

View File

@ -125,8 +125,7 @@ namespace Emby.Dlna.Profiles
new DirectPlayProfile new DirectPlayProfile
{ {
Container = "flac", Container = "flac,ac3",
AudioCodec = "flac",
Type = DlnaProfileType.Audio Type = DlnaProfileType.Audio
}, },

View File

@ -45,7 +45,7 @@
<DirectPlayProfile container="asf" audioCodec="mp2,ac3" videoCodec="mpeg2video" type="Video" /> <DirectPlayProfile container="asf" audioCodec="mp2,ac3" videoCodec="mpeg2video" type="Video" />
<DirectPlayProfile container="mp3" audioCodec="mp2,mp3" type="Audio" /> <DirectPlayProfile container="mp3" audioCodec="mp2,mp3" type="Audio" />
<DirectPlayProfile container="mp4" audioCodec="mp4" type="Audio" /> <DirectPlayProfile container="mp4" audioCodec="mp4" type="Audio" />
<DirectPlayProfile container="flac" audioCodec="flac" type="Audio" /> <DirectPlayProfile container="flac,ac3" type="Audio" />
<DirectPlayProfile container="asf" audioCodec="wmav2,wmapro,wmavoice" type="Audio" /> <DirectPlayProfile container="asf" audioCodec="wmav2,wmapro,wmavoice" type="Audio" />
<DirectPlayProfile container="ogg" audioCodec="vorbis" type="Audio" /> <DirectPlayProfile container="ogg" audioCodec="vorbis" type="Audio" />
<DirectPlayProfile container="jpeg,png,gif,bmp,tiff" type="Photo" /> <DirectPlayProfile container="jpeg,png,gif,bmp,tiff" type="Photo" />

View File

@ -19,13 +19,12 @@ using Rssdp.Infrastructure;
namespace Emby.Dlna.Ssdp namespace Emby.Dlna.Ssdp
{ {
public class DeviceDiscovery : IDeviceDiscovery, IDisposable public class DeviceDiscovery : IDeviceDiscovery
{ {
private bool _disposed; private bool _disposed;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly CancellationTokenSource _tokenSource;
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered; public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered;
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft; public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
@ -37,8 +36,6 @@ namespace Emby.Dlna.Ssdp
public DeviceDiscovery(ILogger logger, IServerConfigurationManager config, ISocketFactory socketFactory, ITimerFactory timerFactory) public DeviceDiscovery(ILogger logger, IServerConfigurationManager config, ISocketFactory socketFactory, ITimerFactory timerFactory)
{ {
_tokenSource = new CancellationTokenSource();
_logger = logger; _logger = logger;
_config = config; _config = config;
_socketFactory = socketFactory; _socketFactory = socketFactory;
@ -59,39 +56,10 @@ namespace Emby.Dlna.Ssdp
_deviceLocator.DeviceAvailable += deviceLocator_DeviceAvailable; _deviceLocator.DeviceAvailable += deviceLocator_DeviceAvailable;
_deviceLocator.DeviceUnavailable += _DeviceLocator_DeviceUnavailable; _deviceLocator.DeviceUnavailable += _DeviceLocator_DeviceUnavailable;
// Perform a search so we don't have to wait for devices to broadcast notifications var dueTime = TimeSpan.FromSeconds(5);
// again to get any results right away (notifications are broadcast periodically). var interval = TimeSpan.FromSeconds(_config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds);
StartAsyncSearch();
}
private void StartAsyncSearch() _deviceLocator.RestartBroadcastTimer(dueTime, interval);
{
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);
} }
// Process each found device in the event handler // Process each found device in the event handler
@ -141,7 +109,11 @@ namespace Emby.Dlna.Ssdp
if (!_disposed) if (!_disposed)
{ {
_disposed = true; _disposed = true;
_tokenSource.Cancel(); if (_deviceLocator != null)
{
_deviceLocator.Dispose();
_deviceLocator = null;
}
} }
} }
} }

View File

@ -2092,13 +2092,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
writer.WriteElementString("credits", person); writer.WriteElementString("credits", person);
} }
var rt = item.GetProviderId(MetadataProviders.RottenTomatoes);
if (!string.IsNullOrEmpty(rt))
{
writer.WriteElementString("rottentomatoesid", rt);
}
var tmdbCollection = item.GetProviderId(MetadataProviders.TmdbCollection); var tmdbCollection = item.GetProviderId(MetadataProviders.TmdbCollection);
if (!string.IsNullOrEmpty(tmdbCollection)) if (!string.IsNullOrEmpty(tmdbCollection))

View File

@ -365,7 +365,6 @@ namespace Emby.Server.Implementations.LiveTv
} }
} }
_logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info));
Normalize(info, service, isVideo); Normalize(info, service, isVideo);
return new Tuple<MediaSourceInfo, IDirectStreamProvider>(info, directStreamProvider); return new Tuple<MediaSourceInfo, IDirectStreamProvider>(info, directStreamProvider);

View File

@ -154,6 +154,7 @@ namespace Emby.Server.Implementations.LiveTv
_logger.ErrorException("Error probing live tv stream", ex); _logger.ErrorException("Error probing live tv stream", ex);
} }
_logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(stream));
return new Tuple<MediaSourceInfo, IDirectStreamProvider>(stream, directStreamProvider); return new Tuple<MediaSourceInfo, IDirectStreamProvider>(stream, directStreamProvider);
} }

View File

@ -24,10 +24,6 @@ namespace MediaBrowser.Model.Entities
/// </summary> /// </summary>
Tvcom = 5, Tvcom = 5,
/// <summary> /// <summary>
/// The rotten tomatoes
/// </summary>
RottenTomatoes = 6,
/// <summary>
/// Tmdb Collection Id /// Tmdb Collection Id
/// </summary> /// </summary>
TmdbCollection = 7, TmdbCollection = 7,

View File

@ -17,6 +17,7 @@ namespace MediaBrowser.Model.System
{ {
Windows, Windows,
Linux, Linux,
OSX OSX,
BSD
} }
} }

View File

@ -142,7 +142,7 @@ namespace MediaBrowser.Providers.Music
if (fileInfo.Exists) if (fileInfo.Exists)
{ {
if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7) if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 3)
{ {
return _cachedTask; return _cachedTask;
} }

View File

@ -319,5 +319,18 @@ namespace MediaBrowser.Server.Mono
{ {
return Syscall.getuid().ToString(CultureInfo.InvariantCulture); return Syscall.getuid().ToString(CultureInfo.InvariantCulture);
} }
public override Model.System.OperatingSystem OperatingSystem
{
get
{
if (IsBsd)
{
return Model.System.OperatingSystem.BSD;
}
return base.OperatingSystem;
}
}
} }
} }

View File

@ -139,6 +139,23 @@ namespace MediaBrowser.WebDashboard.Api
_memoryStreamFactory = memoryStreamFactory; _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) public object Get(GetFavIcon request)
{ {
return Get(new GetDashboardResource return Get(new GetDashboardResource
@ -176,7 +193,7 @@ namespace MediaBrowser.WebDashboard.Api
if (plugin != null && stream != null) 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(); throw new ResourceNotFoundException();
@ -274,9 +291,11 @@ namespace MediaBrowser.WebDashboard.Api
path = path.Replace("bower_components" + _appHost.ApplicationVersion, "bower_components", StringComparison.OrdinalIgnoreCase); path = path.Replace("bower_components" + _appHost.ApplicationVersion, "bower_components", StringComparison.OrdinalIgnoreCase);
var contentType = MimeTypes.GetMimeType(path); var contentType = MimeTypes.GetMimeType(path);
var basePath = DashboardUIPath;
// Bounce them to the startup wizard if it hasn't been completed yet // 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. // But don't redirect if an html import is being requested.
if (path.IndexOf("bower_components", StringComparison.OrdinalIgnoreCase) == -1) if (path.IndexOf("bower_components", StringComparison.OrdinalIgnoreCase) == -1)
@ -296,7 +315,7 @@ namespace MediaBrowser.WebDashboard.Api
!contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) && !contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) &&
!contentType.StartsWith("font/", 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); return _resultFactory.GetResult(stream, contentType);
} }
@ -311,7 +330,7 @@ namespace MediaBrowser.WebDashboard.Api
var cacheKey = (_appHost.ApplicationVersion + (localizationCulture ?? string.Empty) + path).GetMD5(); 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() private string GetLocalizationCulture()
@ -322,86 +341,72 @@ namespace MediaBrowser.WebDashboard.Api
/// <summary> /// <summary>
/// Gets the resource stream. /// Gets the resource stream.
/// </summary> /// </summary>
/// <param name="path">The path.</param> private Task<Stream> GetResourceStream(string basePath, string virtualPath, string localizationCulture)
/// <param name="localizationCulture">The localization culture.</param>
/// <returns>Task{Stream}.</returns>
private Task<Stream> GetResourceStream(string path, string localizationCulture)
{ {
return GetPackageCreator() return GetPackageCreator(basePath)
.GetResource(path, null, localizationCulture, _appHost.ApplicationVersion.ToString()); .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) public async Task<object> Get(GetDashboardPackage request)
{ {
var mode = request.Mode; var mode = request.Mode;
var path = !string.IsNullOrWhiteSpace(mode) ? var inputPath = string.IsNullOrWhiteSpace(mode) ?
Path.Combine(_serverConfigurationManager.ApplicationPaths.ProgramDataPath, "webclient-dump") DashboardUIPath
: "C:\\dev\\emby-web-mobile-master\\dist";
var targetPath = !string.IsNullOrWhiteSpace(mode) ?
inputPath
: "C:\\dev\\emby-web-mobile\\src"; : "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; string culture = null;
var appVersion = _appHost.ApplicationVersion.ToString(); var appVersion = _appHost.ApplicationVersion.ToString();
// Try to trim the output size a bit await DumpHtml(packageCreator, inputPath, targetPath, mode, culture, appVersion);
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);
return ""; return "";
} }
private void DeleteFoldersByName(string path, string name) private async Task DumpHtml(PackageCreator packageCreator, string source, string destination, string mode, string culture, string appVersion)
{
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)
{ {
foreach (var file in _fileSystem.GetFiles(source)) foreach (var file in _fileSystem.GetFiles(source))
{ {
var filename = file.Name; 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)) using (var fs = _fileSystem.GetFileStream(destinationFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
{ {

View File

@ -19,31 +19,33 @@ namespace MediaBrowser.WebDashboard.Api
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly IMemoryStreamFactory _memoryStreamFactory; 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; _fileSystem = fileSystem;
_logger = logger; _logger = logger;
_config = config; _config = config;
_memoryStreamFactory = memoryStreamFactory; _memoryStreamFactory = memoryStreamFactory;
_basePath = basePath;
} }
public async Task<Stream> GetResource(string path, public async Task<Stream> GetResource(string virtualPath,
string mode, string mode,
string localizationCulture, string localizationCulture,
string appVersion) string appVersion)
{ {
var resourceStream = GetRawResourceStream(path); var resourceStream = GetRawResourceStream(virtualPath);
if (resourceStream != null) if (resourceStream != null)
{ {
// Don't apply any caching for html pages // Don't apply any caching for html pages
// jQuery ajax doesn't seem to handle if-modified-since correctly // 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); 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> /// <summary>
/// Gets the dashboard resource path. /// Gets the dashboard resource path.
/// </summary> /// </summary>
/// <param name="virtualPath">The virtual path.</param>
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
private string GetDashboardResourcePath(string virtualPath) private string GetDashboardResourcePath(string virtualPath)
{ {
var rootPath = DashboardUIPath; var fullPath = Path.Combine(_basePath, virtualPath.Replace('/', _fileSystem.DirectorySeparatorChar));
var fullPath = Path.Combine(rootPath, virtualPath.Replace('/', _fileSystem.DirectorySeparatorChar));
try try
{ {
@ -100,7 +82,7 @@ namespace MediaBrowser.WebDashboard.Api
} }
// Don't allow file system access outside of the source folder // 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"); throw new SecurityException("Access denied");
} }
@ -118,10 +100,8 @@ namespace MediaBrowser.WebDashboard.Api
path = GetDashboardResourcePath(path); path = GetDashboardResourcePath(path);
var parent = Path.GetDirectoryName(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> /// <summary>
@ -319,11 +299,9 @@ namespace MediaBrowser.WebDashboard.Api
/// <summary> /// <summary>
/// Gets the raw resource stream. /// Gets the raw resource stream.
/// </summary> /// </summary>
/// <param name="path">The path.</param> private Stream GetRawResourceStream(string virtualPath)
/// <returns>Task{Stream}.</returns>
private Stream GetRawResourceStream(string path)
{ {
return _fileSystem.GetFileStream(GetDashboardResourcePath(path), FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, true); return _fileSystem.GetFileStream(GetDashboardResourcePath(virtualPath), FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, true);
} }
} }

View File

@ -579,14 +579,6 @@ namespace MediaBrowser.XbmcMetadata.Savers
writer.WriteElementString("website", item.HomePageUrl); writer.WriteElementString("website", item.HomePageUrl);
} }
var rt = item.GetProviderId(MetadataProviders.RottenTomatoes);
if (!string.IsNullOrEmpty(rt))
{
writer.WriteElementString("rottentomatoesid", rt);
writtenProviderIds.Add(MetadataProviders.RottenTomatoes.ToString());
}
var tmdbCollection = item.GetProviderId(MetadataProviders.TmdbCollection); var tmdbCollection = item.GetProviderId(MetadataProviders.TmdbCollection);
if (!string.IsNullOrEmpty(tmdbCollection)) if (!string.IsNullOrEmpty(tmdbCollection))

View File

@ -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);
} }
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."); 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,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) OnDeviceAvailable(device, isNewDevice, localIpAddress);
{
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);
} }
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))