commit
f1c7b86081
|
@ -527,7 +527,7 @@ return null;
|
||||||
|
|
||||||
RegisterSingleInstance(FileSystemManager);
|
RegisterSingleInstance(FileSystemManager);
|
||||||
|
|
||||||
HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, LogManager.GetLogger("HttpClient"), FileSystemManager, MemoryStreamFactory);
|
HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, LogManager.GetLogger("HttpClient"), FileSystemManager, MemoryStreamFactory, GetDefaultUserAgent);
|
||||||
RegisterSingleInstance(HttpClient);
|
RegisterSingleInstance(HttpClient);
|
||||||
|
|
||||||
RegisterSingleInstance(NetworkManager);
|
RegisterSingleInstance(NetworkManager);
|
||||||
|
@ -549,6 +549,30 @@ return null;
|
||||||
return Task.FromResult(true);
|
return Task.FromResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetDefaultUserAgent()
|
||||||
|
{
|
||||||
|
var name = FormatAttribute(Name);
|
||||||
|
|
||||||
|
return name + "/" + ApplicationVersion.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string FormatAttribute(string str)
|
||||||
|
{
|
||||||
|
var arr = str.ToCharArray();
|
||||||
|
|
||||||
|
arr = Array.FindAll<char>(arr, (c => (char.IsLetterOrDigit(c)
|
||||||
|
|| char.IsWhiteSpace(c))));
|
||||||
|
|
||||||
|
var result = new string(arr);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(result))
|
||||||
|
{
|
||||||
|
result = "Emby";
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a list of types within an assembly
|
/// Gets a list of types within an assembly
|
||||||
/// This will handle situations that would normally throw an exception - such as a type within the assembly that depends on some other non-existant reference
|
/// This will handle situations that would normally throw an exception - such as a type within the assembly that depends on some other non-existant reference
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,6 +18,7 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Common.Implementations.HttpClientManager;
|
using Emby.Common.Implementations.HttpClientManager;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Common;
|
||||||
|
|
||||||
namespace Emby.Common.Implementations.HttpClientManager
|
namespace Emby.Common.Implementations.HttpClientManager
|
||||||
{
|
{
|
||||||
|
@ -43,17 +44,12 @@ namespace Emby.Common.Implementations.HttpClientManager
|
||||||
|
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly IMemoryStreamFactory _memoryStreamProvider;
|
private readonly IMemoryStreamFactory _memoryStreamProvider;
|
||||||
|
private readonly Func<string> _defaultUserAgentFn;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="HttpClientManager" /> class.
|
/// Initializes a new instance of the <see cref="HttpClientManager" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="appPaths">The app paths.</param>
|
public HttpClientManager(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem, IMemoryStreamFactory memoryStreamProvider, Func<string> defaultUserAgentFn)
|
||||||
/// <param name="logger">The logger.</param>
|
|
||||||
/// <param name="fileSystem">The file system.</param>
|
|
||||||
/// <exception cref="System.ArgumentNullException">appPaths
|
|
||||||
/// or
|
|
||||||
/// logger</exception>
|
|
||||||
public HttpClientManager(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem, IMemoryStreamFactory memoryStreamProvider)
|
|
||||||
{
|
{
|
||||||
if (appPaths == null)
|
if (appPaths == null)
|
||||||
{
|
{
|
||||||
|
@ -68,6 +64,7 @@ namespace Emby.Common.Implementations.HttpClientManager
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_memoryStreamProvider = memoryStreamProvider;
|
_memoryStreamProvider = memoryStreamProvider;
|
||||||
_appPaths = appPaths;
|
_appPaths = appPaths;
|
||||||
|
_defaultUserAgentFn = defaultUserAgentFn;
|
||||||
|
|
||||||
#if NET46
|
#if NET46
|
||||||
// http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c
|
// http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c
|
||||||
|
@ -257,6 +254,8 @@ namespace Emby.Common.Implementations.HttpClientManager
|
||||||
|
|
||||||
private void AddRequestHeaders(HttpWebRequest request, HttpRequestOptions options)
|
private void AddRequestHeaders(HttpWebRequest request, HttpRequestOptions options)
|
||||||
{
|
{
|
||||||
|
var hasUserAgent = false;
|
||||||
|
|
||||||
foreach (var header in options.RequestHeaders.ToList())
|
foreach (var header in options.RequestHeaders.ToList())
|
||||||
{
|
{
|
||||||
if (string.Equals(header.Key, "Accept", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(header.Key, "Accept", StringComparison.OrdinalIgnoreCase))
|
||||||
|
@ -265,11 +264,8 @@ namespace Emby.Common.Implementations.HttpClientManager
|
||||||
}
|
}
|
||||||
else if (string.Equals(header.Key, "User-Agent", StringComparison.OrdinalIgnoreCase))
|
else if (string.Equals(header.Key, "User-Agent", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
#if NET46
|
SetUserAgent(request, header.Value);
|
||||||
request.UserAgent = header.Value;
|
hasUserAgent = true;
|
||||||
#elif NETSTANDARD1_6
|
|
||||||
request.Headers["User-Agent"] = header.Value;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -280,6 +276,20 @@ namespace Emby.Common.Implementations.HttpClientManager
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!hasUserAgent && options.EnableDefaultUserAgent)
|
||||||
|
{
|
||||||
|
SetUserAgent(request, _defaultUserAgentFn());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetUserAgent(HttpWebRequest request, string userAgent)
|
||||||
|
{
|
||||||
|
#if NET46
|
||||||
|
request.UserAgent = userAgent;
|
||||||
|
#elif NETSTANDARD1_6
|
||||||
|
request.Headers["User-Agent"] = userAgent;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -447,6 +457,8 @@ namespace Emby.Common.Implementations.HttpClientManager
|
||||||
if (options.RequestContentBytes != null ||
|
if (options.RequestContentBytes != null ||
|
||||||
!string.IsNullOrEmpty(options.RequestContent) ||
|
!string.IsNullOrEmpty(options.RequestContent) ||
|
||||||
string.Equals(httpMethod, "post", StringComparison.OrdinalIgnoreCase))
|
string.Equals(httpMethod, "post", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var bytes = options.RequestContentBytes ??
|
var bytes = options.RequestContentBytes ??
|
||||||
Encoding.UTF8.GetBytes(options.RequestContent ?? string.Empty);
|
Encoding.UTF8.GetBytes(options.RequestContent ?? string.Empty);
|
||||||
|
@ -458,6 +470,11 @@ namespace Emby.Common.Implementations.HttpClientManager
|
||||||
#endif
|
#endif
|
||||||
(await httpWebRequest.GetRequestStreamAsync().ConfigureAwait(false)).Write(bytes, 0, bytes.Length);
|
(await httpWebRequest.GetRequestStreamAsync().ConfigureAwait(false)).Write(bytes, 0, bytes.Length);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new HttpException(ex.Message) { IsTimedOut = true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (options.ResourcePool != null)
|
if (options.ResourcePool != null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -309,8 +309,8 @@
|
||||||
<Project>{4f26d5d8-a7b0-42b3-ba42-7cb7d245934e}</Project>
|
<Project>{4f26d5d8-a7b0-42b3-ba42-7cb7d245934e}</Project>
|
||||||
<Name>SocketHttpListener.Portable</Name>
|
<Name>SocketHttpListener.Portable</Name>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
<Reference Include="Emby.XmlTv, Version=1.0.6241.4924, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="Emby.XmlTv, Version=1.0.6249.32870, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\Emby.XmlTv.1.0.5\lib\portable-net45+win8\Emby.XmlTv.dll</HintPath>
|
<HintPath>..\packages\Emby.XmlTv.1.0.6\lib\portable-net45+win8\Emby.XmlTv.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="MediaBrowser.Naming, Version=1.0.6201.24431, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="MediaBrowser.Naming, Version=1.0.6201.24431, Culture=neutral, processorArchitecture=MSIL">
|
||||||
|
|
|
@ -2627,6 +2627,18 @@ namespace Emby.Server.Implementations.Library
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var map in ConfigurationManager.Configuration.PathSubstitutions)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(map.From))
|
||||||
|
{
|
||||||
|
var substitutionResult = SubstitutePathInternal(path, map.From, map.To);
|
||||||
|
if (substitutionResult.Item2)
|
||||||
|
{
|
||||||
|
return substitutionResult.Item1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,21 +74,20 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||||
return new MusicArtist();
|
return new MusicArtist();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_config.Configuration.EnableSimpleArtistDetection)
|
||||||
|
{
|
||||||
return null;
|
return null;
|
||||||
//if (_config.Configuration.EnableSimpleArtistDetection)
|
}
|
||||||
//{
|
|
||||||
// return null;
|
|
||||||
//}
|
|
||||||
|
|
||||||
//// Avoid mis-identifying top folders
|
// Avoid mis-identifying top folders
|
||||||
//if (args.Parent.IsRoot) return null;
|
if (args.Parent.IsRoot) return null;
|
||||||
|
|
||||||
//var directoryService = args.DirectoryService;
|
var directoryService = args.DirectoryService;
|
||||||
|
|
||||||
//var albumResolver = new MusicAlbumResolver(_logger, _fileSystem, _libraryManager);
|
var albumResolver = new MusicAlbumResolver(_logger, _fileSystem, _libraryManager);
|
||||||
|
|
||||||
//// If we contain an album assume we are an artist folder
|
// If we contain an album assume we are an artist folder
|
||||||
//return args.FileSystemChildren.Where(i => i.IsDirectory).Any(i => albumResolver.IsMusicAlbum(i.FullName, directoryService, args.GetLibraryOptions())) ? new MusicArtist() : null;
|
return args.FileSystemChildren.Where(i => i.IsDirectory).Any(i => albumResolver.IsMusicAlbum(i.FullName, directoryService, args.GetLibraryOptions())) ? new MusicArtist() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -423,6 +423,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
{
|
{
|
||||||
tunerChannel.Name = epgChannel.Name;
|
tunerChannel.Name = epgChannel.Name;
|
||||||
}
|
}
|
||||||
|
if (!string.IsNullOrWhiteSpace(epgChannel.ImageUrl))
|
||||||
|
{
|
||||||
|
tunerChannel.ImageUrl = epgChannel.ImageUrl;
|
||||||
|
tunerChannel.HasImage = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -469,16 +474,20 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
|
|
||||||
public ChannelInfo GetEpgChannelFromTunerChannel(List<NameValuePair> mappings, ChannelInfo tunerChannel, List<ChannelInfo> epgChannels)
|
public ChannelInfo GetEpgChannelFromTunerChannel(List<NameValuePair> mappings, ChannelInfo tunerChannel, List<ChannelInfo> epgChannels)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(tunerChannel.TunerChannelId))
|
var tunerChannelId = string.IsNullOrWhiteSpace(tunerChannel.TunerChannelId)
|
||||||
{
|
? tunerChannel.Id
|
||||||
var tunerChannelId = GetMappedChannel(tunerChannel.TunerChannelId, mappings);
|
: tunerChannel.TunerChannelId;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(tunerChannelId))
|
if (!string.IsNullOrWhiteSpace(tunerChannelId))
|
||||||
{
|
{
|
||||||
tunerChannelId = tunerChannel.TunerChannelId;
|
var mappedTunerChannelId = GetMappedChannel(tunerChannelId, mappings);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(mappedTunerChannelId))
|
||||||
|
{
|
||||||
|
mappedTunerChannelId = tunerChannelId;
|
||||||
}
|
}
|
||||||
|
|
||||||
var channel = epgChannels.FirstOrDefault(i => string.Equals(tunerChannelId, i.Id, StringComparison.OrdinalIgnoreCase));
|
var channel = epgChannels.FirstOrDefault(i => string.Equals(mappedTunerChannelId, i.Id, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
if (channel != null)
|
if (channel != null)
|
||||||
{
|
{
|
||||||
|
@ -1163,7 +1172,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
};
|
};
|
||||||
|
|
||||||
var isAudio = false;
|
var isAudio = false;
|
||||||
await new LiveStreamHelper(_mediaEncoder, _logger).AddMediaInfoWithProbe(stream, isAudio, cancellationToken).ConfigureAwait(false);
|
await new LiveStreamHelper(_mediaEncoder, _logger).AddMediaInfoWithProbe(stream, isAudio, false, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return new List<MediaSourceInfo>
|
return new List<MediaSourceInfo>
|
||||||
{
|
{
|
||||||
|
@ -2092,13 +2101,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))
|
||||||
|
|
|
@ -155,7 +155,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
var durationParam = " -t " + _mediaEncoder.GetTimeParameter(duration.Ticks);
|
var durationParam = " -t " + _mediaEncoder.GetTimeParameter(duration.Ticks);
|
||||||
var inputModifiers = "-fflags +genpts -async 1 -vsync -1";
|
var inputModifiers = "-fflags +genpts -async 1 -vsync -1";
|
||||||
var mapArgs = string.Equals(OutputFormat, "mkv", StringComparison.OrdinalIgnoreCase) ? "-map 0" : "-sn";
|
var mapArgs = string.Equals(OutputFormat, "mkv", StringComparison.OrdinalIgnoreCase) ? "-map 0" : "-sn";
|
||||||
// temporary
|
|
||||||
mapArgs = "-sn";
|
mapArgs = "-sn";
|
||||||
var commandLineArgs = "-i \"{0}\"{4} " + mapArgs + " {2} -map_metadata -1 -threads 0 {3} -y \"{1}\"";
|
var commandLineArgs = "-i \"{0}\"{4} " + mapArgs + " {2} -map_metadata -1 -threads 0 {3} -y \"{1}\"";
|
||||||
|
|
||||||
|
|
|
@ -152,7 +152,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
responseString);
|
responseString);
|
||||||
var programDict = programDetails.ToDictionary(p => p.programID, y => y);
|
var programDict = programDetails.ToDictionary(p => p.programID, y => y);
|
||||||
|
|
||||||
var images = await GetImageForPrograms(info, programDetails.Where(p => p.hasImageArtwork).Select(p => p.programID).ToList(), cancellationToken);
|
var programIdsWithImages =
|
||||||
|
programDetails.Where(p => p.hasImageArtwork).Select(p => p.programID)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var images = await GetImageForPrograms(info, programIdsWithImages, cancellationToken);
|
||||||
|
|
||||||
var schedules = dailySchedules.SelectMany(d => d.programs);
|
var schedules = dailySchedules.SelectMany(d => d.programs);
|
||||||
foreach (ScheduleDirect.Program schedule in schedules)
|
foreach (ScheduleDirect.Program schedule in schedules)
|
||||||
|
@ -439,13 +443,20 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
List<string> programIds,
|
List<string> programIds,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
if (programIds.Count == 0)
|
||||||
|
{
|
||||||
|
return new List<ScheduleDirect.ShowImages>();
|
||||||
|
}
|
||||||
|
|
||||||
var imageIdString = "[";
|
var imageIdString = "[";
|
||||||
|
|
||||||
foreach (var i in programIds)
|
foreach (var i in programIds)
|
||||||
{
|
{
|
||||||
if (!imageIdString.Contains(i.Substring(0, 10)))
|
var imageId = i.Substring(0, 10);
|
||||||
|
|
||||||
|
if (!imageIdString.Contains(imageId))
|
||||||
{
|
{
|
||||||
imageIdString += "\"" + i.Substring(0, 10) + "\",";
|
imageIdString += "\"" + imageId + "\",";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -461,14 +472,21 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
// The data can be large so give it some extra time
|
// The data can be large so give it some extra time
|
||||||
TimeoutMs = 60000
|
TimeoutMs = 60000
|
||||||
};
|
};
|
||||||
List<ScheduleDirect.ShowImages> images;
|
|
||||||
|
try
|
||||||
|
{
|
||||||
using (var innerResponse2 = await Post(httpOptions, true, info).ConfigureAwait(false))
|
using (var innerResponse2 = await Post(httpOptions, true, info).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
images = _jsonSerializer.DeserializeFromStream<List<ScheduleDirect.ShowImages>>(
|
return _jsonSerializer.DeserializeFromStream<List<ScheduleDirect.ShowImages>>(
|
||||||
innerResponse2.Content);
|
innerResponse2.Content);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error getting image info from schedules direct", ex);
|
||||||
|
|
||||||
return images;
|
return new List<ScheduleDirect.ShowImages>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<NameIdPair>> GetHeadends(ListingsProviderInfo info, string country, string location, CancellationToken cancellationToken)
|
public async Task<List<NameIdPair>> GetHeadends(ListingsProviderInfo info, string country, string location, CancellationToken cancellationToken)
|
||||||
|
|
|
@ -6,6 +6,7 @@ using System.Threading.Tasks;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.LiveTv
|
namespace Emby.Server.Implementations.LiveTv
|
||||||
|
@ -21,7 +22,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken)
|
public async Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, bool assumeInterlaced, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var originalRuntime = mediaSource.RunTimeTicks;
|
var originalRuntime = mediaSource.RunTimeTicks;
|
||||||
|
|
||||||
|
@ -95,6 +96,17 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
videoStream.IsAVC = null;
|
videoStream.IsAVC = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (assumeInterlaced)
|
||||||
|
{
|
||||||
|
foreach (var mediaStream in mediaSource.MediaStreams)
|
||||||
|
{
|
||||||
|
if (mediaStream.Type == MediaStreamType.Video)
|
||||||
|
{
|
||||||
|
mediaStream.IsInterlaced = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Try to estimate this
|
// Try to estimate this
|
||||||
mediaSource.InferTotalBitrate(true);
|
mediaSource.InferTotalBitrate(true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -246,9 +246,9 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
return info.Item1;
|
return info.Item1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetChannelStream(string id, string mediaSourceId, CancellationToken cancellationToken)
|
public Task<Tuple<MediaSourceInfo, IDirectStreamProvider, bool>> GetChannelStream(string id, string mediaSourceId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return await GetLiveStream(id, mediaSourceId, true, cancellationToken).ConfigureAwait(false);
|
return GetLiveStream(id, mediaSourceId, true, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetItemExternalId(BaseItem item)
|
private string GetItemExternalId(BaseItem item)
|
||||||
|
@ -308,7 +308,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
return _services.FirstOrDefault(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase));
|
return _services.FirstOrDefault(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStream(string id, string mediaSourceId, bool isChannel, CancellationToken cancellationToken)
|
private async Task<Tuple<MediaSourceInfo, IDirectStreamProvider, bool>> GetLiveStream(string id, string mediaSourceId, bool isChannel, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (string.Equals(id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
@ -319,6 +319,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
bool isVideo;
|
bool isVideo;
|
||||||
ILiveTvService service;
|
ILiveTvService service;
|
||||||
IDirectStreamProvider directStreamProvider = null;
|
IDirectStreamProvider directStreamProvider = null;
|
||||||
|
var assumeInterlaced = false;
|
||||||
|
|
||||||
if (isChannel)
|
if (isChannel)
|
||||||
{
|
{
|
||||||
|
@ -365,10 +366,14 @@ 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);
|
if (!(service is EmbyTV.EmbyTV))
|
||||||
|
{
|
||||||
|
assumeInterlaced = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Tuple<MediaSourceInfo, IDirectStreamProvider, bool>(info, directStreamProvider, assumeInterlaced);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Normalize(MediaSourceInfo mediaSource, ILiveTvService service, bool isVideo)
|
private void Normalize(MediaSourceInfo mediaSource, ILiveTvService service, bool isVideo)
|
||||||
|
|
|
@ -126,12 +126,14 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
var keys = openToken.Split(new[] { StreamIdDelimeter }, 3);
|
var keys = openToken.Split(new[] { StreamIdDelimeter }, 3);
|
||||||
var mediaSourceId = keys.Length >= 3 ? keys[2] : null;
|
var mediaSourceId = keys.Length >= 3 ? keys[2] : null;
|
||||||
IDirectStreamProvider directStreamProvider = null;
|
IDirectStreamProvider directStreamProvider = null;
|
||||||
|
var assumeInterlaced = false;
|
||||||
|
|
||||||
if (string.Equals(keys[0], typeof(LiveTvChannel).Name, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(keys[0], typeof(LiveTvChannel).Name, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var info = await _liveTvManager.GetChannelStream(keys[1], mediaSourceId, cancellationToken).ConfigureAwait(false);
|
var info = await _liveTvManager.GetChannelStream(keys[1], mediaSourceId, cancellationToken).ConfigureAwait(false);
|
||||||
stream = info.Item1;
|
stream = info.Item1;
|
||||||
directStreamProvider = info.Item2;
|
directStreamProvider = info.Item2;
|
||||||
|
assumeInterlaced = info.Item3;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -146,7 +148,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await new LiveStreamHelper(_mediaEncoder, _logger).AddMediaInfoWithProbe(stream, isAudio, cancellationToken).ConfigureAwait(false);
|
await new LiveStreamHelper(_mediaEncoder, _logger).AddMediaInfoWithProbe(stream, isAudio, assumeInterlaced, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -154,6 +156,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,8 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
{
|
{
|
||||||
var channel = _liveTvManager.GetInternalChannel(liveTvItem.ChannelId);
|
var channel = _liveTvManager.GetInternalChannel(liveTvItem.ChannelId);
|
||||||
|
|
||||||
|
if (channel != null)
|
||||||
|
{
|
||||||
var response = await service.GetProgramImageAsync(GetItemExternalId(liveTvItem), GetItemExternalId(channel), cancellationToken).ConfigureAwait(false);
|
var response = await service.GetProgramImageAsync(GetItemExternalId(liveTvItem), GetItemExternalId(channel), cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (response != null)
|
if (response != null)
|
||||||
|
@ -59,6 +61,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
imageResponse.Format = response.Format;
|
imageResponse.Format = response.Format;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch (NotImplementedException)
|
catch (NotImplementedException)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<packages>
|
<packages>
|
||||||
<package id="Emby.XmlTv" version="1.0.5" targetFramework="portable45-net45+win8" />
|
<package id="Emby.XmlTv" version="1.0.6" targetFramework="portable45-net45+win8" />
|
||||||
<package id="MediaBrowser.Naming" version="1.0.4" targetFramework="portable45-net45+win8" />
|
<package id="MediaBrowser.Naming" version="1.0.4" targetFramework="portable45-net45+win8" />
|
||||||
<package id="SQLitePCL.pretty" version="1.1.0" targetFramework="portable45-net45+win8" />
|
<package id="SQLitePCL.pretty" version="1.1.0" targetFramework="portable45-net45+win8" />
|
||||||
<package id="SQLitePCLRaw.core" version="1.1.1" targetFramework="portable45-net45+win8" />
|
<package id="SQLitePCLRaw.core" version="1.1.1" targetFramework="portable45-net45+win8" />
|
||||||
|
|
|
@ -791,7 +791,7 @@ namespace MediaBrowser.Api.LiveTv
|
||||||
ProviderChannels = providerChannels.Select(i => new NameIdPair
|
ProviderChannels = providerChannels.Select(i => new NameIdPair
|
||||||
{
|
{
|
||||||
Name = i.Name,
|
Name = i.Name,
|
||||||
Id = i.TunerChannelId
|
Id = string.IsNullOrWhiteSpace(i.TunerChannelId) ? i.Id : i.TunerChannelId
|
||||||
|
|
||||||
}).ToList(),
|
}).ToList(),
|
||||||
|
|
||||||
|
|
|
@ -119,6 +119,7 @@ namespace MediaBrowser.Api
|
||||||
config.SkipDeserializationForAudio = true;
|
config.SkipDeserializationForAudio = true;
|
||||||
config.EnableSeriesPresentationUniqueKey = true;
|
config.EnableSeriesPresentationUniqueKey = true;
|
||||||
config.EnableLocalizedGuids = true;
|
config.EnableLocalizedGuids = true;
|
||||||
|
config.EnableSimpleArtistDetection = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Post(UpdateStartupConfiguration request)
|
public void Post(UpdateStartupConfiguration request)
|
||||||
|
|
|
@ -100,6 +100,7 @@ namespace MediaBrowser.Common.Net
|
||||||
|
|
||||||
public int TimeoutMs { get; set; }
|
public int TimeoutMs { get; set; }
|
||||||
public bool PreferIpv4 { get; set; }
|
public bool PreferIpv4 { get; set; }
|
||||||
|
public bool EnableDefaultUserAgent { get; set; }
|
||||||
|
|
||||||
private string GetHeaderValue(string name)
|
private string GetHeaderValue(string name)
|
||||||
{
|
{
|
||||||
|
|
|
@ -157,7 +157,7 @@ namespace MediaBrowser.Controller.LiveTv
|
||||||
/// <param name="mediaSourceId">The media source identifier.</param>
|
/// <param name="mediaSourceId">The media source identifier.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task{StreamResponseInfo}.</returns>
|
/// <returns>Task{StreamResponseInfo}.</returns>
|
||||||
Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetChannelStream(string id, string mediaSourceId, CancellationToken cancellationToken);
|
Task<Tuple<MediaSourceInfo, IDirectStreamProvider, bool>> GetChannelStream(string id, string mediaSourceId, CancellationToken cancellationToken);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the program.
|
/// Gets the program.
|
||||||
|
|
|
@ -725,11 +725,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
|
|
||||||
if (video.Protocol != MediaProtocol.File)
|
if (video.Protocol != MediaProtocol.File)
|
||||||
{
|
{
|
||||||
// If it's mpeg based, assume true
|
|
||||||
if ((videoStream.Codec ?? string.Empty).IndexOf("mpeg", StringComparison.OrdinalIgnoreCase) != -1)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -192,6 +192,10 @@ namespace MediaBrowser.Model.Configuration
|
||||||
public bool EnableExternalContentInSuggestions { get; set; }
|
public bool EnableExternalContentInSuggestions { get; set; }
|
||||||
|
|
||||||
public int ImageExtractionTimeoutMs { get; set; }
|
public int ImageExtractionTimeoutMs { get; set; }
|
||||||
|
|
||||||
|
public PathSubstitution[] PathSubstitutions { get; set; }
|
||||||
|
public bool EnableSimpleArtistDetection { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ServerConfiguration" /> class.
|
/// Initializes a new instance of the <see cref="ServerConfiguration" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -202,6 +206,8 @@ namespace MediaBrowser.Model.Configuration
|
||||||
Migrations = new string[] { };
|
Migrations = new string[] { };
|
||||||
ImageExtractionTimeoutMs = 0;
|
ImageExtractionTimeoutMs = 0;
|
||||||
EnableLocalizedGuids = true;
|
EnableLocalizedGuids = true;
|
||||||
|
PathSubstitutions = new PathSubstitution[] { };
|
||||||
|
EnableSimpleArtistDetection = true;
|
||||||
|
|
||||||
DisplaySpecialsWithinSeasons = true;
|
DisplaySpecialsWithinSeasons = true;
|
||||||
EnableExternalContentInSuggestions = true;
|
EnableExternalContentInSuggestions = true;
|
||||||
|
@ -563,4 +569,10 @@ namespace MediaBrowser.Model.Configuration
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class PathSubstitution
|
||||||
|
{
|
||||||
|
public string From { get; set; }
|
||||||
|
public string To { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -17,6 +17,7 @@ namespace MediaBrowser.Model.System
|
||||||
{
|
{
|
||||||
Windows,
|
Windows,
|
||||||
Linux,
|
Linux,
|
||||||
OSX
|
OSX,
|
||||||
|
BSD
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -347,7 +347,8 @@ namespace MediaBrowser.Providers.Omdb
|
||||||
Url = url,
|
Url = url,
|
||||||
ResourcePool = ResourcePool,
|
ResourcePool = ResourcePool,
|
||||||
CancellationToken = cancellationToken,
|
CancellationToken = cancellationToken,
|
||||||
BufferContent = true
|
BufferContent = true,
|
||||||
|
EnableDefaultUserAgent = true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,10 +385,11 @@ namespace MediaBrowser.Providers.Omdb
|
||||||
{
|
{
|
||||||
T item = itemResult.Item;
|
T item = itemResult.Item;
|
||||||
|
|
||||||
|
var isConfiguredForEnglish = IsConfiguredForEnglish(item);
|
||||||
|
|
||||||
// Grab series genres because imdb data is better than tvdb. Leave movies alone
|
// Grab series genres because imdb data is better than tvdb. Leave movies alone
|
||||||
// But only do it if english is the preferred language because this data will not be localized
|
// But only do it if english is the preferred language because this data will not be localized
|
||||||
if (ShouldFetchGenres(item) &&
|
if (isConfiguredForEnglish && !string.IsNullOrWhiteSpace(result.Genre))
|
||||||
!string.IsNullOrWhiteSpace(result.Genre))
|
|
||||||
{
|
{
|
||||||
item.Genres.Clear();
|
item.Genres.Clear();
|
||||||
|
|
||||||
|
@ -417,8 +419,11 @@ namespace MediaBrowser.Providers.Omdb
|
||||||
hasAwards.AwardSummary = WebUtility.HtmlDecode(result.Awards);
|
hasAwards.AwardSummary = WebUtility.HtmlDecode(result.Awards);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Imdb plots are usually pretty short
|
if (isConfiguredForEnglish)
|
||||||
|
{
|
||||||
|
// Omdb is currently english only, so for other languages skip this and let secondary providers fill it in
|
||||||
item.Overview = result.Plot;
|
item.Overview = result.Plot;
|
||||||
|
}
|
||||||
|
|
||||||
//if (!string.IsNullOrWhiteSpace(result.Director))
|
//if (!string.IsNullOrWhiteSpace(result.Director))
|
||||||
//{
|
//{
|
||||||
|
@ -461,7 +466,7 @@ namespace MediaBrowser.Providers.Omdb
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ShouldFetchGenres(BaseItem item)
|
private bool IsConfiguredForEnglish(BaseItem item)
|
||||||
{
|
{
|
||||||
var lang = item.GetPreferredMetadataLanguage();
|
var lang = item.GetPreferredMetadataLanguage();
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
||||||
|
|
||||||
|
var packageCreator = GetPackageCreator(inputPath);
|
||||||
|
|
||||||
|
if (!string.Equals(inputPath, targetPath, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_fileSystem.DeleteDirectory(path, true);
|
_fileSystem.DeleteDirectory(targetPath, true);
|
||||||
}
|
}
|
||||||
catch (IOException)
|
catch (IOException)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var creator = GetPackageCreator();
|
CopyDirectory(inputPath, targetPath);
|
||||||
|
}
|
||||||
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))
|
||||||
{
|
{
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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,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))
|
||||||
|
|
Loading…
Reference in New Issue
Block a user